Disciplines · Audits

Oshun v1 — Audit pass 3 gap findings

Each agent was briefed with the exact #90–#118 task list as "out of scope" and instructed to find file:line evidence for severity-ranked findings only.

5sections5 minread

On this page

Date: 2026-05-28 Owner: platform team Previous pass: docs/audits/2026-05-28-v2-gap-audit.md

Audit pass 2 closed tasks #90–#118. This third sweep used seven parallel read-only explore agents to find what those didn't catch, across seven non-overlapping surfaces. 93 net-new actionable findings. This doc is the authoritative source while tasks #120+ are filed.

How the sweep was scoped#

Each agent was briefed with the exact #90–#118 task list as "out of scope" and instructed to find file:line evidence for severity-ranked findings only. Agents were told to skip stylistic issues and cap at 20–25 findings each.

Agent Findings Top themes
security 10 SSRF in gpu-dispatcher callback; OAuth open redirect; CORS regression
reliability 15 23+ services missing SIGTERM; unbounded Maps; missing /health, /metrics
data integrity 20 FK relations without onDelete; soft-delete fields with no sweeper
build / ci / ops 8 67 workflows w/o permissions: block; 65 w/o concurrency cancel-in-prog
test / contract 10 expect(true).toBe(true) stubs; skipped svc-auth integration suite
privacy / pii 15 JWT body contains email+IP; 6 audit trails store full IPs un-minimized
performance 15 iris GraphQL N+1 cluster (resolvers + dataloaders); regex DoS in DLP rules

High-severity findings — fix candidates this pass#

Security#

# Title Location
S1 SSRF via user-controlled callbackUrl in GPU dispatcher apps/infra/gpu-dispatcher/src/callbacks.ts:596
S2 OAuth redirect_uri reflected without allowlist apps/lilith/svc-auth/src/auth-missing-endpoints.ts:176
S3 Wildcard CORS still in 8+ services missed by #71 apps/aja/svc-motion-ai/src/app.ts:114 + veritas/*, aphrodite/devices
S4 RevenueCat webhook signature optional on env var apps/lilith/svc-payment-orchestrator/src/iap/iap-routes.ts:282
S5 jwt.decode instead of jwt.verify in offline encryption apps/lilith/svc-offline/src/encryption.ts

Reliability#

# Title Location
R1 23+ Fastify services missing SIGTERM handler apps/lilith/svc-yoga-practice/src/server.ts:16, svc-voice-pipeline/src/server.ts:18, svc-webrtc/src/server.ts:8, svc-stt/src/server.ts:8, + 19 others
R2 10 unbounded Maps in voice-pipeline (sessions, PTT, VAD, MOS) apps/lilith/svc-voice-pipeline/src/app.ts:68-78
R3 4 unbounded Maps in community-translation apps/lilith/svc-community-translation/src/app.ts:2013-2016
R4 fetch(url) without timeout in model registry downloads apps/lilith/svc-ai/src/distilled-model-registry.ts:577
R5 Unbounded notifications array in VoiceCloningRequestManager apps/lilith/svc-tts/src/voice-cloning-request-manager.ts:72

Privacy / PII#

# Title Location
P1 ConsoleEmailSender logs tokens + emails in dev mode apps/lilith/svc-auth/src/email-sender.ts:232
P2 JWT body contains cleartext email + IP apps/lilith/svc-auth/src/jwt-types.ts:45
P3 Full IPs stored unminimized in 2 audit trails apps/lilith/svc-data-governance/src/app.ts:2293, apps/maat/api-gateway/src/middleware/request-response-logging.ts:433
P4 pg_dump backups gzipped but not encrypted scripts/db-backup.sh:177
P5 user_hash analytics derives identity without salt apps/lilith/svc-observability/src/analytics-routes.ts:52

Performance#

# Title Location
Pe1 iris/api dataloaders loop instead of batch (4 of them) apps/iris/api/src/graphql/data-loaders.ts:130,154,181,208
Pe2 iris GraphQL resolvers run N+1 over agents / KBs / conversations apps/iris/api/src/graphql/resolvers.ts:312,496,1202
Pe3 Hardcoded pageSize=1000 in three service-data-source list calls apps/iris/api/src/graphql/service-data-sources.ts:918,1059,1115
Pe4 RegExp compiled from user-supplied DLP rule pattern (regex DoS) apps/iris/developer-portal/src/services/data-loss-prevention-service.ts:487

