# Oshun V1 — Audit Pass 2 (2026-05-28)

This is the second comprehensive gap audit after the original v1 audit landed 89
fixes (tracked as tasks #1–#89). This pass covered five dimensions in parallel —
security, reliability/observability, data/performance/testing,
build/deploy/compliance/ops, and code quality/docs/config — and produced the
issue list below.

Findings were captured as tasks #90–#118. Status: **closed (in batch 1a, 1b, and
follow-ups):** #90–#92, #94, #96–#97, #103–#104, #109, #118. **open at end of
pass:** #93, #95, #98–#102, #105–#108, #110–#117.

## Security

| ID  | Severity | Finding                                                                                                                                             | Path                                                                  |
| --- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| 90  | Medium   | UI demo apiKey constant was named `_live` and matched a real-credential pattern                                                                     | `apps/demeter/web/src/pages/settings/IntegrationSettingsPage.tsx:372` |
| 91  | Critical | svc-tts `/v1/admin/*` routes were authenticated at USER tier; USER tokens could trigger scale + read GPU metrics                                    | `apps/lilith/svc-tts/src/app.impl.ts:134, 4187+`                      |
| 92  | Critical | svc-moderation had **no authentication at all** — `/v1/admin/model/filters/batch` could be POSTed unauthenticated to swap moderation filters        | `apps/lilith/svc-moderation/src/app.ts:84`                            |
| 93  | Critical | Payment webhook handlers (Stripe, CCBill, etc.) processed without verifying provider signatures — comments said `// TODO: Verify webhook signature` | `apps/aphrodite/payment/src/routes/webhooks.ts:19, 53, 63, 73, 83`    |
| 94  | High     | svc-tts `/v1/admin/autoscaling/scale` read `req.body as any` — `target_instances` not validated                                                     | `apps/lilith/svc-tts/src/app.impl.ts:214`                             |
| 95  | High     | No rate limiting on svc-auth `/v1/auth/oauth/callback` or password-reset paths                                                                      | `apps/lilith/svc-auth/src/auth-endpoints.ts:103`                      |

## Reliability + Observability

| ID  | Severity | Finding                                                                                                                         | Path                                                             |
| --- | -------- | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| 96  | High     | OAuth service `fetch(token_endpoint)` + `fetch(userinfo_endpoint)` had no `AbortSignal.timeout` — could hang login indefinitely | `apps/lilith/svc-auth/src/oauth-service.ts:226, 257`             |
| 97  | High     | Google Play receipt validation fetch had no timeout                                                                             | `apps/lilith/svc-payment-orchestrator/src/iap/iap-routes.ts:114` |
| 98  | Medium   | veritas/video server didn't drain in-flight requests on SIGTERM; no request-ID middleware                                       | `apps/veritas/video/src/server.ts:363`                           |
| 99  | Medium   | 20+ `console.log` calls in veritas/ingestion, social, bellona/bridge-unity main entrypoints                                     | `apps/veritas/ingestion/src/main.ts:90+`                         |
| 100 | Low      | svc-auth hardcodes LilithLogger config without reading `LOG_LEVEL`                                                              | `apps/lilith/svc-auth/src/server.ts:25`                          |
| 101 | Medium   | veritas/video has no Prometheus `/metrics` endpoint (veritas/agents does)                                                       | `apps/veritas/video/src/server.ts`                               |
| 102 | High     | lilith/bff has shutdown handler but no `/health` route                                                                          | `apps/lilith/bff/src/server.ts:54`                               |
| 103 | Medium   | svc-auth in-memory `oauthStateTokens` Map could grow unbounded under attack                                                     | `apps/lilith/svc-auth/src/oauth-service.ts:75`                   |

## Data / Performance / Testing

| ID  | Severity | Finding                                                                                                                              |
| --- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| 104 | High     | aphrodite/admin user + moderation `count()` paged with `Number.MAX_SAFE_INTEGER`                                                     |
| 105 | High     | 7 Prisma schemas lack a `migrations/` folder (veritas, arete, nisaba, psyche, v3/tara-studio, v3/lilith-commons, v3/saraswati-stage) |
| 106 | High     | 5 audit-log tables (bellona, hathor, isis, sophia, yemaya) have no TTL/expires + no scheduled purge                                  |
| 107 | Critical | `scripts/db-backup.sh` only covers Postgres; Redis sessions + queues are not backed up                                               |
| 108 | Critical | No documented S3/MinIO backup strategy for user uploads + project files                                                              |

