Skip to content

Commit 4529f86

Browse files
SafetyMPSage Hart
andauthored
Harden harness digests, canary, and HITL defaults. (#43)
Bind adversarial probes into scripts/harness, require a non-trivial verify canary, exercise real AUTH/OTEL/ASI05 checks, and stop auto-approving company-day exceptions by default. Co-authored-by: Sage Hart <sagehart@MacBookAir.attlocal.net>
1 parent 0ceb8cd commit 4529f86

13 files changed

Lines changed: 189 additions & 37 deletions

File tree

AGENTS.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,21 @@
77
| `./scripts/harness/verify.sh` | Functional and static acceptance |
88
| `./scripts/harness/adversarial.sh` | Authorized local adversarial probes |
99

10-
Record `verification_scripts` as the site directory `scripts/harness` (exactly those
11-
two scripts). Optional wrappers may remain at `scripts/verify.sh` /
12-
`scripts/adversarial.sh` for humans; they are outside the digest boundary.
10+
Record `verification_scripts` as the site directory `scripts/harness`. Required
11+
entrypoints are `verify.sh` and `adversarial.sh`. Digest-bound companions under
12+
the same directory (included in the harness digest) are:
13+
14+
- `check-stub-canary.sh` — mandatory non-trivial verify canary
15+
- `adversarial-run.mjs` — adversarial probe implementation
16+
17+
Optional wrappers may remain at `scripts/verify.sh` / `scripts/adversarial.sh`
18+
(and `scripts/check-stub-canary.sh`) for humans; they are outside the digest
19+
boundary.
1320

1421
The corporate handoff fixes scope. The site manager assigns ADRs; site specialists write;
1522
operations excellence reviews current evidence. Work in isolated roots, never edit
1623
corporate approval state, and never self-approve.
1724

1825
Site id: `corpos`. Prior Cursor Harness v4 is under `_archives/harness-v4/`.
26+
Product company-day demos must not auto-approve exceptions unless a test/CI
27+
caller passes `autoApproveException: true` explicitly.

apps/api/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
"version": "0.2.0",
44
"private": true,
55
"type": "module",
6+
"main": "./dist/app.js",
7+
"types": "./dist/app.d.ts",
8+
"exports": {
9+
".": {
10+
"types": "./dist/app.d.ts",
11+
"import": "./dist/app.js"
12+
}
13+
},
614
"scripts": {
715
"build": "tsc -p tsconfig.json",
816
"typecheck": "tsc -p tsconfig.json --noEmit",

apps/api/src/app.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import fs from "node:fs";
2121
import path from "node:path";
2222
import { fileURLToPath } from "node:url";
2323

24-
function requireAuth(c: { req: { header: (n: string) => string | undefined } }): boolean {
24+
/** Dashboard bearer gate — exported for adversarial behavioral probes. */
25+
export function requireAuth(c: { req: { header: (n: string) => string | undefined } }): boolean {
2526
if (process.env.CORPOS_MODE !== "shared") return true;
2627
const expected = process.env.DASHBOARD_API_TOKEN?.trim();
2728
if (!expected) return false;
@@ -97,10 +98,11 @@ export function buildApp(company: Company, mode: "simulation" | "live" = "simula
9798
});
9899

99100
app.post("/api/company-day", async (c) => {
100-
let autoApproveException = true;
101+
// Default off: do not imply human approval. Explicit body opt-in for demos/tests.
102+
let autoApproveException = false;
101103
try {
102104
const body = await c.req.json<{ autoApproveException?: boolean }>();
103-
if (body.autoApproveException === false) autoApproveException = false;
105+
if (body.autoApproveException === true) autoApproveException = true;
104106
} catch {
105107
/* empty body */
106108
}

apps/console/src/main.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ function App() {
113113
const r = (await fetch("/api/company-day", {
114114
method: "POST",
115115
headers: { "content-type": "application/json" },
116-
body: JSON.stringify({ autoApproveException: true }),
116+
body: JSON.stringify({ autoApproveException: false }),
117117
}).then((res) => res.json())) as CompanyDay;
118118
setDay(r);
119119
await refresh();

docs/governance-crosswalk.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Pedagogical mapping only — **not** a certification claim.
1313

1414
## OWASP Top 10 for Agentic Applications 2026
1515

16-
See Governor `/api/governance` and `scripts/adversarial-run.mjs` ASI cells.
16+
See Governor `/api/governance` and `scripts/harness/adversarial-run.mjs` ASI cells.
1717

1818
## Related standards
1919

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,45 @@
11
{
22
"schema": "corpos-site-delivery-receipt/v1",
33
"program_id": "corpos-autonomous-company-r2",
4-
"recorded_at": "2026-07-26T13:13:35.124777+00:00",
4+
"site_id": "corpos",
5+
"recorded_at": "2026-07-26T13:33:11.756553+00:00",
6+
"captured_at": "2026-07-26T13:33:11.756553+00:00",
7+
"node": "v26.4.0",
58
"verify": "PASS",
69
"adversarial": "PASS",
7-
"notes": "Orchestrator + governance plane revision; shared firm store; ASI matrix."
10+
"verification_scripts": "scripts/harness",
11+
"verify_argv": ["./scripts/harness/verify.sh"],
12+
"adversarial_argv": ["./scripts/harness/adversarial.sh"],
13+
"verification_scripts_sha256": "8415380abcd32346db4a1c151dd4c063dddc58bb3f561780360c0eae62489538",
14+
"harness_file_sha256": {
15+
"adversarial-run.mjs": "3438da18530a0a6de798566eacd97a08456054edbedb026a0563e6475cae66fd",
16+
"adversarial.sh": "c5c40ecb99ba3c5322d036ed7c6838a6f969e343003f5da3148a17288cb11d52",
17+
"check-stub-canary.sh": "7bf1f3ceda1fa7e5f5feee2da0bbc17597c457ffa8b491801162ed8d05e59db5",
18+
"verify.sh": "688518e9a148710768bb565525cac41c78a4092208069b6fea08825ed1483df8"
19+
},
20+
"surfaces": {
21+
"firm_work_control": "packages/core/src",
22+
"api": "apps/api/src",
23+
"console": "apps/console/src",
24+
"mcp_knowledge": "packages/mcp-knowledge/src",
25+
"adrs": "docs/adr",
26+
"essay": "docs/future-of-the-firm.md"
27+
},
28+
"company_day": {
29+
"contractId": "ctr_g16siuyoYE",
30+
"handoffs": 2,
31+
"autonomousSettles": 1,
32+
"exceptionSettles": 1,
33+
"compensated": 1,
34+
"trustAfter": 2,
35+
"slaExceptions": 1,
36+
"auditHead": "913d51c35e75fe4c148b6f893b2add53975564e8033874f24ce8be8737280e5e",
37+
"ok": true
38+
},
39+
"adversarial_stdout": "adversarial: ok",
40+
"stack_forbidden_absent": {
41+
"better-sqlite3": true,
42+
"express_import": true
43+
},
44+
"notes": "Harness effectiveness remediation: digest-bound probes/canary; behavioral AUTH/OTEL/ASI05; HITL auto-approve default off."
845
}

packages/core/src/company-day.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ export async function runCompanyDay(opts?: {
5050
company?: Company;
5151
withMcp?: boolean;
5252
serverCommand?: { command: string; args: string[] };
53-
/** Auto-approve ops exception for deterministic CI (console can decide manually). */
53+
/**
54+
* Explicit opt-in only. Default false so demo/API paths do not imply human
55+
* approval. Tests/CI that need a settled exception must pass true.
56+
*/
5457
autoApproveException?: boolean;
5558
}): Promise<{ company: Company; result: CompanyDayResult }> {
5659
resetSpans();
@@ -194,7 +197,7 @@ export async function runCompanyDay(opts?: {
194197
delegationDepth: 2,
195198
});
196199

197-
const autoApprove = opts?.autoApproveException !== false;
200+
const autoApprove = opts?.autoApproveException === true;
198201
if (ops.awaitingExceptionId) {
199202
push({
200203
agentId: "agent_ops",
@@ -209,7 +212,7 @@ export async function runCompanyDay(opts?: {
209212
agentId: "agent_ops",
210213
role: "Ops",
211214
kind: "exception",
212-
summary: "Human approved restart; billing-api restarted.",
215+
summary: "Demo auto-approved restart (explicit opt-in); billing-api restarted.",
213216
});
214217
await company.trust.recordAccept("agent_support");
215218
push({

scripts/check-stub-canary.sh

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
#!/usr/bin/env bash
2-
# DO_NOT_DELETE_STUB_CANARY
3-
set -euo pipefail
4-
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
5-
cd "$ROOT"
6-
if [[ -f scripts/verify.sh ]] && grep -q 'TODO: add real test' scripts/verify.sh; then
7-
echo "STUB_CANARY: placeholder verify.sh" >&2
8-
exit 1
9-
fi
10-
echo "check-stub-canary: ok"
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
exec "$(cd "$(dirname "$0")" && pwd)/harness/check-stub-canary.sh" "$@"
Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
listSpans,
77
resetSpans,
88
} from "@corpos/core";
9+
import { buildApp } from "@corpos/api";
910
import { eq } from "drizzle-orm";
1011
import fs from "node:fs";
1112
import path from "node:path";
@@ -19,6 +20,8 @@ function cell(id, ok, detail = "") {
1920
if (!ok) throw new Error(`${id} failed: ${detail}`);
2021
}
2122

23+
const DANGEROUS_TOOL_RE = /\b(shell|eval|exec|spawn|child_process|bash|powershell|os\.system)\b/i;
24+
2225
// ASI02 / gateway bypass
2326
const bypass = await company.gateway.invoke(
2427
"evil.shell",
@@ -37,13 +40,17 @@ cell(
3740
"unknown tool",
3841
);
3942

40-
// ASI05 attestation — no shell/eval tools
41-
const aibomPath = path.resolve("docs/aibom.json");
42-
const aibom = JSON.parse(fs.readFileSync(aibomPath, "utf8"));
43+
// ASI05 — live tool registry scan (not AIBOM self-attestation alone)
44+
const registered = company.gateway.list();
45+
const dangerousLive = registered.filter(
46+
(t) => DANGEROUS_TOOL_RE.test(t.name) || DANGEROUS_TOOL_RE.test(t.description ?? ""),
47+
);
4348
cell(
4449
"ASI05",
45-
Array.isArray(aibom.dangerous_tools_registered) && aibom.dangerous_tools_registered.length === 0,
46-
"no dangerous tools",
50+
registered.length > 0 && dangerousLive.length === 0,
51+
dangerousLive.length
52+
? `dangerous tools: ${dangerousLive.map((t) => t.name).join(",")}`
53+
: "no shell/eval tools in live registry",
4754
);
4855

4956
// ASI03 three-layer privilege expansion
@@ -127,6 +134,8 @@ cell("ASI01", untrusted, "KB marked untrusted");
127134
cell("ASI06", untrusted, "context poisoning boundary");
128135

129136
// ASI04 AIBOM
137+
const aibomPath = path.resolve("docs/aibom.json");
138+
const aibom = JSON.parse(fs.readFileSync(aibomPath, "utf8"));
130139
cell("ASI04", Boolean(aibom.policyBundleHash) && Array.isArray(aibom.mcp_servers), "aibom");
131140

132141
// ASI07 handoff envelope via deny missing originator
@@ -153,10 +162,35 @@ const email = await company.gateway.invoke(
153162
);
154163
cell("ASI09", email.decision.effect === "approve" && Boolean(email.decision.approvalId), "HITL");
155164

156-
// Auth shared mode
165+
// AUTH — exercise real requireAuth via HTTP on /api/kill (shared mode)
166+
const prevMode = process.env.CORPOS_MODE;
167+
const prevToken = process.env.DASHBOARD_API_TOKEN;
157168
process.env.CORPOS_MODE = "shared";
158169
process.env.DASHBOARD_API_TOKEN = "secret";
159-
cell("AUTH", `Bearer ${process.env.DASHBOARD_API_TOKEN}` === "Bearer secret", "token");
170+
const app = buildApp(company, "simulation");
171+
const unauth = await app.request("/api/kill", {
172+
method: "POST",
173+
headers: { "content-type": "application/json" },
174+
body: JSON.stringify({ killed: true }),
175+
});
176+
const authed = await app.request("/api/kill", {
177+
method: "POST",
178+
headers: {
179+
"content-type": "application/json",
180+
authorization: "Bearer secret",
181+
},
182+
body: JSON.stringify({ killed: false }),
183+
});
184+
if (prevMode === undefined) delete process.env.CORPOS_MODE;
185+
else process.env.CORPOS_MODE = prevMode;
186+
if (prevToken === undefined) delete process.env.DASHBOARD_API_TOKEN;
187+
else process.env.DASHBOARD_API_TOKEN = prevToken;
188+
const authedBody = await authed.json();
189+
cell(
190+
"AUTH",
191+
unauth.status === 401 && authed.status === 200 && authedBody.killed === false,
192+
`unauth=${unauth.status} authed=${authed.status}`,
193+
);
160194

161195
// Audit forge
162196
await company.audit.append("a", { n: 1 });
@@ -173,9 +207,13 @@ const { resolveProvider } = await import("@corpos/core");
173207
const resolved = resolveProvider(process.env);
174208
cell("HEALTH", resolved.mode === "simulation", "no live without allow");
175209

176-
// OTel spans emitted on gateway
177-
void listSpans();
178-
cell("OTEL", true, "span buffer available");
210+
// OTel — require recorded spans from prior gateway invokes
211+
const spans = listSpans();
212+
cell(
213+
"OTEL",
214+
spans.some((s) => s.operation === "execute_tool" || s.operation === "invoke_agent"),
215+
`spans=${spans.length}`,
216+
);
179217

180218
company.close();
181219
console.log("adversarial: ok");

scripts/harness/adversarial.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,10 @@
22
set -euo pipefail
33
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
44
cd "$ROOT"
5-
node scripts/adversarial-run.mjs
5+
# Probe logic lives in digest-bound scripts/harness/adversarial-run.mjs
6+
PROBE="./scripts/harness/adversarial-run.mjs"
7+
if [[ ! -f "${PROBE}" ]]; then
8+
echo "adversarial: missing ${PROBE}" >&2
9+
exit 1
10+
fi
11+
node "${PROBE}"

0 commit comments

Comments
 (0)