CI (SIAX Cloud) / quality (push) Successful in 51s
Security Scan (SIAX Cloud) / security (push) Successful in 26s
CI (SIAX Cloud) / security (push) Successful in 28s
CI (SIAX Cloud) / integration (push) Successful in 1m1s
CI (SIAX Cloud) / integration (pull_request) Successful in 30s
CI (SIAX Cloud) / security (pull_request) Successful in 24s
CI (SIAX Cloud) / quality (pull_request) Successful in 48s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 22s
Tenant isolation (security-critical): every app/api/0pt/** route reading
tenant_id from client-controlled query/body never cross-checked it against
the token middleware.ts already verified (x-0pt-verified-tenant). Adds a
shared assertTenantMatchesVerified guard and wires it into all 20+ affected
routes. Also closes two full-leak-by-omission bugs found in this pass
(documents/capture and lakehouse/mappings GETs returned every tenant's rows
when tenant_id was simply omitted) and two "no tenant check at all" reads
(rate-policies/[id], lakehouse/mappings/[id]) reachable by ID alone.
Duplicate-delivery idempotency (correctness-critical): POST /api/orders now
honors Idempotency-Key (sql/006, replay-or-409-on-conflict, Postgres +
in-memory); the Python orchestrator's POST /orders/{id}/run — the actual
delivery trigger — is now idempotent under an asyncio lock, closing a real
concurrent-request race, not just the sequential-retry case.
Engine-duplication resolution: adaptive_router.py's docstring wrongly
invited a bypass ("see b0ts for scraping") contradicting the product's own
compliance stance; corrected, and given real (additive, not escalating)
block-signal tracking so a rate-limited/blocked source is excluded from the
plan instead of hammered. Also fixes a previously-undiscovered production
bug: the Python engine's only call into the TS quality bridge
(X-Internal-Token) was unconditionally 401'd by middleware.ts, which never
checked that credential — the advisory quality bridge was unreachable in any
environment enforcing auth.
Restore drill: executed for real against throwaway Docker Postgres
containers (not simulated). Found and fixed two real blockers along the way:
sql/004 referenced opt_datasets, which no migration ever created (chain was
never actually runnable end to end); lib/db.ts hardcoded ssl:"require",
making any non-TLS Postgres (incl. compose.dev.yaml's own local-dev
container) unreachable. After both fixes: full migration chain applies
cleanly, pg_dump/pg_restore round-trip is byte-identical (row counts +
content checksums), and real application code (lib/orders-store.ts,
SourceRegistryService's Postgres repository) reads the restored data back
correctly, including cross-tenant isolation still holding.
82 new tests (55 TS + 27 Python), all passing; full existing suite (291 TS +
55 Python) still green; typecheck clean on every changed/added file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
74 lines
3.4 KiB
TypeScript
74 lines
3.4 KiB
TypeScript
/**
|
|
* `src/auth/internal-token.ts` -- verification for the ONE machine-to-machine
|
|
* credential 0pt already uses for engine (Python) -> edge (Next.js) calls:
|
|
* the `X-Internal-Token` header, checked against the `INTERNAL_TOKEN` env var
|
|
* (the same shared secret `lib/engine.ts` and `lib/health-checks.ts` already
|
|
* send on the opposite-direction edge -> engine calls).
|
|
*
|
|
* P014 FIX: `services/api/workers/quality_bridge.py` has always called
|
|
* `POST /api/0pt/quality/bridge-run` with `X-Internal-Token`, never an
|
|
* `Authorization: Bearer` header. But this route sits under
|
|
* `/api/0pt/:path*`, which `middleware.ts` guards unconditionally with real
|
|
* Zitadel Bearer verification -- and nothing anywhere checked
|
|
* `X-Internal-Token` as an alternative credential. The result: in any
|
|
* environment with the default `AUTH_ENFORCE` posture (enforced), the
|
|
* Python engine's ONLY call into this bridge was rejected with 401 by
|
|
* middleware before the route handler ever ran. `OPT_QUALITY_BRIDGE_ENABLED=true`
|
|
* therefore could never actually succeed against a real deployment --
|
|
* quality_bridge.py's fail-open design (see its own docstring) silently
|
|
* absorbed the 401 as `status: "unavailable"`, so nothing outwardly broke,
|
|
* but the whole point of building the bridge (an advisory second opinion
|
|
* from the TS QualityService) was unreachable. Neither test suite caught
|
|
* this: the TS test (tests/p0/quality-bridge.test.ts) calls the route's
|
|
* POST function directly, bypassing middleware.ts entirely; the Python test
|
|
* (services/api/tests/test_quality_bridge.py) mocks the HTTP layer and never
|
|
* exercises the edge's real auth gate.
|
|
*
|
|
* This module verifies `X-Internal-Token` in constant time. middleware.ts
|
|
* accepts it ONLY for the one route the engine actually calls
|
|
* (`/api/0pt/quality/bridge-run`) -- not a blanket bypass for the rest of
|
|
* `/api/0pt/*`. Fails closed: an unset/empty `INTERNAL_TOKEN` env var never
|
|
* authenticates, even against an empty or missing header (no accidental
|
|
* "unset equals unset, so let it through").
|
|
*
|
|
* Uses only Web APIs (`TextEncoder`), not `node:crypto` -- Next.js
|
|
* middleware runs on the Edge runtime, which does not expose Node's
|
|
* `crypto.timingSafeEqual`.
|
|
*/
|
|
|
|
/** Minimal header accessor -- Web `Headers` and `NextRequest.headers` satisfy this. */
|
|
export interface HeaderLookup {
|
|
get(name: string): string | null | undefined;
|
|
}
|
|
|
|
export const INTERNAL_TOKEN_HEADER = "x-internal-token";
|
|
|
|
/** Constant-time-ish byte comparison (Edge-runtime safe: no node:crypto). */
|
|
function timingSafeEqualStr(a: string, b: string): boolean {
|
|
const enc = new TextEncoder();
|
|
const aBytes = enc.encode(a);
|
|
const bBytes = enc.encode(b);
|
|
const len = Math.max(aBytes.length, bBytes.length);
|
|
let diff = aBytes.length ^ bBytes.length;
|
|
for (let i = 0; i < len; i++) {
|
|
diff |= (aBytes[i] ?? 0) ^ (bBytes[i] ?? 0);
|
|
}
|
|
return diff === 0;
|
|
}
|
|
|
|
/**
|
|
* True only when `X-Internal-Token` is present AND matches a non-empty
|
|
* configured `INTERNAL_TOKEN`. Fails closed on every other combination
|
|
* (missing header, missing/empty env var, mismatch).
|
|
*/
|
|
export function verifyInternalServiceToken(
|
|
headers: HeaderLookup,
|
|
env: { INTERNAL_TOKEN?: string } = process.env,
|
|
): boolean {
|
|
const expected = env.INTERNAL_TOKEN?.trim();
|
|
if (!expected) return false;
|
|
const provided = headers.get(INTERNAL_TOKEN_HEADER);
|
|
if (!provided) return false;
|
|
return timingSafeEqualStr(provided, expected);
|
|
}
|