1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-29 22:02:57 +00:00

Compare commits

...

1 Commits

Author SHA1 Message Date
0762d8b172 misc: sheen hackathon 2025-02-25 16:13:48 +09:00
26 changed files with 1146 additions and 8 deletions

@ -84,6 +84,7 @@
"nanoid": "^3.3.8",
"nodemailer": "^6.9.9",
"odbc": "^2.4.9",
"openai": "^4.85.4",
"openid-client": "^5.6.5",
"ora": "^7.0.1",
"oracledb": "^6.4.0",
@ -11747,6 +11748,17 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/agentkeepalive": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
"integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
"dependencies": {
"humanize-ms": "^1.2.1"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
@ -14897,6 +14909,23 @@
"node": ">= 6"
}
},
"node_modules/form-data-encoder": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
},
"node_modules/formdata-node": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
"dependencies": {
"node-domexception": "1.0.0",
"web-streams-polyfill": "4.0.0-beta.3"
},
"engines": {
"node": ">= 12.20"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@ -15951,6 +15980,14 @@
"node": ">=10.17.0"
}
},
"node_modules/humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
"dependencies": {
"ms": "^2.0.0"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@ -17892,6 +17929,24 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@ -18444,6 +18499,43 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/openai": {
"version": "4.85.4",
"resolved": "https://registry.npmjs.org/openai/-/openai-4.85.4.tgz",
"integrity": "sha512-Nki51PBSu+Aryo7WKbdXvfm0X/iKkQS2fq3O0Uqb/O3b4exOZFid2te1BZ52bbO5UwxQZ5eeHJDCTqtrJLPw0w==",
"dependencies": {
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"form-data-encoder": "1.7.2",
"formdata-node": "^4.3.2",
"node-fetch": "^2.6.7"
},
"bin": {
"openai": "bin/cli"
},
"peerDependencies": {
"ws": "^8.18.0",
"zod": "^3.23.8"
},
"peerDependenciesMeta": {
"ws": {
"optional": true
},
"zod": {
"optional": true
}
}
},
"node_modules/openai/node_modules/@types/node": {
"version": "18.19.76",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz",
"integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/openapi-types": {
"version": "12.1.3",
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
@ -23790,6 +23882,14 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/web-streams-polyfill": {
"version": "4.0.0-beta.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
"engines": {
"node": ">= 14"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@ -24282,9 +24382,9 @@
}
},
"node_modules/zod": {
"version": "3.22.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
"integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==",
"version": "3.24.2",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
"integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}

@ -192,6 +192,7 @@
"nanoid": "^3.3.8",
"nodemailer": "^6.9.9",
"odbc": "^2.4.9",
"openai": "^4.85.4",
"openid-client": "^5.6.5",
"ora": "^7.0.1",
"oracledb": "^6.4.0",

@ -45,6 +45,7 @@ import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TAutomatedSecurityServiceFactory } from "@app/services/automated-security/automated-security-service";
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
@ -228,6 +229,7 @@ declare module "fastify" {
secretSync: TSecretSyncServiceFactory;
kmip: TKmipServiceFactory;
kmipOperation: TKmipOperationServiceFactory;
automatedSecurity: TAutomatedSecurityServiceFactory;
};
// this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer

@ -29,6 +29,9 @@ import {
TAuthTokenSessionsUpdate,
TAuthTokensInsert,
TAuthTokensUpdate,
TAutomatedSecurityReports,
TAutomatedSecurityReportsInsert,
TAutomatedSecurityReportsUpdate,
TBackupPrivateKey,
TBackupPrivateKeyInsert,
TBackupPrivateKeyUpdate,
@ -113,6 +116,9 @@ import {
TIdentityOrgMemberships,
TIdentityOrgMembershipsInsert,
TIdentityOrgMembershipsUpdate,
TIdentityProfile,
TIdentityProfileInsert,
TIdentityProfileUpdate,
TIdentityProjectAdditionalPrivilege,
TIdentityProjectAdditionalPrivilegeInsert,
TIdentityProjectAdditionalPrivilegeUpdate,
@ -930,5 +936,15 @@ declare module "knex/types/tables" {
TKmipClientCertificatesInsert,
TKmipClientCertificatesUpdate
>;
[TableName.IdentityProfile]: KnexOriginal.CompositeTableType<
TIdentityProfile,
TIdentityProfileInsert,
TIdentityProfileUpdate
>;
[TableName.AutomatedSecurityReports]: KnexOriginal.CompositeTableType<
TAutomatedSecurityReports,
TAutomatedSecurityReportsInsert,
TAutomatedSecurityReportsUpdate
>;
}
}

@ -0,0 +1,53 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityProfile))) {
await knex.schema.createTable(TableName.IdentityProfile, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("userId");
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.uuid("identityId");
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
t.text("temporalProfile").notNullable(); // access pattern or frequency
t.text("scopeProfile").notNullable(); // scope of usage - are they accessing development environment secrets. which paths? are they doing mainly admin work?
t.text("usageProfile").notNullable(); // method of usage
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
await createOnUpdateTrigger(knex, TableName.IdentityProfile);
}
if (!(await knex.schema.hasTable(TableName.AutomatedSecurityReports))) {
await knex.schema.createTable(TableName.AutomatedSecurityReports, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("profileId").notNullable();
t.foreign("profileId").references("id").inTable(TableName.IdentityProfile).onDelete("CASCADE");
t.jsonb("event").notNullable();
t.string("remarks").notNullable();
t.string("severity").notNullable();
t.string("status").notNullable();
t.timestamps(true, true, true);
});
await createOnUpdateTrigger(knex, TableName.AutomatedSecurityReports);
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityProfile);
await knex.schema.dropTableIfExists(TableName.AutomatedSecurityReports);
}

