Skip to content

test(coverage): add SSRF + auth-token route tests; wire coverage gate to CI#86

Merged
LanNguyenSi merged 1 commit into
masterfrom
test/depsight-coverage-crit-high
Jun 28, 2026
Merged

test(coverage): add SSRF + auth-token route tests; wire coverage gate to CI#86
LanNguyenSi merged 1 commit into
masterfrom
test/depsight-coverage-crit-high

Conversation

@LanNguyenSi

Copy link
Copy Markdown
Owner

What

Closes the CRITICAL and HIGH test-coverage gaps in depsight flagged by the 2026-06-27 audit, and turns CI coverage from measured-but-unenforced into a real gate.

Why

depsight had a solid lib/ unit suite, but its sole SSRF guard (lib/net/safe-fetch.ts) was only ever mocked, and 26 of 27 API route handlers were untested. CI ran tests without coverage, so the declared coverage config never gated anything.

Tests added (343 total, all green, mutation-verified)

  • lib/net/safe-fetch.ts (CRITICAL, SSRF): literal private/loopback/cloud-metadata IPv4 and IPv6, IPv4-mapped IPv6, DNS resolve and failure paths, the no-redirect transport, and the real pinnedLookup anti-DNS-rebinding fail-closed branch. 97.7% statements.
  • app/api/tokens/route.ts + [id]/route.ts (HIGH): session-only auth (a leaked dsat_ Bearer token cannot mint or revoke), name validation, dsat_ prefix on mint, IDOR userId scoping, revoke idempotency. 100%.
  • app/api/webhooks/route.ts + policies/route.ts (HIGH, establishing the route-handler test pattern): SSRF rejection wiring, input and enum validation, auth and status codes.

Coverage enforcement

  • vitest.config.ts: include app/api/**, ratchet global floor (about 2 points below baseline) plus per-file floors (85 to 95%) for the five newly covered security files; the gate is proven to fail the build when a threshold is breached.
  • ci.yml: test job runs npm run test:coverage.
  • .gitignore: ignore the generated coverage/ dir.

Verification

  • npm run lint: clean. npx tsc --noEmit: clean. npm run test:coverage: 343 passing, thresholds enforced.
  • Independent adversarial mutation review: every CRITICAL/HIGH guard was killed by an inverted-guard run. One originally-inert pinnedLookup test was rewritten to exercise the real function and re-verified (removing the source fail-closed branch now turns it red).

Deferred to follow-ups

  • The remaining 21 app/api route handlers (the pattern is now established) plus an app/api/** directory coverage floor.
  • MEDIUM lib modules (ci/analytics, ci/sync and ingest, cron/auto-scan, pr-scanner, policy/service, github.ts, export/repo-bundle), mcp/src/tools/*, and the mint-api-token CLI.
  • Production hardening: strip brackets from literal IPv6 hosts before net.isIP so http://[::1] takes the synchronous block path (it is currently blocked via the DNS-failure path).

Refs: tc-audit-2026-06-27

… to CI

Cover the highest-severity untested paths flagged by the 2026-06-27 audit
(CRITICAL SSRF guard plus HIGH token/webhook/policy routes) and enforce
coverage in CI.

Tests:
- tests/unit/safe-fetch.test.ts: lib/net/safe-fetch.ts assertPublicUrl
  (literal IPv4/IPv6, IPv4-mapped, cloud-metadata, DNS resolve/failure),
  safeFetch transport, and the real pinnedLookup DNS-pinning plus fail-closed
  re-validation (anti DNS-rebinding). Drives safe-fetch.ts to 97%+ statements.
- tests/unit/tokens-route.test.ts: GET/POST /api/tokens session-only auth
  (dsat_ Bearer rejected), name validation, dsat_ prefix on mint.
- tests/unit/tokens-id-route.test.ts: DELETE /api/tokens/[id] IDOR userId
  scoping, revokedAt idempotency.
- tests/unit/webhooks-route.test.ts: SSRF rejection wiring, event validation,
  401/400/201.
- tests/unit/policies-route.test.ts: PolicyType/Severity enum validation,
  service delegation, auth guard.

Coverage enforcement:
- vitest.config.ts: include app/api/**/*.ts; ratchet global floor
  (37/32/40/37, about 2 points below baseline) plus per-file floors for
  safe-fetch.ts and the tokens, webhooks, and policies routes.
- .github/workflows/ci.yml: test job runs npm run test:coverage so thresholds
  gate every PR.
- .gitignore: ignore the generated coverage/ dir.

lib/net/safe-fetch.ts: export pinnedLookup (testability only, no behavior
change) so its defence-in-depth fail-closed branch is unit-testable.

Refs: tc-audit-2026-06-27
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@LanNguyenSi LanNguyenSi merged commit 1226e48 into master Jun 28, 2026
4 checks passed
@LanNguyenSi LanNguyenSi deleted the test/depsight-coverage-crit-high branch June 28, 2026 06:10
LanNguyenSi added a commit that referenced this pull request Jun 29, 2026
… repo-bundle/policy-service lib (#87)

Closes residual HIGH route-handler + MED lib gaps after #86: 36 tests across
6 targets, each asserting success + every failure/edge branch (12 security/
validation guard mutations killed). Per-file coverage thresholds added.

Refs: tc-audit-2026-06-27 (depsight test-coverage umbrella)

Co-authored-by: Lan Nguyen Si <nguyen-si@publicplan.de>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
LanNguyenSi added a commit that referenced this pull request Jun 30, 2026
…p gates (#88)

* test(coverage): cover remaining 18 API route handlers + per-file gates

Adds unit tests for every previously-untested app/api route handler,
closing the depsight route-handler HIGH/MED surface after #86/#87:
dependabot trio, ci/sync, repos/sync, pr-scan, export, sbom, me, health,
deps, license, ci/analytics/[repoId], ci/analytics/cross-repo, history,
overview, policies/evaluate, repos. 117 new tests (411 -> 528 suite).

Each route covers all auth/ownership/IDOR/validation/error branches with
value-level assertions (exact prisma where clauses incl userId, exact
status + body). Per-file coverage thresholds added (S/F/L 95, branch
floors ~5-8 below measured), negative-control verified.

Refs: tc-audit-2026-06-27

* test(coverage): fold reviewer findings: assert ownership/IDOR boundaries

Adversarial mutation-verify (34 mutations, 30 killed, 4 survived) found
inert ownership assertions; this folds the fixes (each re-mutation-killed
independently):
- dependabot/check: assert prisma.repo.findMany is scoped to
  { userId, tracked:true }; removing userId was a cross-tenant IDOR
  that 100% line coverage masked (HIGH).
- export: assert the primary loadRepoExportData(userId, repoId) call on
  the happy path (MED).
- ci/analytics/[repoId]: mock owned repo.id distinct from the path param
  and assert analytics keyed on the ownership-verified id, not the raw
  param (LOW).

Refs: tc-audit-2026-06-27

---------

Co-authored-by: Lan Nguyen Si <nguyen-si@publicplan.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants