Describe the bug
A source code review of the full monorepo and a browser inspection of the live demo at tryaisoc.com surfaced 13 bugs — all fixable with minimal code changes (1–5 lines each), no infrastructure or migration work required.
Note: I'd like to pick up the implementation for these fixes once this issue has been reviewed and verified by the repo owner. Happy to submit PRs for whichever items are confirmed.
1. Unauthenticated EASM /scan endpoint
Steps to reproduce: Send a POST to /api/v1/easm/scan with a ScanRequest body containing any tenant_id — no auth header needed.
Expected behavior: The endpoint should require authentication like the neighboring /assets and /drift endpoints in the same file.
Actual behavior: No Depends(get_current_user) guard exists, so any unauthenticated caller can trigger EASM discovery scans against any tenant using the operator's Shodan/Censys API keys.
Component: services/api/app/api/v1/endpoints/easm.py
2. EASM /assets and /drift cross-tenant data access
Steps to reproduce: Authenticate as user in tenant A, then call GET /api/v1/easm/assets?tenant_id=<tenant_B_id>.
Expected behavior: Request should be rejected or scoped to the caller's own tenant.
Actual behavior: The endpoint uses effective_tenant = tenant_id or current_user.tenant_id without verifying ownership, so any authenticated user can read another tenant's external assets and drift events.
Component: services/api/app/api/v1/endpoints/easm.py
3. .env.example contains a working Fernet encryption key
Steps to reproduce: Copy .env.example to .env as instructed by the README, then inspect AISOC_CREDENTIAL_KEY.
Expected behavior: The example file should contain a placeholder prompting the operator to generate their own key.
Actual behavior: A valid, base64-encoded Fernet key is shipped. Operators who don't rotate it encrypt all connector credentials under a publicly-known key — anyone with DB access can decrypt stored API tokens, OAuth secrets, and service account keys.
Component: .env.example
4. Database exception details exposed to API clients
Steps to reproduce: Trigger a database error on any of the affected endpoints (e.g., by sending malformed data to knowledge base, compliance, or case endpoints).
Expected behavior: A generic error message like "Internal database error" should be returned.
Actual behavior: Raw Postgres exception messages including table names, column names, constraint names, and SQL fragments are returned via raise HTTPException(detail=f"Database error: {exc}"). Approximately 8 occurrences across knowledge_base.py, compliance.py, and cases.py.
Component: services/api
5. ITSM webhook broken — wrong column name in SQL
Steps to reproduce: Send a webhook payload to the ITSM inbox endpoint.
Expected behavior: The webhook should be processed successfully.
Actual behavior: Raw SQL query references column enabled but the ORM model defines it as is_enabled, causing a runtime ProgrammingError on every call. The entire ITSM webhook integration is non-functional.
Component: services/api/app/api/v1/endpoints/inbox_itsm.py
6. Marketplace endpoint missing path traversal guard
Steps to reproduce: If the marketplace index JSON contains a relative path like ../../etc/passwd, the _resolve_item_path function resolves it without checking bounds.
Expected behavior: Resolved paths should be validated to stay within the repository root via is_relative_to().
Actual behavior: After .resolve(), there is no bounds check, so a manipulated index entry could serve files outside the repo directory.
Component: services/api/app/api/v1/endpoints/marketplace.py
7. Login page open redirect
Steps to reproduce: Navigate to /login?next=https://evil.com, then log in.
Expected behavior: The next parameter should only accept relative paths.
Actual behavior: The value is passed directly to router.replace(next) without validation, enabling phishing attacks where users are redirected to attacker-controlled sites after login.
Component: apps/web/src/app/login/page.tsx
8. GraphQL search vulnerable to wildcard DoS
Steps to reproduce: Send a GraphQL query with search set to %_%_%_%_%_%_%_%_%_%_%_.
Expected behavior: Wildcard characters in user input should be escaped before use in ilike patterns.
Actual behavior: Alert.title.ilike(f"%{search}%") passes unescaped user input, allowing crafted patterns that force expensive full-table scans on the database.
Component: services/api/app/graphql/query.py
9. Prometheus metrics cardinality bomb
Steps to reproduce: Generate traffic to endpoints with dynamic path segments (e.g., /api/v1/connectors/{uuid}), then check Prometheus memory usage over time.
Expected behavior: Metrics should use route templates (e.g., /api/v1/connectors/{connector_id}) for labels.
Actual behavior: endpoint = request.url.path uses raw URLs including UUIDs, creating an unbounded number of time series that grow monotonically and eventually cause OOM.
Component: services/api/app/main.py
10. Insecure default values for internal auth tokens
Steps to reproduce: Deploy without setting REALTIME_INTERNAL_TOKEN, JWT_SECRET, or alert_webhook_secret environment variables.
Expected behavior: The application should fail to start with a clear error if critical secrets are missing.
Actual behavior: These fall back to "changeme" or "changeme-insecure-default", effectively bypassing inter-service authentication and JWT signing if operators don't explicitly configure them.
Component: services/agents/app/playbook/engine.py, services/api/app/auth/oidc.py, services/api/app/auth/saml.py, services/honeytokens/app/core/config.py
11. Duplicate site name in browser tab titles
Steps to reproduce: Visit any console page (e.g., /dashboard, /alerts) and check the browser tab title.
Expected behavior: Dashboard | AiSOC
Actual behavior: Dashboard | AiSOC | AiSOC — the Next.js metadata.title.template is doubling the site name on every console page.
Component: Root layout metadata in apps/web
12. Missing page titles on /honeytokens and /purple-team
Steps to reproduce: Navigate to /honeytokens or /purple-team and check the browser tab title.
Expected behavior: Page-specific titles like Honeytokens | AiSOC.
Actual behavior: Both fall back to the generic homepage title AiSOC -- Open-Source AI Security Operations Center because they're missing their metadata.title export.
Component: apps/web/src/app/(console)/honeytokens/page.tsx, apps/web/src/app/(console)/purple-team/page.tsx
13. Inconsistent title separator characters across pages
Steps to reproduce: Compare browser tab titles across different console pages.
Expected behavior: All pages should use the same separator convention.
Actual behavior: Some pages use | (e.g., Dashboard | AiSOC) while others use -- (e.g., Detection Catalog -- AiSOC).
Component: Various page metadata exports across apps/web
AiSOC version
v7.3.1 (commit 182afc5b, main branch)
Describe the bug
A source code review of the full monorepo and a browser inspection of the live demo at tryaisoc.com surfaced 13 bugs — all fixable with minimal code changes (1–5 lines each), no infrastructure or migration work required.
1. Unauthenticated EASM
/scanendpointSteps to reproduce: Send a POST to
/api/v1/easm/scanwith aScanRequestbody containing anytenant_id— no auth header needed.Expected behavior: The endpoint should require authentication like the neighboring
/assetsand/driftendpoints in the same file.Actual behavior: No
Depends(get_current_user)guard exists, so any unauthenticated caller can trigger EASM discovery scans against any tenant using the operator's Shodan/Censys API keys.Component:
services/api/app/api/v1/endpoints/easm.py2. EASM
/assetsand/driftcross-tenant data accessSteps to reproduce: Authenticate as user in tenant A, then call
GET /api/v1/easm/assets?tenant_id=<tenant_B_id>.Expected behavior: Request should be rejected or scoped to the caller's own tenant.
Actual behavior: The endpoint uses
effective_tenant = tenant_id or current_user.tenant_idwithout verifying ownership, so any authenticated user can read another tenant's external assets and drift events.Component:
services/api/app/api/v1/endpoints/easm.py3.
.env.examplecontains a working Fernet encryption keySteps to reproduce: Copy
.env.exampleto.envas instructed by the README, then inspectAISOC_CREDENTIAL_KEY.Expected behavior: The example file should contain a placeholder prompting the operator to generate their own key.
Actual behavior: A valid, base64-encoded Fernet key is shipped. Operators who don't rotate it encrypt all connector credentials under a publicly-known key — anyone with DB access can decrypt stored API tokens, OAuth secrets, and service account keys.
Component:
.env.example4. Database exception details exposed to API clients
Steps to reproduce: Trigger a database error on any of the affected endpoints (e.g., by sending malformed data to knowledge base, compliance, or case endpoints).
Expected behavior: A generic error message like
"Internal database error"should be returned.Actual behavior: Raw Postgres exception messages including table names, column names, constraint names, and SQL fragments are returned via
raise HTTPException(detail=f"Database error: {exc}"). Approximately 8 occurrences acrossknowledge_base.py,compliance.py, andcases.py.Component:
services/api5. ITSM webhook broken — wrong column name in SQL
Steps to reproduce: Send a webhook payload to the ITSM inbox endpoint.
Expected behavior: The webhook should be processed successfully.
Actual behavior: Raw SQL query references column
enabledbut the ORM model defines it asis_enabled, causing a runtimeProgrammingErroron every call. The entire ITSM webhook integration is non-functional.Component:
services/api/app/api/v1/endpoints/inbox_itsm.py6. Marketplace endpoint missing path traversal guard
Steps to reproduce: If the marketplace index JSON contains a relative path like
../../etc/passwd, the_resolve_item_pathfunction resolves it without checking bounds.Expected behavior: Resolved paths should be validated to stay within the repository root via
is_relative_to().Actual behavior: After
.resolve(), there is no bounds check, so a manipulated index entry could serve files outside the repo directory.Component:
services/api/app/api/v1/endpoints/marketplace.py7. Login page open redirect
Steps to reproduce: Navigate to
/login?next=https://evil.com, then log in.Expected behavior: The
nextparameter should only accept relative paths.Actual behavior: The value is passed directly to
router.replace(next)without validation, enabling phishing attacks where users are redirected to attacker-controlled sites after login.Component:
apps/web/src/app/login/page.tsx8. GraphQL search vulnerable to wildcard DoS
Steps to reproduce: Send a GraphQL query with
searchset to%_%_%_%_%_%_%_%_%_%_%_.Expected behavior: Wildcard characters in user input should be escaped before use in
ilikepatterns.Actual behavior:
Alert.title.ilike(f"%{search}%")passes unescaped user input, allowing crafted patterns that force expensive full-table scans on the database.Component:
services/api/app/graphql/query.py9. Prometheus metrics cardinality bomb
Steps to reproduce: Generate traffic to endpoints with dynamic path segments (e.g.,
/api/v1/connectors/{uuid}), then check Prometheus memory usage over time.Expected behavior: Metrics should use route templates (e.g.,
/api/v1/connectors/{connector_id}) for labels.Actual behavior:
endpoint = request.url.pathuses raw URLs including UUIDs, creating an unbounded number of time series that grow monotonically and eventually cause OOM.Component:
services/api/app/main.py10. Insecure default values for internal auth tokens
Steps to reproduce: Deploy without setting
REALTIME_INTERNAL_TOKEN,JWT_SECRET, oralert_webhook_secretenvironment variables.Expected behavior: The application should fail to start with a clear error if critical secrets are missing.
Actual behavior: These fall back to
"changeme"or"changeme-insecure-default", effectively bypassing inter-service authentication and JWT signing if operators don't explicitly configure them.Component:
services/agents/app/playbook/engine.py,services/api/app/auth/oidc.py,services/api/app/auth/saml.py,services/honeytokens/app/core/config.py11. Duplicate site name in browser tab titles
Steps to reproduce: Visit any console page (e.g.,
/dashboard,/alerts) and check the browser tab title.Expected behavior:
Dashboard | AiSOCActual behavior:
Dashboard | AiSOC | AiSOC— the Next.jsmetadata.title.templateis doubling the site name on every console page.Component: Root layout metadata in
apps/web12. Missing page titles on
/honeytokensand/purple-teamSteps to reproduce: Navigate to
/honeytokensor/purple-teamand check the browser tab title.Expected behavior: Page-specific titles like
Honeytokens | AiSOC.Actual behavior: Both fall back to the generic homepage title
AiSOC -- Open-Source AI Security Operations Centerbecause they're missing theirmetadata.titleexport.Component:
apps/web/src/app/(console)/honeytokens/page.tsx,apps/web/src/app/(console)/purple-team/page.tsx13. Inconsistent title separator characters across pages
Steps to reproduce: Compare browser tab titles across different console pages.
Expected behavior: All pages should use the same separator convention.
Actual behavior: Some pages use
|(e.g.,Dashboard | AiSOC) while others use--(e.g.,Detection Catalog -- AiSOC).Component: Various page metadata exports across
apps/webAiSOC version
v7.3.1 (commit
182afc5b,mainbranch)