@ -0,0 +1,25 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const AutomatedSecurityReportsSchema = z.object({
id: z.string().uuid(),
profileId: z.string().uuid(),
event: z.unknown(),
remarks: z.string(),
severity: z.string(),
status: z.string(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TAutomatedSecurityReports = z.infer<typeof AutomatedSecurityReportsSchema>;
export type TAutomatedSecurityReportsInsert = Omit<z.input<typeof AutomatedSecurityReportsSchema>, TImmutableDBKeys>;
export type TAutomatedSecurityReportsUpdate = Partial<
Omit<z.input<typeof AutomatedSecurityReportsSchema>, TImmutableDBKeys>
>;

@ -0,0 +1,24 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const IdentityProfileSchema = z.object({
id: z.string().uuid(),
userId: z.string().uuid().nullable().optional(),
identityId: z.string().uuid().nullable().optional(),
temporalProfile: z.string(),
scopeProfile: z.string(),
usageProfile: z.string(),
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TIdentityProfile = z.infer<typeof IdentityProfileSchema>;
export type TIdentityProfileInsert = Omit<z.input<typeof IdentityProfileSchema>, TImmutableDBKeys>;
export type TIdentityProfileUpdate = Partial<Omit<z.input<typeof IdentityProfileSchema>, TImmutableDBKeys>>;

@ -7,6 +7,7 @@ export * from "./audit-log-streams";
export * from "./audit-logs";
export * from "./auth-token-sessions";
export * from "./auth-tokens";
export * from "./automated-security-reports";
export * from "./backup-private-key";
export * from "./certificate-authorities";
export * from "./certificate-authority-certs";
@ -35,6 +36,7 @@ export * from "./identity-kubernetes-auths";
export * from "./identity-metadata";
export * from "./identity-oidc-auths";
export * from "./identity-org-memberships";
export * from "./identity-profile";
export * from "./identity-project-additional-privilege";
export * from "./identity-project-membership-role";
export * from "./identity-project-memberships";

@ -80,6 +80,8 @@ export enum TableName {
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
// used by both identity and users
IdentityMetadata = "identity_metadata",
IdentityProfile = "identity_profile",
AutomatedSecurityReports = "automated_security_reports",
ResourceMetadata = "resource_metadata",
ScimToken = "scim_tokens",
AccessApprovalPolicy = "access_approval_policies",

@ -25,8 +25,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
customRateLimits: false,
customAlerts: false,
secretAccessInsights: false,
auditLogs: false,
auditLogsRetentionDays: 0,
auditLogs: true,
auditLogsRetentionDays: 3,
auditLogStreams: false,
auditLogStreamLimit: 3,
samlSSO: false,

@ -228,7 +228,9 @@ const envSchema = z
if (!val) return undefined;
return JSON.parse(val) as string[];
})
)
),
AI_API_KEY: zpStr(z.string().optional())
})
// To ensure that basic encryption is always possible.
.refine(

@ -39,6 +39,7 @@ export enum QueueName {
DynamicSecretRevocation = "dynamic-secret-revocation",
CaCrlRotation = "ca-crl-rotation",
SecretReplication = "secret-replication",
AutomatedSecurity = "automated-security",
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
ProjectV3Migration = "project-v3-migration",
AccessTokenStatusUpdate = "access-token-status-update",
@ -72,7 +73,8 @@ export enum QueueJobs {
SecretSyncSyncSecrets = "secret-sync-sync-secrets",
SecretSyncImportSecrets = "secret-sync-import-secrets",
SecretSyncRemoveSecrets = "secret-sync-remove-secrets",
SecretSyncSendActionFailedNotifications = "secret-sync-send-action-failed-notifications"
SecretSyncSendActionFailedNotifications = "secret-sync-send-action-failed-notifications",
ProfileIdentity = "profile-identity"
}
export type TQueueJobTypes = {
@ -195,6 +197,10 @@ export type TQueueJobTypes = {
};
};
};
[QueueName.AutomatedSecurity]: {
name: QueueJobs.ProfileIdentity;
payload: undefined;
};
[QueueName.AppConnectionSecretSync]:
| {
name: QueueJobs.SecretSyncSyncSecrets;

@ -105,6 +105,9 @@ import { authPaswordServiceFactory } from "@app/services/auth/auth-password-serv
import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service";
import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal";
import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { automatedSecurityReportDALFactory } from "@app/services/automated-security/automated-security-report-dal";
import { automatedSecurityServiceFactory } from "@app/services/automated-security/automated-security-service";
import { identityProfileDALFactory } from "@app/services/automated-security/identity-profile-dal";
import { certificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
import { certificateDALFactory } from "@app/services/certificate/certificate-dal";
import { certificateServiceFactory } from "@app/services/certificate/certificate-service";
@ -392,6 +395,8 @@ export const registerRoutes = async (
const kmipClientCertificateDAL = kmipClientCertificateDALFactory(db);
const kmipOrgConfigDAL = kmipOrgConfigDALFactory(db);
const kmipOrgServerCertificateDAL = kmipOrgServerCertificateDALFactory(db);
const automatedSecurityReportDAL = automatedSecurityReportDALFactory(db);
const identityProfileDAL = identityProfileDALFactory(db);
const permissionService = permissionServiceFactory({
permissionDAL,
@ -1242,6 +1247,7 @@ export const registerRoutes = async (
permissionService,
licenseService
});
const identityUaService = identityUaServiceFactory({
identityOrgMembershipDAL,
permissionService,
@ -1250,6 +1256,7 @@ export const registerRoutes = async (
identityUaDAL,
licenseService
});
const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({
identityKubernetesAuthDAL,
identityOrgMembershipDAL,
@ -1457,6 +1464,15 @@ export const registerRoutes = async (
permissionService
});
const automatedSecurityService = automatedSecurityServiceFactory({
auditLogDAL,
automatedSecurityReportDAL,
identityProfileDAL,
orgDAL,
queueService,
permissionService
});
await superAdminService.initServerCfg();
// setup the communication with license key server
@ -1557,7 +1573,8 @@ export const registerRoutes = async (
appConnection: appConnectionService,
secretSync: secretSyncService,
kmip: kmipService,
kmipOperation: kmipOperationService
kmipOperation: kmipOperationService,
automatedSecurity: automatedSecurityService
});
const cronJobs: CronJob[] = [];

@ -0,0 +1,72 @@
import z from "zod";
import { AutomatedSecurityReportsSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerAutomatedSecurityRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/trigger",
handler: async () => {
return server.services.automatedSecurity.processSecurityJob();
}
});
server.route({
method: "GET",
url: "/reports",
schema: {
response: {
200: AutomatedSecurityReportsSchema.extend({
userId: z.string().nullish(),
name: z.string().nullish()
}).array()
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
return server.services.automatedSecurity.getReports(req.permission.orgId);
}
});
server.route({
method: "PATCH",
url: "/reports/:id/status",
schema: {
params: z.object({
id: z.string()
}),
response: {
200: AutomatedSecurityReportsSchema.extend({
userId: z.string().nullish(),
name: z.string().nullish()
}).array()
},
body: z.object({
status: z.enum(["ignored", "resolved"])
})
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
return server.services.automatedSecurity.patchSecurityReportStatus(req.params.id, req.body.status);
}
});
server.route({
method: "POST",
url: "/project-permission/analyze",
schema: {
body: z.object({
projectId: z.string(),
userId: z.string()
}),
response: {
200: z.any()
}
},
handler: async (req) => {
return server.services.automatedSecurity.analyzeIdentityProjectPermission(req.body.userId, req.body.projectId);
}
});
};

@ -8,6 +8,7 @@ import { registerSecretSyncRouter, SECRET_SYNC_REGISTER_ROUTER_MAP } from "@app/
import { registerAdminRouter } from "./admin-router";
import { registerAuthRoutes } from "./auth-router";
import { registerAutomatedSecurityRouter } from "./automated-security-router";
import { registerProjectBotRouter } from "./bot-router";
import { registerCaRouter } from "./certificate-authority-router";
import { registerCertRouter } from "./certificate-router";
@ -141,4 +142,6 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
},
{ prefix: "/secret-syncs" }
);
await server.register(registerAutomatedSecurityRouter, { prefix: "/automated-security" });
};

@ -0,0 +1,49 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TAutomatedSecurityReportDALFactory = ReturnType<typeof automatedSecurityReportDALFactory>;
export const automatedSecurityReportDALFactory = (db: TDbClient) => {
const automatedSecurityReportOrm = ormify(db, TableName.AutomatedSecurityReports);
const findByOrg = (orgId: string, status = "pending") => {
return db(TableName.AutomatedSecurityReports)
.join(
TableName.IdentityProfile,
`${TableName.IdentityProfile}.id`,
`${TableName.AutomatedSecurityReports}.profileId`
)
.join(TableName.Users, `${TableName.Users}.id`, `${TableName.IdentityProfile}.userId`)
.where(`${TableName.IdentityProfile}.orgId`, "=", orgId)
.where(`${TableName.AutomatedSecurityReports}.status`, "=", status)
.select(
selectAllTableCols(TableName.AutomatedSecurityReports),
db.ref("userId").withSchema(TableName.IdentityProfile).as("userId"),
db.ref("email").withSchema(TableName.Users).as("name")
);
};
const findById = (id: string) => {
return db(TableName.AutomatedSecurityReports)
.join(
TableName.IdentityProfile,
`${TableName.IdentityProfile}.id`,
`${TableName.AutomatedSecurityReports}.profileId`
)
.join(TableName.Users, `${TableName.Users}.id`, `${TableName.IdentityProfile}.userId`)
.where(`${TableName.AutomatedSecurityReports}.id`, "=", id)
.select(
selectAllTableCols(TableName.AutomatedSecurityReports),
selectAllTableCols(TableName.IdentityProfile),
db.ref("email").withSchema(TableName.Users).as("name")
)
.first();
};
return {
...automatedSecurityReportOrm,
findByOrg,
findById
};
};

@ -0,0 +1,540 @@
import { OpenAI } from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import z from "zod";
import { ActionProjectType, TAuditLogs } from "@app/db/schemas";
import { TAuditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { getConfig } from "@app/lib/config/env";
import { NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { AuthMethod } from "../auth/auth-type";
import { TOrgDALFactory } from "../org/org-dal";
import { TAutomatedSecurityReportDALFactory } from "./automated-security-report-dal";
import { TIdentityProfileDALFactory } from "./identity-profile-dal";
type TAutomatedSecurityServiceFactoryDep = {
auditLogDAL: TAuditLogDALFactory;
automatedSecurityReportDAL: TAutomatedSecurityReportDALFactory;
permissionService: TPermissionServiceFactory;
identityProfileDAL: TIdentityProfileDALFactory;
queueService: Pick<TQueueServiceFactory, "start" | "listen" | "queue" | "stopJobById" | "stopRepeatableJob">;
orgDAL: TOrgDALFactory;
};
export type TAutomatedSecurityServiceFactory = ReturnType<typeof automatedSecurityServiceFactory>;
export const ProfileIdentityResponseSchema = z.object({
temporalProfile: z.string(),
scopeProfile: z.string(),
usageProfile: z.string()
});
enum AnomalySeverity {
LOW = "LOW",
MEDIUM = "MEDIUM",
HIGH = "HIGH",
NONE = "NONE"
}
export const AnomalyResponseSchema = z.object({
results: z
.object({
auditLogId: z.string(),
reason: z.string(),
severity: z.nativeEnum(AnomalySeverity),
isAnomalous: z.boolean()
})
.array()
});
export const CaslRuleAnalysisSchema = z.object({
results: z
.object({
rule: z.string(),
classification: z.string(),
justification: z.string()
})
.array()
});
export const automatedSecurityServiceFactory = ({
auditLogDAL,
automatedSecurityReportDAL,
identityProfileDAL,
permissionService,
queueService,
orgDAL
}: TAutomatedSecurityServiceFactoryDep) => {
const getReports = async (orgId: string) => {
const automatedReports = await automatedSecurityReportDAL.findByOrg(orgId, "pending");
return automatedReports;
};
const patchSecurityReportStatus = async (id: string, status: "resolved" | "ignored") => {
const appCfg = getConfig();
const securityReport = await automatedSecurityReportDAL.findById(id);
if (!securityReport) {
throw new NotFoundError({
message: "Cannot find security report"
});
}
if (status === "ignored") {
const openAiClient = new OpenAI({
apiKey: appCfg.AI_API_KEY
});
const profileResponse = await openAiClient.beta.chat.completions.parse({
model: "gpt-4o",
messages: [
{
role: "system",
content: `You are a security behavior analysis system that builds and maintains behavioral profiles.
Your task is to analyze security events and prior profile information to generate cumulative profiles.
Focus on three aspects:
1. Temporal patterns (WHEN/HOW OFTEN)
2. Scope patterns (WHAT/WHERE)
3. Usage patterns (HOW)
Build comprehensive profiles:
- Each profile should be 5-7 sentences to capture both established and emerging patterns
- Core patterns get 1-2 sentences
- Notable exceptions get 1-2 sentences
- Emerging behaviors get 1-2 sentences
- Key correlations get 1 sentence
Build profiles incrementally:
- ALWAYS retain existing profile information unless directly contradicted
- Add ANY new observed patterns, even from limited samples
- Mark patterns as "emerging" if based on few observations
- Use qualifiers like "occasionally" or "sometimes" for sparse patterns
- Never dismiss patterns as insignificant due to sample size
- Maintain history of both frequent and rare behaviors
When updating profiles:
- Keep all historical patterns unless explicitly contradicted
- Add new patterns with appropriate frequency qualifiers
- Note both common and occasional behaviors
- Use tentative language for new patterns ("appears to", "beginning to")
- Highlight any changes or new observations
Remember that dates being passed in are in ISO format`
},
{
role: "user",
content: `Current behavioral profiles:
${JSON.stringify(
{
temporalProfile: securityReport.temporalProfile,
scopeProfile: securityReport.scopeProfile,
usageProfile: securityReport.usageProfile
},
null,
2
)}
New security event to analyze:
${JSON.stringify(securityReport.event)}
Generate updated profiles that preserve ALL existing patterns and add new observations.
Use appropriate qualifiers for frequency but include all behaviors.
Return exactly in this format:
temporalProfile: "..."
scopeProfile: "..."
usageProfile: "..."`
}
],
response_format: zodResponseFormat(ProfileIdentityResponseSchema, "profile_identity_response_schema")
});
const parsedResponse = profileResponse.choices[0].message.parsed;
await identityProfileDAL.updateById(securityReport.profileId, {
temporalProfile: parsedResponse?.temporalProfile ?? "",
usageProfile: parsedResponse?.usageProfile ?? "",
scopeProfile: parsedResponse?.scopeProfile ?? ""
});
}
await automatedSecurityReportDAL.updateById(id, {
status
});
};
const processSecurityJob = async () => {
const appCfg = getConfig();
const orgs = await orgDAL.find({
id: "1edc8c6e-8a39-499e-89a2-5d26149149cb"
});
await Promise.allSettled(
orgs.map(async (org) => {
const orgUsers = await orgDAL.findAllOrgMembers(org.id);
const dateNow = new Date();
const startDate = new Date(dateNow);
startDate.setMinutes(dateNow.getMinutes() - 30);
await Promise.all(
orgUsers.map(async (orgUser) => {
try {
const openAiClient = new OpenAI({
apiKey: appCfg.AI_API_KEY
});
const auditLogEvents = await auditLogDAL.find({
actorId: orgUser.user.id,
orgId: org.id,
startDate: startDate.toISOString(),
limit: 100
});
if (!auditLogEvents.length) {
return;
}
let normalEvents: TAuditLogs[] = auditLogEvents;
const identityProfile = await identityProfileDAL.transaction(async (tx) => {
const profile = await identityProfileDAL.findOne(
{
userId: orgUser.user.id
},
tx
);
if (!profile) {
return identityProfileDAL.create(
{
userId: orgUser.user.id,
temporalProfile: "",
usageProfile: "",
scopeProfile: "",
orgId: org.id
},
tx
);
}
return profile;
});
if (identityProfile.usageProfile && identityProfile.scopeProfile && identityProfile.temporalProfile) {
logger.info("Checking for anomalies...");
const anomalyResponse = await openAiClient.beta.chat.completions.parse({
model: "gpt-4o",
messages: [
{
role: "system",
content: `
You are a security analysis system that uses behavioral profiles to identify potentially suspicious activity for an identity.
Understanding Identity Profiles:
- Temporal Profile: When and how frequently the identity normally operates
- Scope Profile: What resources and permissions the identity typically uses
- Usage Profile: How the identity normally interacts with systems
Security Analysis Rules:
HIGH Severity - Clear security concerns:
- Actions well outside the identity's normal scope of access
- Resource access patterns suggesting compromise
- Violation of critical security boundaries
MEDIUM Severity - Requires investigation:
- Significant expansion of identity's normal access patterns
- Unusual combination of valid permissions
- Behavior suggesting possible credential misuse
NONE Severity (Default) - Expected behavior:
- Actions within established identity patterns
- Minor variations in normal behavior
- Business-justified changes in access patterns
- New but authorized behavior extensions
Note: LOW severity should not be used - an action either
indicates a security concern (HIGH/MEDIUM) or it doesn't (NONE).
Analysis Process:
1. Compare event against identity's established profiles
2. Evaluate if deviations suggest potential security risks
3. Consider business context and authorization
4. Mark as anomalous ONLY if the deviation suggests security risk`
},
{
role: "user",
content: `Given these established behavioral profiles for an identity:
temporalProfile: "${identityProfile.temporalProfile}"
scopeProfile: "${identityProfile.scopeProfile}"
usageProfile: "${identityProfile.usageProfile}"
Analyze these events for anomalies:
${JSON.stringify(auditLogEvents, null, 2)}
Return response in this exact JSON format:
{ results: [
{
"auditLogId": "event-123",
"isAnomalous": false,
"reason": "",
"severity": "NONE"
},
{
"auditLogId": "event-124",
"isAnomalous": true,
"reason": "Accessing production secrets outside business hours",
"severity": "HIGH"
}
]}
`
}
],
response_format: zodResponseFormat(AnomalyResponseSchema, "anomaly_response_schema")
});
const parsedAnomalyResponse = anomalyResponse.choices[0].message.parsed;
const anomalyEvents = parsedAnomalyResponse?.results?.filter((val) => val.isAnomalous);
console.log("ANOMALY EVENTS:", anomalyEvents);
const anomalyEventMap = anomalyEvents?.reduce((accum, item) => {
return { ...accum, [item.auditLogId]: item };
}, {}) as {
[x: string]: {
reason: string;
severity: string;
};
};
const anomalousEventIds = anomalyEvents?.map((evt) => evt.auditLogId);
normalEvents = auditLogEvents.filter((evt) => !anomalousEventIds?.includes(evt.id));
await Promise.all(
(auditLogEvents ?? [])?.map(async (evt) => {
const anomalyDetails = anomalyEventMap[evt.id];
if (!anomalyDetails) {
return;
}
await automatedSecurityReportDAL.create({
status: "pending",
profileId: identityProfile.id,
remarks: anomalyDetails.reason,
severity: anomalyDetails.severity,
event: JSON.stringify(evt, null, 2)
});
})
);
}
console.log("Calibrating identity profile");
// profile identity
const profileResponse = await openAiClient.beta.chat.completions.parse({
model: "gpt-4o",
messages: [
{
role: "system",
content: `You are a security behavior analysis system that builds and maintains behavioral profiles.
Your task is to analyze security events and prior profile information to generate cumulative profiles.
Focus on three aspects:
1. Temporal patterns (WHEN/HOW OFTEN)
2. Scope patterns (WHAT/WHERE)
3. Usage patterns (HOW)
Build comprehensive profiles:
- Each profile should be 5-7 sentences to capture both established and emerging patterns
- Core patterns get 1-2 sentences
- Notable exceptions get 1-2 sentences
- Emerging behaviors get 1-2 sentences
- Key correlations get 1 sentence
Build profiles incrementally:
- ALWAYS retain existing profile information unless directly contradicted
- Add ANY new observed patterns, even from limited samples
- Mark patterns as "emerging" if based on few observations
- Use qualifiers like "occasionally" or "sometimes" for sparse patterns
- Never dismiss patterns as insignificant due to sample size
- Maintain history of both frequent and rare behaviors
When updating profiles:
- Keep all historical patterns unless explicitly contradicted
- Add new patterns with appropriate frequency qualifiers
- Note both common and occasional behaviors
- Use tentative language for new patterns ("appears to", "beginning to")
- Highlight any changes or new observations
Remember that dates being passed in are in ISO format`
},
{
role: "user",
content: `Current behavioral profiles:
${JSON.stringify(
{
temporalProfile: identityProfile.temporalProfile,
scopeProfile: identityProfile.scopeProfile,
usageProfile: identityProfile.usageProfile
},
null,
2
)}
New security events to analyze:
${JSON.stringify(normalEvents, null, 2)}
Generate updated profiles that preserve ALL existing patterns and add new observations.
Use appropriate qualifiers for frequency but include all behaviors.
Return exactly in this format:
temporalProfile: "..."
scopeProfile: "..."
usageProfile: "..."`
}
],
response_format: zodResponseFormat(ProfileIdentityResponseSchema, "profile_identity_response_schema")
});
const parsedResponse = profileResponse.choices[0].message.parsed;
await identityProfileDAL.updateById(identityProfile.id, {
temporalProfile: parsedResponse?.temporalProfile ?? "",
usageProfile: parsedResponse?.usageProfile ?? "",
scopeProfile: parsedResponse?.scopeProfile ?? ""
});
console.log("FINISH");
} catch (err) {
logger.error(err);
throw err;
}
})
);
})
);
};
const analyzeIdentityProjectPermission = async (userId: string, projectId: string) => {
const appCfg = getConfig();
const userProjectPermission = await permissionService.getUserProjectPermission({
userId,
projectId,
authMethod: AuthMethod.EMAIL,
actionProjectType: ActionProjectType.Any,
userOrgId: "1edc8c6e-8a39-499e-89a2-5d26149149cb"
});
const identityProfile = await identityProfileDAL.findOne({
userId
});
const openAiClient = new OpenAI({
apiKey: appCfg.AI_API_KEY
});
const caslRuleAnalysisResponse = await openAiClient.beta.chat.completions.parse({
model: "gpt-4o",
messages: [
{
role: "system",
content: `You are a security analysis system that evaluates CASL permission rules against behavioral profiles.
Your task is to identify CASL rules that appear unnecessary based on the identity's observed behavior.
Analysis process:
1. Review the identity's behavioral profiles (temporal, scope, usage)
2. Examine current CASL permission rules
3. Identify rules that grant permissions never or rarely used based on the profiles
4. Consider business context and security implications before marking rules as unnecessary
Classification criteria:
- UNNECESSARY: Rule grants permissions never observed in the behavior profile
- OVERPROVISIONED: Rule grants broader access than typically used
- QUESTIONABLE: Rule grants permissions used very rarely (<5% of activity)
Provide justification for each classification based on specific patterns in the profiles.
Consider both explicit patterns (directly mentioned) and implicit patterns (strongly implied).
Remember that security is the priority - when in doubt, mark a rule as QUESTIONABLE rather than UNNECESSARY.`
},
{
role: "user",
content: `Identity Behavioral Profiles:
${JSON.stringify(
{
temporalProfile: identityProfile.temporalProfile,
scopeProfile: identityProfile.scopeProfile,
usageProfile: identityProfile.usageProfile
},
null,
2
)}
Current CASL Permission Rules:
${JSON.stringify(userProjectPermission.permission.rules, null, 2)}
Analyze these rules against the behavioral profiles and identify which rules appear unnecessary.
Return in this exact format:
{
"results": [
{
"rule": "create secret",
"classification": "UNNECESSARY",
"justification": "No evidence in profile of needing this resource",
},
{
"rule": "delete secret",
"classification": "NECESSARY",
"justification": "Regular access pattern in usage profile",
}
]
}`
}
],
response_format: zodResponseFormat(CaslRuleAnalysisSchema, "casl_rule_analysis_schema")
});
return caslRuleAnalysisResponse.choices[0].message.parsed;
};
const startJob = async () => {
await queueService.stopRepeatableJob(
QueueName.AutomatedSecurity,
QueueJobs.ProfileIdentity,
{ pattern: "0 0 * * *", utc: true },
QueueName.AutomatedSecurity // just a job id
);
await queueService.queue(QueueName.AutomatedSecurity, QueueJobs.ProfileIdentity, undefined, {
delay: 5000,
jobId: QueueName.AutomatedSecurity,
repeat: { pattern: "0 0 * * *", utc: true }
});
};
queueService.start(QueueName.AutomatedSecurity, async (job) => {
if (job.name === QueueJobs.ProfileIdentity) {
await processSecurityJob();
}
});
queueService.listen(QueueName.AutomatedSecurity, "failed", (job, err) => {
logger.error(err, "Failed to process job", job?.data);
});
return {
processSecurityJob,
patchSecurityReportStatus,
analyzeIdentityProjectPermission,
startJob,
getReports
};
};

@ -0,0 +1,11 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TIdentityProfileDALFactory = ReturnType<typeof identityProfileDALFactory>;
export const identityProfileDALFactory = (db: TDbClient) => {
const identityProfileOrm = ormify(db, TableName.IdentityProfile);
return identityProfileOrm;
};

@ -0,0 +1,2 @@
export * from "./mutations";
export * from "./queries";

@ -0,0 +1,20 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { automatedSecurityKeys } from "./queries";
export const usePatchSecurityReportStatus = (orgId: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, status }: { id: string; status: string }) => {
await apiRequest.patch(`/api/v1/automated-security/reports/${id}/status`, {
status
});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: automatedSecurityKeys.getReports(orgId) });
}
});
};