Also noted (not yet ticketed):

- Schema drift across 5 model names (`User`, `Notification`, `Device`,
  `AuditLog`, `AuthUser`) appearing in multiple `schema.prisma` files with
  divergent fields. Consolidation needs a contracts-team decision.
- Missing compound indexes on `(userId, status)`, `(primaryDomain, isDefault)`,
  `(primaryDomain, createdAt)`, `(status, createdAt)` on hot paths in iris,
  yemaya.
- Foreign-key fields (`Sophia.Document.ownerId`,
  `Bellona.Build.workerId/userId/orgId`) declared as `String` without
  `@relation`, so cascade behavior is wrong.
- No load tests for iris/api, lilith/svc-ai, lilith/svc-conversation, oshun/bff
  — only tara/api has a `test/load/api-load.k6.js` script.
- No chaos tests anywhere.
- 11 packages still ship a `jest.config.*` (Jest→Vitest migration partial).

## Build / Deploy / CI

| ID  | Severity | Finding                                                                                                                            |
| --- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| 115 | Medium   | Multiple services have a `Dockerfile` but no adjacent `.dockerignore` (yemaya/api, nyx/api, others)                                |
| 116 | Medium   | `deploy.yml`, `iris-ci.yml`, `deploy-ecs.yml`, `security-deps-and-secrets.yml` run on every push to main without a `paths:` filter |
| 117 | Low      | CI uses `pnpm install --frozen-lockfile` but doesn't add an explicit `git diff pnpm-lock.yaml` reproducibility check               |

Also noted:

- V5 service Dockerfiles use floating tags (`FROM node:24-slim`) instead of
  digest pins.
- No SBOM generation in all build workflows (only iris/psyche invoke
  `sbom-and-sign.yml`).
- No `.github/actions/` composite actions; the same pnpm + Node + cache pattern
  is duplicated across 130+ workflows.
- Required status checks not documented in `CONTRIBUTING.md` or PR template.

## Compliance / Operations

| ID  | Severity | Finding                                                                                      |
| --- | -------- | -------------------------------------------------------------------------------------------- |
| 110 | Medium   | 15+ runbooks under `docs/runbooks/` with no index                                            |
| 111 | High     | No central SLO doc — per-domain uptime % / p95 latency / error budgets undocumented          |
| 112 | High     | No central docs for log retention windows, alert routing (Slack/PagerDuty), on-call rotation |
| 113 | Critical | No PII inventory and no GDPR data-subject-request endpoint registry                          |
| 114 | Medium   | No `docs/DEPRECATIONS.md` listing patterns slated for removal                                |

## Code Quality

Not individually ticketed (mostly maintenance debt):

- 3,427 `any` types across production code (worst offender:
  svc-community-translation/app.ts with 193).
- 1,362 `@ts-ignore` + 61 `@ts-expect-error` checked in.
- 4,354 `.skip` / `xit` / `xdescribe` test-skip directives.
- 3 very large files (`oshun/bff/src/admin/state.ts` at 30,824 lines —
  generated; `svc-ai/src/providers/civitai/model-type-manager.ts` at 19,667
  lines — model enums; `lilith/sdk/src/generated/openapi.ts` at 22,434 lines —
  generated).
- `formatDate`, `slugify`, `hashString` reimplemented across 50+ files instead
  of consumed from `libs/shared`.

## Documentation

- 10 of 12 API services lack a `libs/openapi/docs/<domain>/openapi.yaml` (only
  yemaya + nyx have one).
- No app-level `CHANGELOG.md` files.
- No per-domain CONTRIBUTING guides for high-volume domains (iris, lilith,
  isis).

## Configuration

- tsconfig targets mix ES2020, ES2022, ESNext.
- 1,667 of 1,756 `package.json` files lack an `engines` field.
- 11 packages still have a `jest.config.*` alongside the broader Vitest
  migration.

---

## Methodology

Each finding was produced by one of four parallel agents working from the repo
root, with explicit "already-fixed list" context so the prior 89 closed tasks
weren't re-reported. Each agent was instructed to return high-signal (80%+
confidence) findings with absolute paths, line numbers, code excerpts, specific
risk, and suggested fix. The findings here are a deduplicated and prioritised
compilation of the four agent reports.
