Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
# logs, and agent output.

# ── Runtime mode ─────────────────────────────────────────────────────
# local (default): approvals/kill do not require a shared token.
# shared: require DASHBOARD_API_TOKEN for approval and kill mutations.
# CORPOS_MODE=local
# Dashboard mutations require DASHBOARD_API_TOKEN by default (FO-017).
# Ungated local simulation only with an explicit opt-in:
# CORPOS_ALLOW_UNAUTHENTICATED=true
# CORPOS_MODE=local does not skip the bearer gate.
# CORPOS_MODE=shared
# DASHBOARD_API_TOKEN=

# ── Data ─────────────────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Architecture decisions live in [`docs/adr/README.md`](docs/adr/README.md).

## Security

Reference architecture — not production-hardened. `CORPOS_MODE=shared` plus `DASHBOARD_API_TOKEN` enables a **Bearer token gate on the API** for approve/kill mutations. The ops console does not yet attach that token (shared-mode approvals return 401 until a Bearer-capable client is used). See [SECURITY.md](SECURITY.md).
Reference architecture — not production-hardened. `DASHBOARD_API_TOKEN` is required for approve/kill mutations by default; `CORPOS_MODE=local` does not skip the bearer gate. Set `CORPOS_ALLOW_UNAUTHENTICATED=true` only for local simulation. See [SECURITY.md](SECURITY.md).

## Community

Expand Down
6 changes: 4 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Treat it as a design artifact you can run locally.
- Unknown tools fail closed.
- Exception HITL with scheduled TTL fail-closed; kill switch; department capital caps.
- Shared-mode console sends Bearer (`VITE_DASHBOARD_API_TOKEN` / `DASHBOARD_API_TOKEN`).
- Dashboard mutations require `DASHBOARD_API_TOKEN` by default; `CORPOS_MODE !== "shared"` does not ungated them.
- G3 quorum, G6 appeal, and enforcement `strict`/`audit` modes.
- Hash-chained audit receipts (`npm run audit:verify`).
- Company-day demos do not auto-approve exceptions unless a caller passes
Expand All @@ -26,8 +27,9 @@ Treat it as a design artifact you can run locally.

## Shared demo

When `CORPOS_MODE=shared`, `DASHBOARD_API_TOKEN` is required as a **Bearer**
token on API approve and kill mutations. The ops console sends
When `CORPOS_ALLOW_UNAUTHENTICATED` is unset, `DASHBOARD_API_TOKEN` is required as a **Bearer**
token on API approve and kill mutations. `CORPOS_MODE=local` (or any value other than
an explicit opt-in) does not skip the gate. The ops console sends
`VITE_DASHBOARD_API_TOKEN` when configured. This is a
static token compare for demos, not an OAuth flow.

Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import { fileURLToPath } from "node:url";

/** Dashboard bearer gate — exported for adversarial behavioral probes. */
export function requireAuth(c: { req: { header: (n: string) => string | undefined } }): boolean {
if (process.env.CORPOS_MODE !== "shared") return true;
// Ungated simulation only with an explicit opt-in (FO-017). CORPOS_MODE !== "shared" must not imply allow.
if (process.env.CORPOS_ALLOW_UNAUTHENTICATED === "true") return true;
const expected = process.env.DASHBOARD_API_TOKEN?.trim();
if (!expected) return false;
const header = c.req.header("authorization") ?? "";
Expand Down
39 changes: 35 additions & 4 deletions scripts/harness/adversarial-run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,34 @@ const email = await company.gateway.invoke(
);
cell("ASI09", email.decision.effect === "approve" && Boolean(email.decision.approvalId), "HITL");

// AUTH — exercise real requireAuth via HTTP on /api/kill (shared mode)
// AUTH — requireAuth is closed by default; CORPOS_MODE=local must not ungated (FO-017)
const prevMode = process.env.CORPOS_MODE;
const prevToken = process.env.DASHBOARD_API_TOKEN;
process.env.CORPOS_MODE = "shared";
const prevAllow = process.env.CORPOS_ALLOW_UNAUTHENTICATED;
delete process.env.CORPOS_ALLOW_UNAUTHENTICATED;
delete process.env.CORPOS_MODE;
process.env.DASHBOARD_API_TOKEN = "secret";
const app = buildApp(company, "simulation");
const defaultUnauth = await app.request("/api/kill", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ killed: true }),
});
process.env.CORPOS_MODE = "local";
const localUnauth = await app.request("/api/kill", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ killed: true }),
});
process.env.CORPOS_ALLOW_UNAUTHENTICATED = "true";
const optIn = await app.request("/api/kill", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ killed: false }),
});
delete process.env.CORPOS_ALLOW_UNAUTHENTICATED;
process.env.CORPOS_MODE = "shared";
process.env.DASHBOARD_API_TOKEN = "secret";
const unauth = await app.request("/api/kill", {
method: "POST",
headers: { "content-type": "application/json" },
Expand All @@ -185,11 +207,20 @@ if (prevMode === undefined) delete process.env.CORPOS_MODE;
else process.env.CORPOS_MODE = prevMode;
if (prevToken === undefined) delete process.env.DASHBOARD_API_TOKEN;
else process.env.DASHBOARD_API_TOKEN = prevToken;
if (prevAllow === undefined) delete process.env.CORPOS_ALLOW_UNAUTHENTICATED;
else process.env.CORPOS_ALLOW_UNAUTHENTICATED = prevAllow;
const authedBody = await authed.json();
const optInBody = await optIn.json();
cell(
"AUTH",
unauth.status === 401 && authed.status === 200 && authedBody.killed === false,
`unauth=${unauth.status} authed=${authed.status}`,
defaultUnauth.status === 401 &&
localUnauth.status === 401 &&
unauth.status === 401 &&
authed.status === 200 &&
authedBody.killed === false &&
optIn.status === 200 &&
optInBody.killed === false,
`default=${defaultUnauth.status} local=${localUnauth.status} unauth=${unauth.status} authed=${authed.status} optin=${optIn.status}`,
);

// Audit forge
Expand Down
Loading