@ -0,0 +1,29 @@
import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
export const automatedSecurityKeys = {
getReports: (orgId: string) => [{ orgId }, "organization-security-reports"] as const
};
export const useGetAutomatedSecurityReports = (orgId: string) => {
return useQuery({
queryKey: automatedSecurityKeys.getReports(orgId),
queryFn: async () => {
const { data } = await apiRequest.get<
{
id: string;
profileId: string;
event: string;
remarks: string;
severity: string;
status: string;
userId: string;
name: string;
}[]
>("/api/v1/automated-security/reports");
return data;
}
});
};

@ -348,6 +348,11 @@ export const MinimizedOrgSidebar = () => {
Audit Logs
</DropdownMenuItem>
</Link>
<Link to="/organization/automated-security">
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faCog} />}>
Automated Security
</DropdownMenuItem>
</Link>
<Link to="/organization/settings">
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faCog} />}>
Organization Settings

@ -0,0 +1,103 @@
import { Helmet } from "react-helmet";
import { createNotification } from "@app/components/notifications";
import {
Button,
PageHeader,
Table,
TableContainer,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import {
useGetAutomatedSecurityReports,
usePatchSecurityReportStatus
} from "@app/hooks/api/automated-security";
export const AutomatedSecurityPage = () => {
const { currentOrg } = useOrganization();
const { data: reportEntries } = useGetAutomatedSecurityReports(currentOrg.id);
const { mutateAsync: patchSecurityReportStatus } = usePatchSecurityReportStatus(currentOrg.id);
return (
<div className="h-full bg-bunker-800">
<Helmet>
<title>Infisical | Automated Security</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
</Helmet>
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">
<PageHeader
title="Automated Security"
description="Your organization's reliable AI security guard"
/>
<TableContainer>
<Table>
<THead>
<Tr>
<Th>Identity ID</Th>
<Th>Name</Th>
<Th>Event</Th>
<Th>Remarks</Th>
<Th>Severity</Th>
<Th />
</Tr>
</THead>
<TBody>
{(reportEntries ?? []).map((entry) => (
<Tr>
<Td>{entry.userId}</Td>
<Td>{entry.name}</Td>
<Td>{JSON.stringify(entry.event, null, 2)}</Td>
<Td>{entry.remarks}</Td>
<Td>{entry.severity}</Td>
<Td>
<Button
className="mb-4 w-[5rem]"
onClick={async () => {
await patchSecurityReportStatus({
id: entry.id,
status: "resolved"
});
createNotification({
type: "success",
text: "Successfully resolved security report"
});
}}
>
Resolve
</Button>
<Button
colorSchema="secondary"
className="w-[5rem]"
onClick={async () => {
await patchSecurityReportStatus({
id: entry.id,
status: "ignored"
});
createNotification({
type: "success",
text: "Successfully ignored security report"
});
}}
>
Ignore
</Button>
</Td>
</Tr>
))}
</TBody>
</Table>
</TableContainer>
</div>
</div>
</div>
);
};

@ -0,0 +1,23 @@
import { faHome } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { AutomatedSecurityPage } from "./AutomatedSecurityPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/organization/automated-security"
)({
component: AutomatedSecurityPage,
context: () => ({
breadcrumbs: [
{
label: "Home",
icon: () => <FontAwesomeIcon icon={faHome} />,
link: linkOptions({ to: "/organization/secret-manager/overview" })
},
{
label: "Automated Security Page"
}
]
})
});

