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

View File

@ -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"
}

View File

@ -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",

View File

@ -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

View File

@ -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
>;
}
}

View File

@ -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);
}

View File

@ -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>
>;

View File

@ -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>>;

View File

@ -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";

View File

@ -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",

View File

@ -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,

View File

@ -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(

View File

@ -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;

View File

@ -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[] = [];

View File

@ -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);
}
});
};

View File

@ -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" });
};

View File

@ -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
};
};

View File

@ -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
};
};

View File

@ -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;
};

View File

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

View File

@ -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) });
}
});
};

View File

@ -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;
}
});
};

View File

@ -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

View File

@ -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>
);
};

View File

@ -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"
}
]
})
});

View File

@ -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"

View File

@ -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"),