Build / CI / ops#

# Title Location
B1 67 workflows missing permissions: block (write-all default) .github/workflows/*.yml (mass)
B2 65 PR workflows missing concurrency: cancel-in-progress .github/workflows/*.yml (mass)
B3 Pre-commit hook has typecheck step commented out .husky/pre-commit:19
B4 20+ Dockerfiles use unpinned base image tags docker/psyche/Dockerfile.tools:19 + others

Test / contract#

# Title Location
T1 describe.skip permanently disables svc-auth integration suite apps/lilith/svc-auth/src/__tests__/integration/auth-mock-typed-demo.test.ts:34
T2 expect(true).toBe(true) stub in DSR processing test apps/lilith/svc-auth/src/__tests__/audit-logging.test.ts:418
T3 expect(true).toBe(true) stub in plagiarism detection test apps/lilith/svc-moderation/__tests__/academic-integrity.test.ts

Data integrity — top 6 of 20#

# Title Location
D1 bellona ExportJob.build relation lacks onDelete: policy libs/bellona/database/prisma/schema.prisma:342
D2 veritas.PaymentTransaction.amount stored as String not Decimal libs/veritas/database/prisma/schema.prisma:1851
D3 sophia Document.deletedAt + Citation.deletedAt without sweeper libs/sophia/database/prisma/schema.prisma:336,489
D4 sophia Entity.canonicalId + mergedInto allow cycles libs/sophia/database/prisma/schema.prisma:551-553
D5 nisaba VariantUnit.manuscriptId FK without explicit onDelete libs/nisaba/database/prisma/schema.prisma:388
D6 yemaya Tag.organizationId allows multiple NULL global collisions libs/yemaya/database/prisma/schema.prisma:553

What's being addressed THIS pass (in-session fixes)#

Tracked as tasks #120–#... — see TODOS.md and TaskList. Highest-leverage small-effort items going in immediately:

  • B3 — uncomment typecheck step in .husky/pre-commit
  • B1/B2 — sweep .github/workflows/*.yml, add permissions: contents: read
    • concurrency: { group: ${{ github.workflow }}-${{ github.ref }}, cancel-in-progress: true } to PR-triggered workflows
  • P1 — strip token from ConsoleEmailSender dev log; keep redacted "magic link sent to " form
  • R4 — wrap distilled-model-registry downloadFromURL fetch in AbortSignal.timeout(60_000)
  • R5 — cap voice-cloning-request-manager notifications array at 10_000 with oldest-first eviction
  • Pe4 — wrap DLP rule regex in try { new RegExp(...) } catch + reject patterns over 256 chars or with (.+)+-style nested quantifiers
  • T1 — un-skip or it.skip.each the auth integration suite with an explicit env-var gate instead of permanent describe.skip
  • T2 / T3 — replace truthy stub assertions with real domain assertions or mark .todo with tracking issue

What's being filed (separate work)#

The following land as #120+ but are bigger pieces:

  • S1 SSRF in gpu-dispatcher (needs URL allowlist + tests)
  • S2 OAuth redirect_uri allowlist (registration table)
  • S3 CORS sweep across remaining Hono services
  • S4 RevenueCat make signature required (not env-gated)
  • S5 jwt.verify migration
  • R1 graceful shutdown across 23+ services (mass refactor)
  • R2 / R3 voice-pipeline + community-translation Map eviction (per-service)
  • R6 process.on('unhandledRejection') across services
  • P2 JWT body PII removal (compat migration)
  • P3 IP minimization helper + sweep audit trails
  • P4 pg_dump encryption (gpg + key management)
  • P5 user_hash salt
  • Pe1–Pe3 iris GraphQL N+1 cluster (substantial DataLoader refactor)
  • All 20 data-integrity findings (schema + migration per fix)
  • B4 Docker base image SHA pinning

Methodology notes#

Agents were instructed to use the same stub-indicator framing the v2 audit used: "Search for evidence of X" rather than "verify X is real." The adversarial framing surfaced regressions in #71 (CORS) and #98 (graceful shutdown only landed in veritas/video, not other services).

False positives were minimal: the test/contract agent's findings about describe.skip for STRIPE_SECRET_KEY-gated tests were correctly classified as out-of-scope (intentional integration test pattern).