@ -44,6 +44,7 @@ import { Route as organizationSettingsPageRouteImport } from './pages/organizati
import { Route as organizationSecretSharingPageRouteImport } from './pages/organization/SecretSharingPage/route'
import { Route as organizationSecretScanningPageRouteImport } from './pages/organization/SecretScanningPage/route'
import { Route as organizationBillingPageRouteImport } from './pages/organization/BillingPage/route'
import { Route as organizationAutomatedSecurityPageRouteImport } from './pages/organization/AutomatedSecurityPage/route'
import { Route as organizationAuditLogsPageRouteImport } from './pages/organization/AuditLogsPage/route'
import { Route as organizationAdminPageRouteImport } from './pages/organization/AdminPage/route'
import { Route as organizationAccessManagementPageRouteImport } from './pages/organization/AccessManagementPage/route'
@ -508,6 +509,14 @@ const organizationBillingPageRouteRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationAutomatedSecurityPageRouteRoute =
organizationAutomatedSecurityPageRouteImport.update({
id: '/automated-security',
path: '/automated-security',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationAuditLogsPageRouteRoute =
organizationAuditLogsPageRouteImport.update({
id: '/audit-logs',
@ -1837,6 +1846,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof organizationAuditLogsPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/automated-security': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/automated-security'
path: '/automated-security'
fullPath: '/organization/automated-security'
preLoaderRoute: typeof organizationAutomatedSecurityPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/billing': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/billing'
path: '/billing'
@ -2904,6 +2920,7 @@ interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren {
organizationAccessManagementPageRouteRoute: typeof organizationAccessManagementPageRouteRoute
organizationAdminPageRouteRoute: typeof organizationAdminPageRouteRoute
organizationAuditLogsPageRouteRoute: typeof organizationAuditLogsPageRouteRoute
organizationAutomatedSecurityPageRouteRoute: typeof organizationAutomatedSecurityPageRouteRoute
organizationBillingPageRouteRoute: typeof organizationBillingPageRouteRoute
organizationSecretScanningPageRouteRoute: typeof organizationSecretScanningPageRouteRoute
organizationSecretSharingPageRouteRoute: typeof organizationSecretSharingPageRouteRoute
@ -2925,6 +2942,8 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: Authentica
organizationAccessManagementPageRouteRoute,
organizationAdminPageRouteRoute: organizationAdminPageRouteRoute,
organizationAuditLogsPageRouteRoute: organizationAuditLogsPageRouteRoute,
organizationAutomatedSecurityPageRouteRoute:
organizationAutomatedSecurityPageRouteRoute,
organizationBillingPageRouteRoute: organizationBillingPageRouteRoute,
organizationSecretScanningPageRouteRoute:
organizationSecretScanningPageRouteRoute,
@ -3601,6 +3620,7 @@ export interface FileRoutesByFullPath {
'/organization/access-management': typeof organizationAccessManagementPageRouteRoute
'/organization/admin': typeof organizationAdminPageRouteRoute
'/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
'/organization/automated-security': typeof organizationAutomatedSecurityPageRouteRoute
'/organization/billing': typeof organizationBillingPageRouteRoute
'/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute
'/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute
@ -3771,6 +3791,7 @@ export interface FileRoutesByTo {
'/organization/access-management': typeof organizationAccessManagementPageRouteRoute
'/organization/admin': typeof organizationAdminPageRouteRoute
'/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
'/organization/automated-security': typeof organizationAutomatedSecurityPageRouteRoute
'/organization/billing': typeof organizationBillingPageRouteRoute
'/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute
'/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute
@ -3949,6 +3970,7 @@ export interface FileRoutesById {
'/_authenticate/_inject-org-details/_org-layout/organization/access-management': typeof organizationAccessManagementPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/admin': typeof organizationAdminPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/automated-security': typeof organizationAutomatedSecurityPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/billing': typeof organizationBillingPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute
@ -4129,6 +4151,7 @@ export interface FileRouteTypes {
| '/organization/access-management'
| '/organization/admin'
| '/organization/audit-logs'
| '/organization/automated-security'
| '/organization/billing'
| '/organization/secret-scanning'
| '/organization/secret-sharing'
@ -4298,6 +4321,7 @@ export interface FileRouteTypes {
| '/organization/access-management'
| '/organization/admin'
| '/organization/audit-logs'
| '/organization/automated-security'
| '/organization/billing'
| '/organization/secret-scanning'
| '/organization/secret-sharing'
@ -4474,6 +4498,7 @@ export interface FileRouteTypes {
| '/_authenticate/_inject-org-details/_org-layout/organization/access-management'
| '/_authenticate/_inject-org-details/_org-layout/organization/admin'
| '/_authenticate/_inject-org-details/_org-layout/organization/audit-logs'
| '/_authenticate/_inject-org-details/_org-layout/organization/automated-security'
| '/_authenticate/_inject-org-details/_org-layout/organization/billing'
| '/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning'
| '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing'
@ -4843,6 +4868,7 @@ export const routeTree = rootRoute
"/_authenticate/_inject-org-details/_org-layout/organization/access-management",
"/_authenticate/_inject-org-details/_org-layout/organization/admin",
"/_authenticate/_inject-org-details/_org-layout/organization/audit-logs",
"/_authenticate/_inject-org-details/_org-layout/organization/automated-security",
"/_authenticate/_inject-org-details/_org-layout/organization/billing",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing",
@ -4881,6 +4907,10 @@ export const routeTree = rootRoute
"filePath": "organization/AuditLogsPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/organization/automated-security": {
"filePath": "organization/AutomatedSecurityPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/organization/billing": {
"filePath": "organization/BillingPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"

@ -15,6 +15,7 @@ const organizationRoutes = route("/organization", [
route("/access-management", "organization/AccessManagementPage/route.tsx"),
route("/admin", "organization/AdminPage/route.tsx"),
route("/audit-logs", "organization/AuditLogsPage/route.tsx"),
route("/automated-security", "organization/AutomatedSecurityPage/route.tsx"),
route("/billing", "organization/BillingPage/route.tsx"),
route("/secret-sharing", "organization/SecretSharingPage/route.tsx"),
route("/settings", "organization/SettingsPage/route.tsx"),