Umbrella issue URL: #308
Summary / Overall Risk Level
Overall risk: P1 / medium-high. Dispatch has a solid Next.js/TypeScript/Prisma foundation, broad unit coverage, centralized route authorization for most mutating APIs, and pinned CI actions. The main risk is accumulated inconsistency from many small patches around auth mode behavior, UI/API coupling, sync orchestration, and release/docs drift. No code changes or PRs were made during this audit.
Top Findings
P1 - Basic Auth browser flows are inconsistent
README says browser mutating API calls automatically include Basic Auth credentials via authedFetch(), and some board paths do. Other active UI paths still use plain fetch() for protected mutating routes, which means DISPATCH_AUTH_MODE=basic operators can hit 401s or inconsistent behavior depending on page/component.
Evidence:
src/lib/client-auth.ts defines authedFetch() and sessionStorage-backed Basic Auth header injection.
src/components/sync-issues-button.tsx:6,35 and src/components/kanban-board.tsx:26,89,118,213 use authedFetch.
src/app/automation/page.tsx:198,212,239, src/app/automation/repos/[...repo]/page.tsx:297, and src/components/issue-card.tsx:96,119,148,194 still use plain fetch for protected POST/DELETE routes.
- Static scan command:
grep -RIn "fetch(" src/app src/components showed the split.
P1 - PR follow-up ingestion has bypass-prone authorization/config defaults
The pull-based PR follow-up sync endpoint is a mutating POST that does not use the shared authorizeRequest() helper. The webhook endpoint can intentionally skip signature verification when WEBHOOK_SECRET is unset, which may be acceptable behind a gateway but is risky as a default unless deployment docs and runtime checks make that explicit.
Evidence:
- Mutating routes without
authorizeRequest scan returned: src/app/api/auth/logout/route.ts, src/app/api/pr-followup/sync/route.ts, src/app/api/pr-followup/webhook/route.ts.
src/app/api/pr-followup/sync/route.ts:88 exports POST() and scans tracked repos using GITHUB_TOKEN without a route auth check.
src/app/api/pr-followup/webhook/route.ts:186-196 verifies signatures only if WEBHOOK_SECRET is configured.
src/app/api/pr-followup/webhook/route.ts:222 persists queue work through processPrFollowupEvents(...).
P1 - Manual sync surfaces can overlap expensive GitHub/database work
Scheduled sync has a DB-backed single-row lock, but normal issue sync and automation sync do not appear to share that lock. Browser refreshes, cron overlap, or repeated clicks can trigger concurrent GitHub scans and per-run/per-job writes.
Evidence:
src/app/api/sync/scheduled/route.ts:13-59 has acquireLock() / releaseLock().
src/app/api/sync/route.ts:8-65 performs issue sync without that lock.
src/app/api/automation/sync/route.ts:329-356 loops through tracked repos; syncRepo() does multiple GitHub calls and upserts per workflow/run/job/release/PR/package.
P2 - Dependency/supply-chain audit needs follow-up
Runtime dependency audit found moderate advisories. CI pins most actions by SHA, but Trivy still tracks aquasecurity/trivy-action@master, which reduces reproducibility of security scanning.
Evidence:
npm audit --omit=dev --json reported 5 moderate advisories: next via bundled postcss, prisma via @prisma/dev / @hono/node-server.
.github/workflows/ci.yaml and .github/workflows/image.yaml pin checkout/setup/build actions by SHA.
.github/workflows/image.yaml uses aquasecurity/trivy-action@master with continue-on-error: true.
P2 - Local validation environment is not reproducible from current checkout state
The audit could not run full tests locally because the checked-out node_modules was incomplete. CI likely installs cleanly with npm ci, but the local workspace state can mislead agents during audits/fixes.
Evidence:
npm run typecheck failed before project checking: TS2688: Cannot find type definition file for '@testing-library/jest-dom'.
npm run test -- --runInBand failed: sh: 1: vitest: not found.
node_modules/.bin lacked vitest; node_modules/@testing-library/jest-dom was missing.
- Repo contains 63 test files, so this is an environment/install issue rather than absence of tests.
P2 - Release/version/docs drift is visible
Version reporting and docs disagree, which undermines operational smoke checks and support diagnostics.
Evidence:
package.json:3 version is 0.4.4.
src/lib/version.ts resolves the package/build version for UI display.
src/app/api/health/route.ts:5 falls back to 0.1.1.
docs/smoke-checklist.md:30-34 still documents health response version 0.1.13.
P2 - Maintainer docs drift from implemented routes
AGENTS.md mentions endpoints that do not match the current file tree, which can misroute agents and decomposer jobs.
Evidence:
AGENTS.md:76-79 documents GET /api/issues/[id]/classify, POST /api/issues/[id]/classify, and POST /api/issue-lanes/classify-bulk.
- Current route tree has
src/app/api/issues/[issueId]/lane/route.ts and no src/app/api/issue-lanes/classify-bulk route.
Recommended Issue Breakdown
- P1 — Normalize browser API auth helpers: replace protected mutating UI
fetch calls with authedFetch or a shared client wrapper and add Basic Auth mode regression tests.
- P1 — Harden PR follow-up ingestion auth: require
authorizeRequest on pull sync and make webhook signature requirements explicit/fail-closed unless an intentional gateway mode is configured.
- P1 — Share sync locking/concurrency controls: extend the scheduled sync lock or a repo-scoped lock to manual issue sync and automation sync, including useful 409 responses.
- P2 — Resolve production dependency advisories: assess Next/PostCSS and Prisma advisory paths, upgrade or document accepted risk, and pin/upgrade Trivy action off
master.
- P2 — Restore reproducible local validation: document/repair local install expectations so
npm run typecheck and npm run test work from a clean checkout with npm ci.
- P2 — Unify health/version reporting: make
/api/health use the shared version helper or build-time version and update smoke docs.
- P2 — Reconcile maintainer docs with implemented lane routes: update AGENTS.md/docs to match current
/api/issues/[issueId]/lane and existing bulk/classification endpoints.
- P3 — Reduce automation sync write amplification: batch obvious independent writes or introduce bounded concurrency after locking is settled.
- P3 — Prune duplicated auth-mode wording/tests: keep one source of truth for auth mode semantics and use route-level tests for high-risk flows.
- P3 — Add operational runbook notes for
DISPATCH_AUTH_MODE=disabled: keep it explicitly local-only and add deployment checks if feasible.
Not Worth Doing Yet
- Do not rewrite the app architecture or replace Prisma/Next.js; current module boundaries are workable.
- Do not introduce a full RBAC system before the Basic/OIDC/Bearer contract is made consistent.
- Do not build a generalized job runner for sync yet; a small shared lock and clear responses should come first.
- Do not chase low-value cosmetic refactors in UI components until auth and sync behavior are stable.
- Do not create child issues manually from this umbrella; the audit decomposer cron should do that deterministically.
Audit Notes
Safe read-only checks performed: refreshed local main, inspected routes/libs/docs/workflows/schema, searched route auth usage, checked labels via GitHub API, ran npm audit --omit=dev --json, attempted npm run typecheck and npm run test. Local repo state after audit included a pre-existing untracked src/components/auth-status.tsx that was not touched.
Decomposed into
Umbrella issue URL: #308
Summary / Overall Risk Level
Overall risk: P1 / medium-high. Dispatch has a solid Next.js/TypeScript/Prisma foundation, broad unit coverage, centralized route authorization for most mutating APIs, and pinned CI actions. The main risk is accumulated inconsistency from many small patches around auth mode behavior, UI/API coupling, sync orchestration, and release/docs drift. No code changes or PRs were made during this audit.
Top Findings
P1 - Basic Auth browser flows are inconsistent
README says browser mutating API calls automatically include Basic Auth credentials via
authedFetch(), and some board paths do. Other active UI paths still use plainfetch()for protected mutating routes, which meansDISPATCH_AUTH_MODE=basicoperators can hit 401s or inconsistent behavior depending on page/component.Evidence:
src/lib/client-auth.tsdefinesauthedFetch()and sessionStorage-backed Basic Auth header injection.src/components/sync-issues-button.tsx:6,35andsrc/components/kanban-board.tsx:26,89,118,213useauthedFetch.src/app/automation/page.tsx:198,212,239,src/app/automation/repos/[...repo]/page.tsx:297, andsrc/components/issue-card.tsx:96,119,148,194still use plainfetchfor protected POST/DELETE routes.grep -RIn "fetch(" src/app src/componentsshowed the split.P1 - PR follow-up ingestion has bypass-prone authorization/config defaults
The pull-based PR follow-up sync endpoint is a mutating POST that does not use the shared
authorizeRequest()helper. The webhook endpoint can intentionally skip signature verification whenWEBHOOK_SECRETis unset, which may be acceptable behind a gateway but is risky as a default unless deployment docs and runtime checks make that explicit.Evidence:
authorizeRequestscan returned:src/app/api/auth/logout/route.ts,src/app/api/pr-followup/sync/route.ts,src/app/api/pr-followup/webhook/route.ts.src/app/api/pr-followup/sync/route.ts:88exportsPOST()and scans tracked repos usingGITHUB_TOKENwithout a route auth check.src/app/api/pr-followup/webhook/route.ts:186-196verifies signatures only ifWEBHOOK_SECRETis configured.src/app/api/pr-followup/webhook/route.ts:222persists queue work throughprocessPrFollowupEvents(...).P1 - Manual sync surfaces can overlap expensive GitHub/database work
Scheduled sync has a DB-backed single-row lock, but normal issue sync and automation sync do not appear to share that lock. Browser refreshes, cron overlap, or repeated clicks can trigger concurrent GitHub scans and per-run/per-job writes.
Evidence:
src/app/api/sync/scheduled/route.ts:13-59hasacquireLock()/releaseLock().src/app/api/sync/route.ts:8-65performs issue sync without that lock.src/app/api/automation/sync/route.ts:329-356loops through tracked repos;syncRepo()does multiple GitHub calls and upserts per workflow/run/job/release/PR/package.P2 - Dependency/supply-chain audit needs follow-up
Runtime dependency audit found moderate advisories. CI pins most actions by SHA, but Trivy still tracks
aquasecurity/trivy-action@master, which reduces reproducibility of security scanning.Evidence:
npm audit --omit=dev --jsonreported 5 moderate advisories:nextvia bundledpostcss,prismavia@prisma/dev/@hono/node-server..github/workflows/ci.yamland.github/workflows/image.yamlpin checkout/setup/build actions by SHA..github/workflows/image.yamlusesaquasecurity/trivy-action@masterwithcontinue-on-error: true.P2 - Local validation environment is not reproducible from current checkout state
The audit could not run full tests locally because the checked-out
node_moduleswas incomplete. CI likely installs cleanly withnpm ci, but the local workspace state can mislead agents during audits/fixes.Evidence:
npm run typecheckfailed before project checking:TS2688: Cannot find type definition file for '@testing-library/jest-dom'.npm run test -- --runInBandfailed:sh: 1: vitest: not found.node_modules/.binlackedvitest;node_modules/@testing-library/jest-domwas missing.P2 - Release/version/docs drift is visible
Version reporting and docs disagree, which undermines operational smoke checks and support diagnostics.
Evidence:
package.json:3version is0.4.4.src/lib/version.tsresolves the package/build version for UI display.src/app/api/health/route.ts:5falls back to0.1.1.docs/smoke-checklist.md:30-34still documents health response version0.1.13.P2 - Maintainer docs drift from implemented routes
AGENTS.md mentions endpoints that do not match the current file tree, which can misroute agents and decomposer jobs.
Evidence:
AGENTS.md:76-79documentsGET /api/issues/[id]/classify,POST /api/issues/[id]/classify, andPOST /api/issue-lanes/classify-bulk.src/app/api/issues/[issueId]/lane/route.tsand nosrc/app/api/issue-lanes/classify-bulkroute.Recommended Issue Breakdown
fetchcalls withauthedFetchor a shared client wrapper and add Basic Auth mode regression tests.authorizeRequeston pull sync and make webhook signature requirements explicit/fail-closed unless an intentional gateway mode is configured.master.npm run typecheckandnpm run testwork from a clean checkout withnpm ci./api/healthuse the shared version helper or build-time version and update smoke docs./api/issues/[issueId]/laneand existing bulk/classification endpoints.DISPATCH_AUTH_MODE=disabled: keep it explicitly local-only and add deployment checks if feasible.Not Worth Doing Yet
Audit Notes
Safe read-only checks performed: refreshed local
main, inspected routes/libs/docs/workflows/schema, searched route auth usage, checked labels via GitHub API, rannpm audit --omit=dev --json, attemptednpm run typecheckandnpm run test. Local repo state after audit included a pre-existing untrackedsrc/components/auth-status.tsxthat was not touched.Decomposed into