Date: 2026-04-25
Phase 1-C operationalizes the async admin video path delivered in Phase 1-B and adds low-risk engineering guardrails. The legacy synchronous admin video route still exists for emergency/debug compatibility, but it is no longer default-accessible: server-side access now requires the explicit ALLOW_SYNC_VIDEO_DEBUG=true config flag, and the normal admin UI continues to use async video jobs.
Risk reduced:
- Long synchronous video generation is no longer reachable by default through
/api/admin/ai/test-video. - Admin/support operators can inspect sanitized async-video poison messages and failed jobs through admin-only APIs.
- Release/preflight and CI now include low-risk checks for toolchain pinning, obvious committed secrets, new unreviewed DOM sinks, targeted JS syntax, and unsafe Worker body parser calls.
Still not solved:
- This is not a full TypeScript/checkJs migration.
- The DOM sink gate is count-baseline based; it prevents new unreviewed sinks but does not prove every legacy sink is XSS-safe.
- Cloudflare WAF/static-header/RUM controls are still not fully repo-enforced.
- There is no full admin UI for poison-message triage; Phase 1-C adds API-level operational tooling.
- Org/tenant/billing/compliance are still out of scope.
Implemented:
- Default-disabled sync video debug route.
- Admin-only poison-message and failed-video-job inspection APIs.
- Release compatibility route contract updates for debug-only and ops routes.
- Toolchain pinning through
.nvmrcandpackage.jsonengines. - Quality gates and tests for secret scanning, DOM sink baseline, targeted JS syntax, toolchain consistency, and Worker body parser safety.
- CI/preflight integration for stable checks.
Not implemented:
- Removing the sync route entirely.
- Broad frontend/admin UI refactor.
- Full lint/typecheck migration.
- Cloudflare IaC or production deploy.
Phase 1-B made async video production-usable, but:
/api/admin/ai/test-videoremained an admin-accessible synchronous compatibility route.ai_video_job_poison_messagesexisted, but operators had no admin API to inspect it.- Quality gates were limited, with no repo-local secret scan, DOM sink guard, JS syntax gate, or toolchain pin.
- Release preflight did not run the new quality gates.
Decision: keep the route as an emergency/debug fallback, but default-deny it.
Behavior now:
/api/admin/ai/test-videoreturns404 not_foundunlessALLOW_SYNC_VIDEO_DEBUG=true.- When enabled, the route still requires admin auth and the central production MFA boundary.
- Same-origin checks remain enforced centrally for the POST mutation.
rateLimitAdminAi()remains fail-closed before body parsing and provider work.- The route logs a safe warning event when blocked or used; it does not log prompts, frame inputs, request bodies, secrets, signatures, or provider credentials.
- The admin UI only calls the route when
window.__BITBI_ADMIN_AI_SYNC_VIDEO_DEBUG === true; default UI flow uses/api/admin/ai/video-jobs.
Relevant files:
workers/auth/src/routes/admin-ai.jsjs/pages/admin/ai-lab.jstests/workers.spec.jstests/auth-admin.spec.jsconfig/release-compat.json
Phase 1-C adds API-only operational tooling:
GET /api/admin/ai/video-jobs/poisonGET /api/admin/ai/video-jobs/poison/:idGET /api/admin/ai/video-jobs/failedGET /api/admin/ai/video-jobs/failed/:id
Security behavior:
- Admin-only through existing
requireAdmin()and MFA policy. - Fail-closed rate limiting through
rateLimitAdminAi(). - Safe pagination with bounded
limitand cursor support. - Poison responses include redacted
bodySummary, reason code, queue name, schema version, job id when parseable, correlation id, and timestamp. - Failed-job diagnostics include provider/model/status, owner id/email, attempts, safe error code/message, and booleans for provider task/output/poster presence.
- Responses do not expose prompts, raw
input_json, provider raw payloads, provider state blobs, R2 keys, signatures, secrets, request bodies, or stack traces.
UI status:
- No new admin UI was added in Phase 1-C. The API gives staff/support tooling a stable backend surface first; a small UI can be added later without changing the data contract.
| Gate | Script | Behavior |
|---|---|---|
| Toolchain pinning | npm run check:toolchain |
Verifies .nvmrc, package.json engines, and GitHub Actions Node 20 configuration. |
| Scanner tests | npm run test:quality-gates |
Tests secret scanner, DOM sink baseline scanner, and toolchain validator behavior. |
| Secret leakage | npm run check:secrets |
Scans repo text files for obvious private keys, provider keys, bearer tokens, and generic high-entropy secret assignments with safe placeholder allowances. |
| DOM sink baseline | npm run check:dom-sinks |
Blocks new unreviewed innerHTML, outerHTML, insertAdjacentHTML, and document.write usage above config/dom-sink-baseline.json. |
| Targeted JS syntax | npm run check:js |
Runs node --check over scripts plus high-risk/new Worker/admin/shared modules. |
| Worker body parsers | npm run check:worker-body-parsers |
Existing Phase 1-A gate remains in preflight and blocks direct Worker request.json(), formData(), text(), or arrayBuffer() calls. |
Limitations:
check:jsis syntax-only, not TypeScript or full semantic checkJs.- The DOM baseline is a pragmatic guardrail, not proof that legacy sinks are safe.
- The secret scanner is conservative and pattern-based; it complements, but does not replace, provider-side secret scanning.
Updated:
package.json.github/workflows/static.ymlscripts/lib/release-plan.mjsscripts/lib/release-compat.mjsscripts/test-release-plan.mjsscripts/test-release-compat.mjs
npm run release:preflight now includes the stable quality gates through the release planner:
npm run check:toolchainnpm run test:quality-gatesnpm run check:secretsnpm run check:dom-sinksnpm run check:js- existing release, Cloudflare prereq, body parser, Worker, asset, and static checks as applicable
The static GitHub Pages workflow now runs these quality gates in the release-compatibility job before deploy jobs.
| Area | Files |
|---|---|
| Sync route restriction | workers/auth/src/routes/admin-ai.js, tests/workers.spec.js, workers/auth/CLAUDE.md, config/release-compat.json |
| Poison/failed-job inspection | workers/auth/src/lib/ai-video-jobs.js, workers/auth/src/routes/admin-ai.js, js/shared/auth-api.js, tests/helpers/auth-worker-harness.js, tests/workers.spec.js |
| Quality gates | .nvmrc, package.json, package-lock.json, config/dom-sink-baseline.json, scripts/check-toolchain.mjs, scripts/check-secrets.mjs, scripts/check-dom-sinks.mjs, scripts/check-js.mjs, scripts/lib/quality-gates.mjs, scripts/test-quality-gates.mjs |
| CI/release tooling | .github/workflows/static.yml, scripts/lib/release-plan.mjs, scripts/lib/release-compat.mjs, scripts/test-release-plan.mjs, scripts/test-release-compat.mjs |
| Static preflight stabilization | tests/smoke.spec.js |
| Docs | PHASE1C_REMEDIATION_REPORT.md, AUDIT_ACTION_PLAN.md, AUDIT_NEXT_LEVEL.md, PHASE1B_REMEDIATION_REPORT.md, PHASE1_OBSERVABILITY_BASELINE.md |
Worker tests:
- Default sync video route rejects when
ALLOW_SYNC_VIDEO_DEBUGis absent. - Explicit debug flag allows admin-only sync video use.
- Non-admin sync route access is rejected.
- Foreign-origin sync route mutation is rejected before provider work.
- Missing limiter backend fails closed for the sync debug route.
- Admin can list/view sanitized video poison messages.
- Non-admin cannot list poison messages.
- Admin can list/view sanitized failed video job diagnostics.
- Operational responses do not include raw prompt, request payload, provider state, or R2 key data.
- Missing limiter backend fails closed for video ops inspection.
Quality gate tests:
- Secret scanner detects a provider-key-shaped fixture.
- Secret scanner ignores safe secret-name placeholders.
- DOM scanner allows reviewed baseline sinks and rejects new unreviewed sinks.
- Toolchain validator accepts the expected Node/npm/workflow pinning files.
Static/admin UI:
- Existing Phase 1-B admin UI tests continue to cover default async video flow and no default
/api/admin/ai/test-videocall. - The mobile footer contact drawer smoke test now waits for the drawer's visible open transition before clicking to close. This does not weaken the assertion; it keeps the same collapsed-state checks while removing a preflight-only actionability race observed under the full release preflight load.
| Command | Result | Notes |
|---|---|---|
npm run check:toolchain |
PASS | Toolchain files are pinned to Node 20/npm 10+ expectations. |
npm run test:quality-gates |
PASS | Scanner/validator unit coverage passed. |
npm run check:secrets |
PASS | No obvious committed secret patterns found. |
npm run check:dom-sinks |
PASS | No new unreviewed DOM sinks above baseline. |
npm run check:js |
PASS | Targeted syntax checks passed. |
npm run test:workers -- --grep "legacy sync video debug route|admin video operations" |
PASS, 4/4 | Targeted Worker coverage for Phase 1-C behavior passed. |
npm run test:release-compat |
PASS | Release compatibility tests passed after route/check updates. |
npm run test:release-plan |
PASS | Release planner tests passed after adding new preflight checks. |
npm run test:cloudflare-prereqs |
PASS | Cloudflare prerequisite validator tests passed. |
npm run validate:cloudflare-prereqs |
PASS repo config, production BLOCKED | Repo config is valid; live Cloudflare validation was skipped, so production deploy is still blocked. |
npm run check:worker-body-parsers |
PASS | Worker body parser guard passed. |
npm run test:asset-version |
PASS | Asset version tests passed. |
npm run validate:asset-version |
PASS | Asset version validation passed. |
npm run validate:release |
PASS | Release compatibility validation passed. |
npm run build:static |
PASS | Static build completed. |
npm run test:workers |
PASS, 289/289 | Full Worker route/security suite passed. |
npm run test:static |
PASS, 155/155 | Full static/admin UI suite passed. |
npx playwright test tests/smoke.spec.js --grep "footer contact drawer stays collapsed by default on mobile" --repeat-each=5 |
PASS, 5/5 | Reproduced and stabilized the preflight-only mobile drawer actionability race. |
npm run release:preflight |
PASS | Full release preflight passed after the mobile drawer deterministic wait was added. |
npm ci |
PASS with EBADENGINE warning | Dependencies install reproducibly; warning is expected because this local shell runs Node v24.14.0 while Phase 1-C pins project/CI expectations to Node 20. |
npm ls --depth=0 |
PASS | Root package tree resolves to @playwright/test, serve, and viem. |
npm audit --audit-level=low |
PASS | Root audit found 0 vulnerabilities. |
git diff --check |
PASS | No whitespace errors in the final diff. |
Current status: conditional pass.
Merge is safe only if all Phase 1-C changed and new files are committed together. Do not merge only the API changes without the release compatibility, tests, quality-gate scripts, smoke-test stabilization, and documentation updates.
Current status: not production-deploy-ready from this code pass alone.
Production remains blocked until live Cloudflare prerequisites and staging checks pass:
- Matching
AI_SERVICE_AUTH_SECRETexists in both auth and AI workers. SERVICE_AUTH_REPLAYDurable Object binding/migration exists.- Auth D1 migrations through
0030_harden_ai_video_jobs_phase1b.sqlare applied. AI_VIDEO_JOBS_QUEUEexists and is bound.USER_IMAGESR2 binding exists and accepts video/poster objects.VIDU_API_KEYis provisioned in the AI worker if Vidu async jobs are enabled.- Staging verifies async video create, provider pending, provider success, R2 output/poster read, poison-message inspection, and failed-job inspection.
ALLOW_SYNC_VIDEO_DEBUG should remain absent/false in production except during controlled emergency debugging. Enabling it reopens the long synchronous path for admins and should require an explicit incident/change record.
- If the ops endpoints misbehave, remove their use from support tooling; they are read-only and do not affect queue processing.
- If a quality gate false-positive blocks CI, fix the scanner or baseline in a dedicated review rather than bypassing the workflow.
- If async video has a staging incident, keep
ALLOW_SYNC_VIDEO_DEBUGdisabled by default and use it only as a controlled temporary debug flag. - No new D1 migration is added in Phase 1-C.
| Risk | Impact | Blocks merge | Blocks production deploy | Next action |
|---|---|---|---|---|
| Sync route still exists behind debug flag | Admins can re-enable long synchronous provider work if the flag is set. | No | No, if disabled in production | Keep disabled by default; retire route after confidence window. |
| Poison tooling is API-only | Operators need API/client tooling to inspect rows. | No | No | Add small admin/support UI in Phase 1-D if useful. |
| DOM gate is baseline-count based | Existing sinks remain and same-count rewrites may not be caught. | No | No | Replace risky legacy sinks incrementally and adopt stronger lint rules. |
| No full checkJs/typecheck | Semantic JS drift still possible. | No | No | Introduce TypeScript checkJs or ESLint/Biome incrementally. |
| Dashboard controls not IaC-managed | WAF/header/RUM drift remains possible. | No | Yes until live verification | Add Cloudflare API drift checks or IaC. |
- Commit all Phase 1-C files together.
- Verify in staging that
/api/admin/ai/test-videois disabled withoutALLOW_SYNC_VIDEO_DEBUG. - Keep
ALLOW_SYNC_VIDEO_DEBUGabsent/false in production unless there is an explicit incident/debug change record. - Add a small admin/support UI for poison and failed-job APIs if operators need browser-based access.
- Plan Phase 1-D for retiring the sync route, expanding checkJs/lint coverage, and moving dashboard controls toward IaC/drift checks.