@scopebond/hook 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +239 -0
  2. package/dist/budget-load.d.ts +68 -0
  3. package/dist/budget-load.d.ts.map +1 -0
  4. package/dist/budget-load.js +164 -0
  5. package/dist/budget-load.js.map +1 -0
  6. package/dist/capabilities.d.ts +103 -0
  7. package/dist/capabilities.d.ts.map +1 -0
  8. package/dist/capabilities.js +212 -0
  9. package/dist/capabilities.js.map +1 -0
  10. package/dist/classify.d.ts +17 -0
  11. package/dist/classify.d.ts.map +1 -0
  12. package/dist/classify.js +78 -0
  13. package/dist/classify.js.map +1 -0
  14. package/dist/cli.d.ts.map +1 -1
  15. package/dist/cli.js +585 -13
  16. package/dist/cli.js.map +1 -1
  17. package/dist/cloud.d.ts +8 -0
  18. package/dist/cloud.d.ts.map +1 -1
  19. package/dist/cloud.js.map +1 -1
  20. package/dist/dispatch-cli.d.ts +2 -0
  21. package/dist/dispatch-cli.d.ts.map +1 -0
  22. package/dist/dispatch-cli.js +128 -0
  23. package/dist/dispatch-cli.js.map +1 -0
  24. package/dist/explain.d.ts.map +1 -1
  25. package/dist/explain.js +2 -1
  26. package/dist/explain.js.map +1 -1
  27. package/dist/group.d.ts +12 -0
  28. package/dist/group.d.ts.map +1 -0
  29. package/dist/group.js +55 -0
  30. package/dist/group.js.map +1 -0
  31. package/dist/index.d.ts +29 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +19 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/install.d.ts +10 -0
  36. package/dist/install.d.ts.map +1 -1
  37. package/dist/install.js +56 -0
  38. package/dist/install.js.map +1 -1
  39. package/dist/managed.d.ts +69 -0
  40. package/dist/managed.d.ts.map +1 -0
  41. package/dist/managed.js +184 -0
  42. package/dist/managed.js.map +1 -0
  43. package/dist/map.d.ts +4 -0
  44. package/dist/map.d.ts.map +1 -1
  45. package/dist/map.js +29 -1
  46. package/dist/map.js.map +1 -1
  47. package/dist/obs-emitter.d.ts +132 -0
  48. package/dist/obs-emitter.d.ts.map +1 -0
  49. package/dist/obs-emitter.js +415 -0
  50. package/dist/obs-emitter.js.map +1 -0
  51. package/dist/obs-store.d.ts +160 -0
  52. package/dist/obs-store.d.ts.map +1 -0
  53. package/dist/obs-store.js +367 -0
  54. package/dist/obs-store.js.map +1 -0
  55. package/dist/obs-upload.d.ts +24 -0
  56. package/dist/obs-upload.d.ts.map +1 -0
  57. package/dist/obs-upload.js +231 -0
  58. package/dist/obs-upload.js.map +1 -0
  59. package/dist/observation.d.ts +232 -0
  60. package/dist/observation.d.ts.map +1 -0
  61. package/dist/observation.js +308 -0
  62. package/dist/observation.js.map +1 -0
  63. package/dist/paths.d.ts +15 -0
  64. package/dist/paths.d.ts.map +1 -0
  65. package/dist/paths.js +79 -0
  66. package/dist/paths.js.map +1 -0
  67. package/dist/policy-load.d.ts +60 -0
  68. package/dist/policy-load.d.ts.map +1 -0
  69. package/dist/policy-load.js +151 -0
  70. package/dist/policy-load.js.map +1 -0
  71. package/dist/policy-sync.d.ts +45 -0
  72. package/dist/policy-sync.d.ts.map +1 -0
  73. package/dist/policy-sync.js +171 -0
  74. package/dist/policy-sync.js.map +1 -0
  75. package/dist/proof.d.ts +30 -0
  76. package/dist/proof.d.ts.map +1 -0
  77. package/dist/proof.js +186 -0
  78. package/dist/proof.js.map +1 -0
  79. package/dist/rules.d.ts +20 -0
  80. package/dist/rules.d.ts.map +1 -1
  81. package/dist/rules.js +50 -0
  82. package/dist/rules.js.map +1 -1
  83. package/dist/runtime.d.ts +21 -1
  84. package/dist/runtime.d.ts.map +1 -1
  85. package/dist/runtime.js +77 -10
  86. package/dist/runtime.js.map +1 -1
  87. package/dist/scan.d.ts +7 -0
  88. package/dist/scan.d.ts.map +1 -0
  89. package/dist/scan.js +38 -0
  90. package/dist/scan.js.map +1 -0
  91. package/dist/shell.d.ts.map +1 -1
  92. package/dist/shell.js +12 -3
  93. package/dist/shell.js.map +1 -1
  94. package/dist/sql-classify.d.ts +11 -0
  95. package/dist/sql-classify.d.ts.map +1 -0
  96. package/dist/sql-classify.js +251 -0
  97. package/dist/sql-classify.js.map +1 -0
  98. package/dist/typed-infra.d.ts +73 -0
  99. package/dist/typed-infra.d.ts.map +1 -0
  100. package/dist/typed-infra.js +758 -0
  101. package/dist/typed-infra.js.map +1 -0
  102. package/dist/typed-ops.d.ts +140 -0
  103. package/dist/typed-ops.d.ts.map +1 -0
  104. package/dist/typed-ops.js +549 -0
  105. package/dist/typed-ops.js.map +1 -0
  106. package/dist/vectors.d.ts +40 -0
  107. package/dist/vectors.d.ts.map +1 -0
  108. package/dist/vectors.js +219 -0
  109. package/dist/vectors.js.map +1 -0
  110. package/package.json +5 -5
package/dist/proof.js ADDED
@@ -0,0 +1,186 @@
1
+ // Safe proof fixtures.
2
+ //
3
+ // For each supported capability cell this runs its tagged vectors end to end against a
4
+ // throwaway hook home: a safe action must be allowed and countersigned; a violating one
5
+ // must be denied at the hook boundary; every receipt must verify against the
6
+ // countersigning key; and every receipt of one tool call must carry one action group.
7
+ //
8
+ // Nothing here touches a developer's real settings, policy, keys or receipts. Every run
9
+ // creates its own directory under the OS temp dir, scaffolds a fresh machine key, policy
10
+ // and store there, and deletes it afterwards. No network is used, so a fixture can never
11
+ // produce a Cloud acknowledgement: `cloud_ack` is always `not_checked`, and `origin` is
12
+ // always `fixture`. A cell therefore cannot reach `verified_reporting` from this runner
13
+ // (see capabilities.ts); it can only show `configured_unverified` with a passing
14
+ // fixture, or `degraded` with a failing one.
15
+ //
16
+ // Observation-only cells (Cursor's after-edit event, and monitor-mode fetch and MCP
17
+ // calls) are proven with a known successful after-action fixture, labelled
18
+ // observation-only. No deny fixture is run for them and none is claimed.
19
+ import { copyFileSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
20
+ import { tmpdir } from "node:os";
21
+ import { join } from "node:path";
22
+ import { createCloudExporter, createMemoryCloudOutbox, verifyReceipt } from "@scopebond/gateway";
23
+ import { loadOrCreateAttester } from "@scopebond/gateway/node";
24
+ import { fillPushBranch } from "./map.js";
25
+ import { scaffold } from "./init.js";
26
+ import { createHookRuntime } from "./runtime.js";
27
+ import { ACTION_GROUP_PARAM } from "./group.js";
28
+ import { computeManifest, specForCell, vectorDigest, vectorsForCell } from "./capabilities.js";
29
+ import { mapVector } from "./vectors.js";
30
+ import { bindingKeyFromHex, operationsForCall, sourceReceiptHash } from "./observation.js";
31
+ import { callRequestOf, fixtureProbe } from "./typed-ops.js";
32
+ import { fixtureFiles } from "./typed-infra.js";
33
+ export const PROOF_FILE = "capability-proof.json";
34
+ const receiptActionType = (receipt) => receipt?.payload?.intent?.action_type;
35
+ const receiptParams = (receipt) => {
36
+ const payload = receipt?.payload;
37
+ return payload?.intent?.params ?? {};
38
+ };
39
+ const FIXTURE_KEY = bindingKeyFromHex("00".repeat(32));
40
+ /** Whether the adapter derives the typed operation a vector expects, from the same dispatched
41
+ * actions and raw request the hook uses, against a deterministic fixture repository. */
42
+ function typedDerived(vector, dispatched) {
43
+ if (!vector.typed)
44
+ return true;
45
+ const request = callRequestOf(vector.input);
46
+ const operations = operationsForCall({ dispatched: dispatched ?? [], request }, { key: FIXTURE_KEY, cwd: "/fixture", repositoryId: "sbr_fixture", probe: fixtureProbe, packageManagerVersion: () => undefined, files: fixtureFiles, env: () => undefined });
47
+ return operations.some((op) => op?.type === vector.typed.type && (vector.typed.verb === undefined || op.verb === vector.typed.verb));
48
+ }
49
+ async function run(vector, runtime, attesterPem) {
50
+ const decision = await runtime.evaluate(fillPushBranch(mapVector(vector), "feature/work"), { groupKey: `proof:${vector.id}` });
51
+ const receipts = decision.receipts ?? (decision.receipt ? [decision.receipt] : []);
52
+ const valid = receipts.length > 0 && receipts.every((r) => verifyReceipt(r, attesterPem).valid);
53
+ const groups = new Set(receipts.map((r) => receiptParams(r)[ACTION_GROUP_PARAM]));
54
+ const grouped = receipts.length > 0 && groups.size === 1 && typeof [...groups][0] === "string" && String([...groups][0]) !== "";
55
+ return { decision: decision.decision, receipts, valid, grouped, typed: typedDerived(vector, decision.dispatched) };
56
+ }
57
+ /** Run one cell's fixtures in a temp hook home. Returns null when the cell has no vectors. */
58
+ async function proveCell(adapter, actionType, phase, observationOnly, adapterVersion, options) {
59
+ const vectors = vectorsForCell(adapter, actionType, phase);
60
+ const digest = vectorDigest(vectors);
61
+ if (digest === null)
62
+ return null;
63
+ const dir = mkdtempSync(join(tmpdir(), "scopebond-proof-"));
64
+ let runtime;
65
+ try {
66
+ if (options.identityDir) {
67
+ for (const name of ["agent.key", "attester.key"]) {
68
+ const source = join(options.identityDir, name);
69
+ if (existsSync(source))
70
+ copyFileSync(source, join(dir, name));
71
+ }
72
+ }
73
+ scaffold(dir);
74
+ const attesterPath = join(dir, "attester.key");
75
+ const { attester } = loadOrCreateAttester({ file: attesterPath });
76
+ runtime = createHookRuntime({ policyPath: join(dir, "policy.json"), keyPath: join(dir, "agent.key"), attesterPath, dbPath: join(dir, "receipts.db") });
77
+ const allow = vectors.filter((v) => v.cell?.role === "allow" || v.cell?.role === "after_action");
78
+ const deny = vectors.filter((v) => v.cell?.role === "deny");
79
+ const allowed = [];
80
+ for (const v of allow)
81
+ allowed.push(await run(v, runtime, attester.publicKeyPem));
82
+ const denied = [];
83
+ if (!observationOnly)
84
+ for (const v of deny)
85
+ denied.push(await run(v, runtime, attester.publicKeyPem));
86
+ const all = [...allowed, ...denied];
87
+ const receipts = all.flatMap((o) => o.receipts);
88
+ // Only receipts of the cell's own action type stand as its proof (a compound command can
89
+ // also produce receipts of other types), and each is named by the workspace's projection.
90
+ const proofDigests = [...new Set(receipts.filter((r) => receiptActionType(r) === actionType).map((r) => sourceReceiptHash(r)))];
91
+ return { receipts, proof: {
92
+ ran_at: new Date().toISOString(), adapter_version: adapterVersion, test_vector_digest: digest, origin: "fixture",
93
+ safe_allow: allowed.length > 0 && allowed.every((o) => o.decision !== "deny" && o.receipts.length > 0),
94
+ safe_deny: observationOnly ? "not_applicable" : denied.length > 0 && denied.every((o) => o.decision === "deny"),
95
+ signature: all.length > 0 && all.every((o) => o.valid),
96
+ grouping: all.length > 0 && all.every((o) => o.grouped),
97
+ cloud_ack: "not_checked", observation_only: observationOnly, proof_digests: proofDigests,
98
+ ...(vectors.some((v) => v.typed) ? { typed_operation: all.length > 0 && all.every((o) => o.typed !== false) } : {}),
99
+ } };
100
+ }
101
+ finally {
102
+ try {
103
+ runtime?.close();
104
+ }
105
+ catch { /* the temp home is removed next */ }
106
+ try {
107
+ rmSync(dir, { recursive: true, force: true, maxRetries: 5 });
108
+ }
109
+ catch { /* a handle still open on Windows; the OS temp dir is cleaned later */ }
110
+ }
111
+ }
112
+ /** Run every supported cell's fixtures once per adapter/action/phase, and record the
113
+ * result under each host variant's key. A fixture exercises the adapter, so it cannot
114
+ * tell Codex desktop from Codex CLI: both keys get the same fixture proof, and neither
115
+ * becomes verified (see `cellState`). */
116
+ export async function runProofFixtures(adapterVersion, options = {}) {
117
+ const cells = computeManifest({ adapterVersion, configured: { claude: true, codex: true, cursor: true } }).cells;
118
+ const records = {};
119
+ const cache = new Map();
120
+ for (const cell of cells) {
121
+ if (cell.state === "unsupported")
122
+ continue;
123
+ const spec = specForCell(cell);
124
+ if (!spec)
125
+ continue;
126
+ const id = `${spec.adapter}/${spec.action_type}/${spec.phase}`;
127
+ if (!cache.has(id)) {
128
+ const run = await proveCell(spec.adapter, spec.action_type, spec.phase, cell.observation_only, adapterVersion, options);
129
+ if (run)
130
+ options.collect?.push(...run.receipts);
131
+ cache.set(id, run?.proof ?? null);
132
+ }
133
+ const proof = cache.get(id);
134
+ if (proof)
135
+ records[cell.key] = { cell: cell.key, ...proof };
136
+ }
137
+ return records;
138
+ }
139
+ export function loadProofs(configDir) {
140
+ const file = join(configDir, PROOF_FILE);
141
+ if (!existsSync(file))
142
+ return {};
143
+ try {
144
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
145
+ return parsed.proofs && typeof parsed.proofs === "object" ? parsed.proofs : {};
146
+ }
147
+ catch {
148
+ return {};
149
+ }
150
+ }
151
+ export function saveProofs(configDir, proofs) {
152
+ const file = join(configDir, PROOF_FILE);
153
+ writeFileSync(file, `${JSON.stringify({ version: 1, proofs }, null, 2)}\n`);
154
+ return file;
155
+ }
156
+ /** Whether a cell's fixture proof passed, for a summary line. */
157
+ export const proofPassed = (proof, cell) => proof.safe_allow && (cell.observation_only ? proof.safe_deny === "not_applicable" : proof.safe_deny === true) && proof.signature && proof.grouping;
158
+ /** Deliver fixture receipts to the workspace's receipt route and report whether every one was
159
+ * accepted. A proof names these receipts by digest, so it is only worth sending after they
160
+ * landed; anything short of full delivery leaves the proof unsent, never half-claimed. */
161
+ export async function deliverProofReceipts(connection, receipts, options = {}) {
162
+ if (receipts.length === 0)
163
+ return true;
164
+ const exporter = createCloudExporter({
165
+ url: connection.url, credential: connection.credential, outbox: createMemoryCloudOutbox({ maxPending: Math.max(1000, receipts.length) }),
166
+ fetch: options.fetch, flushMs: 60_000,
167
+ });
168
+ try {
169
+ for (const receipt of receipts)
170
+ exporter.enqueue(receipt);
171
+ const deadline = Date.now() + (options.timeoutMs ?? 15_000);
172
+ while (exporter.pending() > 0 && Date.now() < deadline) {
173
+ await exporter.flush();
174
+ if (exporter.pending() > 0) {
175
+ if (exporter.status().consecutiveFailures > 0)
176
+ return false;
177
+ await new Promise((resolve) => setTimeout(resolve, 25));
178
+ }
179
+ }
180
+ return exporter.pending() === 0;
181
+ }
182
+ finally {
183
+ exporter.stop();
184
+ }
185
+ }
186
+ //# sourceMappingURL=proof.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proof.js","sourceRoot":"","sources":["../src/proof.ts"],"names":[],"mappings":"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","sourcesContent":["// Safe proof fixtures.\n//\n// For each supported capability cell this runs its tagged vectors end to end against a\n// throwaway hook home: a safe action must be allowed and countersigned; a violating one\n// must be denied at the hook boundary; every receipt must verify against the\n// countersigning key; and every receipt of one tool call must carry one action group.\n//\n// Nothing here touches a developer's real settings, policy, keys or receipts. Every run\n// creates its own directory under the OS temp dir, scaffolds a fresh machine key, policy\n// and store there, and deletes it afterwards. No network is used, so a fixture can never\n// produce a Cloud acknowledgement: `cloud_ack` is always `not_checked`, and `origin` is\n// always `fixture`. A cell therefore cannot reach `verified_reporting` from this runner\n// (see capabilities.ts); it can only show `configured_unverified` with a passing\n// fixture, or `degraded` with a failing one.\n//\n// Observation-only cells (Cursor's after-edit event, and monitor-mode fetch and MCP\n// calls) are proven with a known successful after-action fixture, labelled\n// observation-only. No deny fixture is run for them and none is claimed.\n\nimport { copyFileSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { createCloudExporter, createMemoryCloudOutbox, verifyReceipt, type SignedReceipt } from \"@scopebond/gateway\";\nimport { loadOrCreateAttester } from \"@scopebond/gateway/node\";\nimport { fillPushBranch } from \"./map.js\";\nimport { scaffold } from \"./init.js\";\nimport { createHookRuntime } from \"./runtime.js\";\nimport { ACTION_GROUP_PARAM } from \"./group.js\";\nimport { computeManifest, specForCell, vectorDigest, vectorsForCell, type Adapter, type CapabilityCell, type ProofRecord } from \"./capabilities.js\";\nimport { mapVector, type Vector } from \"./vectors.js\";\nimport { bindingKeyFromHex, operationsForCall, sourceReceiptHash } from \"./observation.js\";\nimport { callRequestOf, fixtureProbe } from \"./typed-ops.js\";\nimport { fixtureFiles } from \"./typed-infra.js\";\nimport type { HookConnection } from \"./cloud.js\";\n\nexport const PROOF_FILE = \"capability-proof.json\";\n\nconst receiptActionType = (receipt: unknown): string | undefined =>\n (receipt as { payload?: { intent?: { action_type?: unknown } } } | undefined)?.payload?.intent?.action_type as string | undefined;\n\n/** Options for a fixture run. */\nexport interface ProofRunOptions {\n /** A real hook config dir whose machine and countersigning keys the fixtures should be\n * signed with (copied into the temp home, never modified), so the workspace can accept\n * the fixture receipts and resolve a proof's digests against them. Without it the\n * fixtures use throwaway keys and produce no digests a workspace could resolve. */\n identityDir?: string;\n /** Receives every fixture receipt produced, once each, for delivery. */\n collect?: unknown[];\n}\n\nconst receiptParams = (receipt: unknown): Record<string, unknown> => {\n const payload = (receipt as { payload?: { intent?: { params?: Record<string, unknown> } } } | undefined)?.payload;\n return payload?.intent?.params ?? {};\n};\n\ninterface Outcome { decision: string; receipts: unknown[]; valid: boolean; grouped: boolean; typed?: boolean }\n\nconst FIXTURE_KEY = bindingKeyFromHex(\"00\".repeat(32));\n\n/** Whether the adapter derives the typed operation a vector expects, from the same dispatched\n * actions and raw request the hook uses, against a deterministic fixture repository. */\nfunction typedDerived(vector: Vector, dispatched: Array<{ action: { action_type: string; params: Record<string, unknown> } }> | undefined): boolean {\n if (!vector.typed) return true;\n const request = callRequestOf(vector.input);\n const operations = operationsForCall({ dispatched: dispatched ?? [], request }, { key: FIXTURE_KEY, cwd: \"/fixture\", repositoryId: \"sbr_fixture\", probe: fixtureProbe, packageManagerVersion: () => undefined, files: fixtureFiles, env: () => undefined });\n return operations.some((op) => op?.type === vector.typed!.type && (vector.typed!.verb === undefined || op.verb === vector.typed!.verb));\n}\n\nasync function run(vector: Vector, runtime: ReturnType<typeof createHookRuntime>, attesterPem: string): Promise<Outcome> {\n const decision = await runtime.evaluate(fillPushBranch(mapVector(vector), \"feature/work\"), { groupKey: `proof:${vector.id}` });\n const receipts = decision.receipts ?? (decision.receipt ? [decision.receipt] : []);\n const valid = receipts.length > 0 && receipts.every((r) => (verifyReceipt(r as Parameters<typeof verifyReceipt>[0], attesterPem) as unknown as { valid: boolean }).valid);\n const groups = new Set(receipts.map((r) => receiptParams(r)[ACTION_GROUP_PARAM]));\n const grouped = receipts.length > 0 && groups.size === 1 && typeof [...groups][0] === \"string\" && String([...groups][0]) !== \"\";\n return { decision: decision.decision, receipts, valid, grouped, typed: typedDerived(vector, decision.dispatched) };\n}\n\n/** Run one cell's fixtures in a temp hook home. Returns null when the cell has no vectors. */\nasync function proveCell(adapter: Adapter, actionType: string, phase: \"pre_action\" | \"after_action\", observationOnly: boolean, adapterVersion: string, options: ProofRunOptions): Promise<{ proof: Omit<ProofRecord, \"cell\">; receipts: unknown[] } | null> {\n const vectors = vectorsForCell(adapter, actionType, phase);\n const digest = vectorDigest(vectors);\n if (digest === null) return null;\n const dir = mkdtempSync(join(tmpdir(), \"scopebond-proof-\"));\n let runtime: ReturnType<typeof createHookRuntime> | undefined;\n try {\n if (options.identityDir) {\n for (const name of [\"agent.key\", \"attester.key\"]) {\n const source = join(options.identityDir, name);\n if (existsSync(source)) copyFileSync(source, join(dir, name));\n }\n }\n scaffold(dir);\n const attesterPath = join(dir, \"attester.key\");\n const { attester } = loadOrCreateAttester({ file: attesterPath });\n runtime = createHookRuntime({ policyPath: join(dir, \"policy.json\"), keyPath: join(dir, \"agent.key\"), attesterPath, dbPath: join(dir, \"receipts.db\") });\n const allow = vectors.filter((v) => v.cell?.role === \"allow\" || v.cell?.role === \"after_action\");\n const deny = vectors.filter((v) => v.cell?.role === \"deny\");\n const allowed: Outcome[] = [];\n for (const v of allow) allowed.push(await run(v, runtime, attester.publicKeyPem));\n const denied: Outcome[] = [];\n if (!observationOnly) for (const v of deny) denied.push(await run(v, runtime, attester.publicKeyPem));\n const all = [...allowed, ...denied];\n const receipts = all.flatMap((o) => o.receipts);\n // Only receipts of the cell's own action type stand as its proof (a compound command can\n // also produce receipts of other types), and each is named by the workspace's projection.\n const proofDigests = [...new Set(receipts.filter((r) => receiptActionType(r) === actionType).map((r) => sourceReceiptHash(r)))];\n return { receipts, proof: {\n ran_at: new Date().toISOString(), adapter_version: adapterVersion, test_vector_digest: digest, origin: \"fixture\",\n safe_allow: allowed.length > 0 && allowed.every((o) => o.decision !== \"deny\" && o.receipts.length > 0),\n safe_deny: observationOnly ? \"not_applicable\" : denied.length > 0 && denied.every((o) => o.decision === \"deny\"),\n signature: all.length > 0 && all.every((o) => o.valid),\n grouping: all.length > 0 && all.every((o) => o.grouped),\n cloud_ack: \"not_checked\", observation_only: observationOnly, proof_digests: proofDigests,\n ...(vectors.some((v) => v.typed) ? { typed_operation: all.length > 0 && all.every((o) => o.typed !== false) } : {}),\n } };\n } finally {\n try { runtime?.close(); } catch { /* the temp home is removed next */ }\n try { rmSync(dir, { recursive: true, force: true, maxRetries: 5 }); } catch { /* a handle still open on Windows; the OS temp dir is cleaned later */ }\n }\n}\n\n/** Run every supported cell's fixtures once per adapter/action/phase, and record the\n * result under each host variant's key. A fixture exercises the adapter, so it cannot\n * tell Codex desktop from Codex CLI: both keys get the same fixture proof, and neither\n * becomes verified (see `cellState`). */\nexport async function runProofFixtures(adapterVersion: string, options: ProofRunOptions = {}): Promise<Record<string, ProofRecord>> {\n const cells = computeManifest({ adapterVersion, configured: { claude: true, codex: true, cursor: true } }).cells;\n const records: Record<string, ProofRecord> = {};\n const cache = new Map<string, Omit<ProofRecord, \"cell\"> | null>();\n for (const cell of cells) {\n if (cell.state === \"unsupported\") continue;\n const spec = specForCell(cell);\n if (!spec) continue;\n const id = `${spec.adapter}/${spec.action_type}/${spec.phase}`;\n if (!cache.has(id)) {\n const run = await proveCell(spec.adapter, spec.action_type, spec.phase, cell.observation_only, adapterVersion, options);\n if (run) options.collect?.push(...run.receipts);\n cache.set(id, run?.proof ?? null);\n }\n const proof = cache.get(id);\n if (proof) records[cell.key] = { cell: cell.key, ...proof };\n }\n return records;\n}\n\nexport function loadProofs(configDir: string): Record<string, ProofRecord> {\n const file = join(configDir, PROOF_FILE);\n if (!existsSync(file)) return {};\n try {\n const parsed = JSON.parse(readFileSync(file, \"utf8\")) as { proofs?: Record<string, ProofRecord> };\n return parsed.proofs && typeof parsed.proofs === \"object\" ? parsed.proofs : {};\n } catch { return {}; }\n}\n\nexport function saveProofs(configDir: string, proofs: Record<string, ProofRecord>): string {\n const file = join(configDir, PROOF_FILE);\n writeFileSync(file, `${JSON.stringify({ version: 1, proofs }, null, 2)}\\n`);\n return file;\n}\n\n/** Whether a cell's fixture proof passed, for a summary line. */\nexport const proofPassed = (proof: ProofRecord, cell: CapabilityCell): boolean =>\n proof.safe_allow && (cell.observation_only ? proof.safe_deny === \"not_applicable\" : proof.safe_deny === true) && proof.signature && proof.grouping;\n\n/** Deliver fixture receipts to the workspace's receipt route and report whether every one was\n * accepted. A proof names these receipts by digest, so it is only worth sending after they\n * landed; anything short of full delivery leaves the proof unsent, never half-claimed. */\nexport async function deliverProofReceipts(connection: HookConnection, receipts: unknown[], options: { fetch?: typeof fetch; timeoutMs?: number } = {}): Promise<boolean> {\n if (receipts.length === 0) return true;\n const exporter = createCloudExporter({\n url: connection.url, credential: connection.credential, outbox: createMemoryCloudOutbox({ maxPending: Math.max(1000, receipts.length) }),\n fetch: options.fetch, flushMs: 60_000,\n });\n try {\n for (const receipt of receipts) exporter.enqueue(receipt as SignedReceipt);\n const deadline = Date.now() + (options.timeoutMs ?? 15_000);\n while (exporter.pending() > 0 && Date.now() < deadline) {\n await exporter.flush();\n if (exporter.pending() > 0) {\n if (exporter.status().consecutiveFailures > 0) return false;\n await new Promise((resolve) => setTimeout(resolve, 25));\n }\n }\n return exporter.pending() === 0;\n } finally { exporter.stop(); }\n}\n"]}
package/dist/rules.d.ts CHANGED
@@ -28,6 +28,16 @@ export interface RuleSet {
28
28
  protected_read: PathRule[];
29
29
  /** Action types recorded but not blocked. */
30
30
  observe: string[];
31
+ /** Optional workspace scope. When set, a write whose physical target (symlinks and
32
+ * junctions followed, rename and link destinations included) is outside these roots,
33
+ * or cannot be resolved, is denied. `.` means the project directory. Absent = not
34
+ * enforced, which is the default. */
35
+ allowed_roots?: string[];
36
+ /** Optional. When true, a shell command that runs SQL against a remote database (a Wrangler
37
+ * D1 command without --local, or psql to a host other than this machine) is denied before it
38
+ * runs if the SQL drops a table, deletes or updates every row, drops or renames in an ALTER, or
39
+ * cannot be read. Absent = not enforced, which is the default. */
40
+ protect_remote_database?: boolean;
31
41
  }
32
42
  export declare const RULES_FILE = "rules.json";
33
43
  /** A protected-path rule from a plain path the user typed. A trailing `/` (or a path with
@@ -46,6 +56,16 @@ export declare function defaultRules(): RuleSet;
46
56
  export declare const rulesPath: (dir: string) => string;
47
57
  export declare function loadRules(configDir: string): RuleSet | null;
48
58
  export declare function saveRules(configDir: string, rules: RuleSet): string;
59
+ /** Whether `path` is inside the location a protected-path rule names. This is the same
60
+ * pattern the compiled policy evaluates (the rule allows a path when the pattern's
61
+ * negative lookahead matches), tested rule by rule so a classification can say which
62
+ * kind of location it was. */
63
+ export declare function pathRuleMatches(rule: PathRule, path: string): boolean;
64
+ /** Whether a shell program is on the destructive list, folded the way the compiled
65
+ * policy folds it: case-insensitive, without an executable suffix. */
66
+ export declare function isDestructiveProgram(rules: RuleSet, program: string): boolean;
67
+ /** Whether a push destination is one of the protected refs (`release/*` is a prefix). */
68
+ export declare function isProtectedBranch(rules: RuleSet, ref: string): boolean;
49
69
  /** Plain English, for `scopebond-hook rules`. */
50
70
  export declare function describeRules(rules: RuleSet): string;
51
71
  //# sourceMappingURL=rules.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAsBA;;6EAE6E;AAC7E,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,CAAC,CAAC;IACX,8EAA8E;IAC9E,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,2EAA2E;IAC3E,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,eAAe,EAAE,QAAQ,EAAE,CAAC;IAC5B,cAAc,EAAE,QAAQ,EAAE,CAAC;IAC3B,6CAA6C;IAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,eAAO,MAAM,UAAU,eAAe,CAAC;AAiBvC;;;;;;oCAMoC;AACpC,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAenD;AA6BD,gEAAgE;AAChE,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgCjF;AAED;wDACwD;AACxD,wBAAgB,YAAY,IAAI,OAAO,CA0DtC;AAED,eAAO,MAAM,SAAS,GAAI,KAAK,MAAM,KAAG,MAA+B,CAAC;AAExE,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAQ3D;AAED,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAInE;AAED,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAiBpD"}
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAsBA;;6EAE6E;AAC7E,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,CAAC,CAAC;IACX,8EAA8E;IAC9E,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,2EAA2E;IAC3E,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,eAAe,EAAE,QAAQ,EAAE,CAAC;IAC5B,cAAc,EAAE,QAAQ,EAAE,CAAC;IAC3B,6CAA6C;IAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;0CAGsC;IACtC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;;;uEAGmE;IACnE,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAED,eAAO,MAAM,UAAU,eAAe,CAAC;AAiBvC;;;;;;oCAMoC;AACpC,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAenD;AA6BD,gEAAgE;AAChE,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0CjF;AAED;wDACwD;AACxD,wBAAgB,YAAY,IAAI,OAAO,CAmEtC;AAED,eAAO,MAAM,SAAS,GAAI,KAAK,MAAM,KAAG,MAA+B,CAAC;AAExE,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAQ3D;AAED,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAInE;AAED;;;+BAG+B;AAC/B,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAGrE;AAED;uEACuE;AACvE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAG7E;AAED,yFAAyF;AACzF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAMtE;AAED,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAyBpD"}
package/dist/rules.js CHANGED
@@ -107,6 +107,16 @@ export function compile(rules, agentKid) {
107
107
  action_types: rules.observe,
108
108
  description: `Observe ${sentence(rules.observe)} — recorded, not blocked. Add bounds to enforce. Change this list in .scopebond/${RULES_FILE}.`,
109
109
  },
110
+ ...(rules.allowed_roots?.length ? [{
111
+ id: "protect-root", type: "action_allowlist", mode: "enforce", action_types: ["file.write"],
112
+ param_bounds: { root_scope: { pattern: "^inside$" } },
113
+ description: `Deny writes whose physical target is outside ${sentence(rules.allowed_roots)} — symlinks, junctions, rename and link destinations followed — and writes whose target cannot be resolved. Change these in .scopebond/${RULES_FILE}.`,
114
+ }] : []),
115
+ ...(rules.protect_remote_database ? [{
116
+ id: "protect-remote-database", type: "action_allowlist", mode: "enforce", action_types: ["db.exec"],
117
+ param_bounds: { risk: { pattern: "^ordinary$" } },
118
+ description: `Deny shell commands that run SQL against a remote database (a wrangler d1 command without --local, or psql to a host other than this machine) when the SQL drops a table, deletes or updates every row, drops or renames inside an ALTER, or cannot be read. The hook cannot tell which database is production, so every remote database is covered. Change this in .scopebond/${RULES_FILE}.`,
119
+ }] : []),
110
120
  { id: "keys", type: "key_policy", active_keys: [agentKid], description: "Only the enrolled machine key may sign." },
111
121
  ],
112
122
  };
@@ -130,6 +140,8 @@ export function defaultRules() {
130
140
  { kind: "under", value: "\\.git/hooks", label: "git hooks" },
131
141
  { kind: "named", value: "\\.git/config", label: "git config" },
132
142
  { kind: "under", value: "\\.husky", label: "Husky hooks" },
143
+ { kind: "named", value: "\\.cursor/mcp\\.json", label: "Cursor MCP config" },
144
+ { kind: "under", value: "\\.githooks", label: "git hooks (.githooks)" },
133
145
  { kind: "under", value: "\\.github/workflows", label: "GitHub workflows" },
134
146
  { kind: "under", value: "\\.github/actions", label: "GitHub actions" },
135
147
  { kind: "named", value: "\\.gitlab-ci\\.yml", label: ".gitlab-ci.yml" },
@@ -137,6 +149,13 @@ export function defaultRules() {
137
149
  { kind: "under", value: "\\.circleci", label: "CircleCI config" },
138
150
  { kind: "named", value: "azure-pipelines\\.yml", label: "azure-pipelines.yml" },
139
151
  { kind: "named", value: "Jenkinsfile", label: "Jenkinsfile" },
152
+ { kind: "named", value: "azure-pipelines\\.yaml", label: "azure-pipelines.yaml" },
153
+ { kind: "named", value: "bitbucket-pipelines\\.yml", label: "bitbucket-pipelines.yml" },
154
+ { kind: "named", value: "\\.travis\\.yml", label: ".travis.yml" },
155
+ { kind: "named", value: "\\.drone\\.yml", label: ".drone.yml" },
156
+ { kind: "named", value: "cloudbuild\\.yaml", label: "cloudbuild.yaml" },
157
+ { kind: "named", value: "cloudbuild\\.yml", label: "cloudbuild.yml" },
158
+ { kind: "under", value: "\\.buildkite", label: "Buildkite pipelines" },
140
159
  ],
141
160
  protected_read: [
142
161
  { kind: "under", value: "\\.scopebond", label: "the hook's own policy and keys (.scopebond)" },
@@ -192,6 +211,29 @@ export function saveRules(configDir, rules) {
192
211
  writeFileSync(file, `${JSON.stringify(rules, null, 2)}\n`);
193
212
  return file;
194
213
  }
214
+ /** Whether `path` is inside the location a protected-path rule names. This is the same
215
+ * pattern the compiled policy evaluates (the rule allows a path when the pattern's
216
+ * negative lookahead matches), tested rule by rule so a classification can say which
217
+ * kind of location it was. */
218
+ export function pathRuleMatches(rule, path) {
219
+ if (path === "")
220
+ return false;
221
+ return !new RegExp(`^${pathPattern(rule)}.+`).test(path);
222
+ }
223
+ /** Whether a shell program is on the destructive list, folded the way the compiled
224
+ * policy folds it: case-insensitive, without an executable suffix. */
225
+ export function isDestructiveProgram(rules, program) {
226
+ const name = program.toLowerCase().replace(/\.(?:exe|cmd|bat|com|ps1)$/, "");
227
+ return name !== "" && rules.destructive_programs.some((p) => p.toLowerCase() === name);
228
+ }
229
+ /** Whether a push destination is one of the protected refs (`release/*` is a prefix). */
230
+ export function isProtectedBranch(rules, ref) {
231
+ const name = ref.toLowerCase();
232
+ return rules.protected_branches.some((b) => {
233
+ const p = b.toLowerCase();
234
+ return p.endsWith("/*") ? name.startsWith(p.slice(0, -1)) : name === p;
235
+ });
236
+ }
195
237
  /** Plain English, for `scopebond-hook rules`. */
196
238
  export function describeRules(rules) {
197
239
  const lines = [];
@@ -209,6 +251,14 @@ export function describeRules(rules) {
209
251
  for (const rule of rules.protected_read)
210
252
  lines.push(` ${rule.label}`);
211
253
  lines.push("");
254
+ if (rules.protect_remote_database) {
255
+ lines.push(` remote SQL that drops a table, deletes or updates every row, or cannot be read`);
256
+ lines.push("");
257
+ }
258
+ if (rules.allowed_roots?.length) {
259
+ lines.push(` writes outside ${rules.allowed_roots.join(", ")} (physical target; unresolved targets too)`);
260
+ lines.push("");
261
+ }
212
262
  lines.push("Recorded, not blocked:");
213
263
  lines.push(` ${rules.observe.join(", ")}`);
214
264
  return lines.join("\n");
package/dist/rules.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"rules.js","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,mFAAmF;AACnF,0FAA0F;AAC1F,yFAAyF;AACzF,wFAAwF;AACxF,QAAQ;AACR,EAAE;AACF,qFAAqF;AACrF,gFAAgF;AAChF,sFAAsF;AACtF,qFAAqF;AACrF,iBAAiB;AACjB,EAAE;AACF,qFAAqF;AACrF,mFAAmF;AACnF,kCAAkC;AAElC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAuBlE,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAC;AAEvC,MAAM,WAAW,GAAG,CAAC,IAAc,EAAU,EAAE,CAC7C,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;IACvC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;QAC3C,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC;YACvC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;AAEnB;;;;;;2CAM2C;AAC3C,MAAM,WAAW,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAC;AAE3F;;;;;;oCAMoC;AACpC,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,MAAM,kBAAkB,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzD,MAAM,eAAe,GAAG,kBAAkB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC;IAC/G,gFAAgF;IAChF,IAAI,KAAK,GAAG,eAAe,CAAC;IAC5B,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvD,qFAAqF;IACrF,yFAAyF;IACzF,+DAA+D;IAC/D,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACxE,OAAO,WAAW;QAChB,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,KAAK,wBAAwB,EAAE;QACvF,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AACjE,CAAC;AAED;2EAC2E;AAC3E,SAAS,aAAa,CAAC,QAAkB;IACvC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACrF,MAAM,KAAK,GAAG;QACZ,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACzD,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QACtC,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK;KAC3B,CAAC;IACF,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;AAChC,CAAC;AAED,MAAM,cAAc,GAAG,CAAC,QAAkB,EAAU,EAAE,CACpD,UAAU,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,4BAA4B,CAAC,QAAQ,CAAC;AAEtF,MAAM,YAAY,GAAG,CAAC,KAAiB,EAAU,EAAE,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;AAE5F;;2DAE2D;AAC3D,SAAS,QAAQ,CAAC,KAAe,EAAE,KAAK,GAAG,EAAE;IAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IACzC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,OAAO,CAAC,KAAc,EAAE,QAAgB;IACtD,OAAO;QACL,kBAAkB,EAAE,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;QAChE,OAAO,EAAE;YACP;gBACE,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,UAAU,CAAC;gBAC7F,YAAY,EAAE,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,kBAAkB,CAAC,EAAE,EAAE;gBAC3E,WAAW,EAAE,kBAAkB,QAAQ,CAAC,KAAK,CAAC,kBAAkB,CAAC,0QAA0Q,UAAU,GAAG;aACzV;YACD;gBACE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,YAAY,CAAC;gBACzF,YAAY,EAAE,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,EAAE;gBAClF,WAAW,EAAE,8BAA8B,QAAQ,CAAC,KAAK,CAAC,oBAAoB,EAAE,EAAE,CAAC,4TAA4T,UAAU,GAAG;aAC7Z;YACD;gBACE,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,YAAY,CAAC;gBAC5F,YAAY,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,EAAE;gBACxE,WAAW,EAAE,wCAAwC,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,sDAAsD,UAAU,GAAG;aAC5K;YACD;gBACE,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,WAAW,CAAC;gBAC1F,YAAY,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,EAAE;gBACvE,WAAW,EAAE,oCAAoC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,sDAAsD,UAAU,GAAG;aACvK;YACD;gBACE,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,SAAS;gBAChE,YAAY,EAAE,KAAK,CAAC,OAAO;gBAC3B,WAAW,EAAE,WAAW,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,mFAAmF,UAAU,GAAG;aAChJ;YACD,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,yCAAyC,EAAE;SACpH;KACF,CAAC;AACJ,CAAC;AAED;wDACwD;AACxD,MAAM,UAAU,YAAY;IAC1B,OAAO;QACL,OAAO,EAAE,CAAC;QACV,kBAAkB,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC;QACnD,oBAAoB,EAAE,CAAC,GAAG,WAAW,CAAC;QACtC,eAAe,EAAE;YACf,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,6CAA6C,EAAE;YAC9F,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,oBAAoB,CAAC,GAAG,EAAE,KAAK,EAAE,sBAAsB,EAAE;YAClG,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,mBAAmB,EAAE;YACvE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,oBAAoB,EAAE;YACzE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,iBAAiB,CAAC,GAAG,EAAE,KAAK,EAAE,sBAAsB,EAAE;YAC/F,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,uBAAuB,EAAE,KAAK,EAAE,qBAAqB,EAAE;YAC/E,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,wBAAwB,EAAE,KAAK,EAAE,cAAc,EAAE;YACzE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,EAAE;YAC7D,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,WAAW,EAAE;YAC5D,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,YAAY,EAAE;YAC9D,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,EAAE;YAC1D,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,qBAAqB,EAAE,KAAK,EAAE,kBAAkB,EAAE;YAC1E,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,gBAAgB,EAAE;YACtE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB,EAAE,KAAK,EAAE,gBAAgB,EAAE;YACvE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,qBAAqB,EAAE,KAAK,EAAE,iBAAiB,EAAE;YACzE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,iBAAiB,EAAE;YACjE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,uBAAuB,EAAE,KAAK,EAAE,qBAAqB,EAAE;YAC/E,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE;SAC9D;QACD,cAAc,EAAE;YACd,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,6CAA6C,EAAE;YAC9F,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,qCAAqC,CAAC,IAAI,EAAE,KAAK,EAAE,qEAAqE,EAAE;YAC7J;gBACE,IAAI,EAAE,KAAK;gBACX,OAAO,EAAE,cAAc,EAAE,CAAC,QAAQ,CAAC,yBAAyB,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,oBAAoB;gBAC7I,KAAK,EAAE,mFAAmF;aAC3F;YACD,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE;YACrD;gBACE,IAAI,EAAE,KAAK;gBACX,OAAO,EAAE,cAAc,EAAE,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,MAAM;gBAChH,KAAK,EAAE,2FAA2F;aACnG;YACD,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,4CAA4C,EAAE;YACvI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE;YACrD,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE;YACvD,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE;YACrD,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;YACnD,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB,EAAE,KAAK,EAAE,kBAAkB,EAAE;YACzE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,qBAAqB,EAAE;YAC/D,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,cAAc,EAAE;YACjE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,uBAAuB,EAAE;YACnE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,yBAAyB,EAAE,KAAK,EAAE,qBAAqB,EAAE;YACjF,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,oBAAoB,EAAE;YACzE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,mBAAmB,EAAE;YAChE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE;YACzD,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,iCAAiC,EAAE;YAChF,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,0BAA0B,EAAE,KAAK,EAAE,4BAA4B,EAAE;YACzF,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,iCAAiC,EAAE,KAAK,EAAE,iCAAiC,EAAE;SACtG;QACD,OAAO,EAAE,CAAC,WAAW,EAAE,eAAe,CAAC;KACxC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,GAAW,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;AAExE,MAAM,UAAU,SAAS,CAAC,SAAiB;IACzC,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAY,CAAC;QACjE,IAAI,MAAM,EAAE,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC;YAAE,OAAO,IAAI,CAAC;QACpF,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,SAAiB,EAAE,KAAc;IACzD,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC3D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IACtC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,wBAAwB,KAAK,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1E,KAAK,CAAC,IAAI,CAAC,oEAAoE,CAAC,CAAC;IACjF,KAAK,CAAC,IAAI,CAAC,wBAAwB,KAAK,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5E,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,wBAAwB,KAAK,CAAC,eAAe,CAAC,MAAM,yBAAyB,CAAC,CAAC;IAC1F,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,eAAe;QAAE,KAAK,CAAC,IAAI,CAAC,0BAA0B,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAC7F,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,wBAAwB,KAAK,CAAC,cAAc,CAAC,MAAM,yBAAyB,CAAC,CAAC;IACzF,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,cAAc;QAAE,KAAK,CAAC,IAAI,CAAC,0BAA0B,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IAC5F,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;IACrC,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5C,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC","sourcesContent":["// Rules you can actually read and change.\n//\n// The site says \"set rules in plain terms\" and \"it is a plain JSON file — edit the\n// limits\". What `init` wrote was 6.7 KB of generated regular expression: the `safe-shell`\n// clause alone is a ~700-character case-folded negative lookahead. Nobody edits that, so\n// in practice the starter policy was the *only* policy and \"set your own rules\" was not\n// true.\n//\n// The fix is not to weaken the patterns — they are careful, and the canonicalization\n// tests exist because bypasses are subtle. It is to stop making the pattern the\n// interface. The lists the patterns are built from live in `.scopebond/rules.json`, a\n// short readable file, and `policy.json` is compiled from it. The regex stays as the\n// compiled form.\n//\n// `compile()` reproduces the shipped starter policy's enforcement patterns exactly —\n// `rules.test.mjs` asserts that against `starterPolicy()`, so a change here cannot\n// quietly alter what is enforced.\n\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { ci, under, named, dir, DESTRUCTIVE } from \"./runtime.js\";\n\n/** One protected location. `under`/`named`/`dir` are the readable, editable shapes;\n * `raw` carries the few patterns with bespoke exceptions (`.env` templates are allowed,\n * `.ssh/config` and `*.pub` are allowed) that no simple shape expresses. */\nexport type PathRule =\n | { kind: \"under\"; value: string; label: string }\n | { kind: \"named\"; value: string; label: string }\n | { kind: \"dir\"; value: string; label: string }\n | { kind: \"raw\"; pattern: string; label: string };\n\nexport interface RuleSet {\n version: 1;\n /** Refs an agent may not push to. `release/*` means the prefix `release/`. */\n protected_branches: string[];\n /** Programs an agent may not run, in any case and with or without .exe. */\n destructive_programs: string[];\n protected_write: PathRule[];\n protected_read: PathRule[];\n /** Action types recorded but not blocked. */\n observe: string[];\n}\n\nexport const RULES_FILE = \"rules.json\";\n\nconst pathPattern = (rule: PathRule): string =>\n rule.kind === \"under\" ? under(rule.value)\n : rule.kind === \"named\" ? named(rule.value)\n : rule.kind === \"dir\" ? dir(rule.value)\n : rule.pattern;\n\n/** Escape every regular-expression metacharacter, backslash included.\n *\n * A path the user typed is literal text, and this value goes straight into a rule that\n * decides what the agent may touch: if one metacharacter escapes escaping, the rule\n * silently protects something other than what they asked for. The class is therefore\n * complete on its own rather than relying on an earlier step having removed backslashes\n * (CodeQL js/incomplete-sanitization). */\nconst escapeRegex = (text: string): string => text.replace(/[\\\\^$.*+?()[\\]{}|/]/g, \"\\\\$&\");\n\n/** A protected-path rule from a plain path the user typed. A trailing `/` (or a path with\n * no dot in its last segment) reads as a directory; anything else as a file name.\n *\n * Normalisation is one obvious step at a time. The previous version folded separator\n * conversion and leading-`./` stripping into a single `^[./\\\\]+` replace with a callback,\n * which was both hard to read and quadratic on a path of many leading dots\n * (CodeQL js/polynomial-redos). */\nexport function pathRuleFor(input: string): PathRule {\n const trimmed = input.trim();\n const withForwardSlashes = trimmed.split(\"\\\\\").join(\"/\");\n const withoutDotSlash = withForwardSlashes.startsWith(\"./\") ? withForwardSlashes.slice(2) : withForwardSlashes;\n // Drop trailing separators without a quantifier, so the cost is plainly linear.\n let clean = withoutDotSlash;\n while (clean.endsWith(\"/\")) clean = clean.slice(0, -1);\n // A trailing slash is an explicit directory; so is a last segment with no extension.\n // Checked by string, not by `/\\.[^/]*$/`: that pattern is unanchored at the front, so it\n // retries at every position and goes quadratic on a long path.\n const lastSegment = clean.slice(clean.lastIndexOf(\"/\") + 1);\n const isDirectory = trimmed.endsWith(\"/\") || !lastSegment.includes(\".\");\n return isDirectory\n ? { kind: \"under\", value: escapeRegex(clean), label: `${clean}/ and everything in it` }\n : { kind: \"named\", value: escapeRegex(clean), label: clean };\n}\n\n/** The branch pattern: deny the listed refs, plus the \"every branch at once\" flags and a\n * push whose destination could not be read. `--tags` alone is allowed. */\nfunction branchPattern(branches: string[]): string {\n const exact = branches.filter((b) => !b.endsWith(\"/*\"));\n const prefixes = branches.filter((b) => b.endsWith(\"/*\")).map((b) => b.slice(0, -1));\n const parts = [\n exact.length ? `(?!(?:${exact.map(ci).join(\"|\")})$)` : \"\",\n ...prefixes.map((p) => `(?!${ci(p)})`),\n `(?!-(?!-${ci(\"tags\")}$))`,\n ];\n return `^${parts.join(\"\")}.+`;\n}\n\nconst programPattern = (programs: string[]): string =>\n `^(?!(?:${programs.map(ci).join(\"|\")})(?:${ci(\"\\\\.(?:exe|cmd|bat|com|ps1)\")})?$).+`;\n\nconst pathsPattern = (rules: PathRule[]): string => `^${rules.map(pathPattern).join(\"\")}.+`;\n\n/** A sentence listing what a clause covers, generated from the list rather than written by\n * hand — so it stays true after an edit. The block message quotes this, so a stale\n * description would be a lie told at the worst moment. */\nfunction sentence(items: string[], limit = 12): string {\n const shown = items.slice(0, limit);\n const rest = items.length - shown.length;\n return shown.join(\", \") + (rest > 0 ? `, and ${rest} more` : \"\");\n}\n\n/** Compile a rule set into the policy the gateway evaluates. */\nexport function compile(rules: RuleSet, agentKid: string): Record<string, unknown> {\n return {\n vocabulary_version: \"1.0\", policy_id: \"coding-agent\", version: 1,\n clauses: [\n {\n id: \"protect-branches\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"git.push\"],\n param_bounds: { ref: { pattern: branchPattern(rules.protected_branches) } },\n description: `Deny pushes to ${sentence(rules.protected_branches)} (any case, any refspec spelling), pushes of every branch at once (--all, --mirror) and pushes whose destination cannot be read from the command (a git alias, a configured push refspec, send-pack). A tags-only push (--tags) is allowed. Change these in .scopebond/${RULES_FILE}.`,\n },\n {\n id: \"safe-shell\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"shell.exec\"],\n param_bounds: { program: { pattern: programPattern(rules.destructive_programs) } },\n description: `Deny destructive programs (${sentence(rules.destructive_programs, 10)}) in any case and with or without .exe. An empty program — a command that could not be parsed, or whose program is only known at run time ($VAR, $(…), eval of a variable) — is denied. Argument-shaped deletion (find -delete, git clean) is not a program name and is not covered here. Change this list in .scopebond/${RULES_FILE}.`,\n },\n {\n id: \"protect-write\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.write\"],\n param_bounds: { path: { pattern: pathsPattern(rules.protected_write) } },\n description: `Allow workspace writes, but never to ${sentence(rules.protected_write.map((r) => r.label))}. Case-insensitive. Change this list in .scopebond/${RULES_FILE}.`,\n },\n {\n id: \"protect-read\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.read\"],\n param_bounds: { path: { pattern: pathsPattern(rules.protected_read) } },\n description: `Allow workspace reads, but never ${sentence(rules.protected_read.map((r) => r.label))}. Case-insensitive. Change this list in .scopebond/${RULES_FILE}.`,\n },\n {\n id: \"observe-net-mcp\", type: \"action_allowlist\", mode: \"monitor\",\n action_types: rules.observe,\n description: `Observe ${sentence(rules.observe)} — recorded, not blocked. Add bounds to enforce. Change this list in .scopebond/${RULES_FILE}.`,\n },\n { id: \"keys\", type: \"key_policy\", active_keys: [agentKid], description: \"Only the enrolled machine key may sign.\" },\n ],\n };\n}\n\n/** The shipped defaults. Every entry here reproduces one piece of the starter policy's\n * patterns; `rules.test.mjs` pins that equivalence. */\nexport function defaultRules(): RuleSet {\n return {\n version: 1,\n protected_branches: [\"main\", \"master\", \"release/*\"],\n destructive_programs: [...DESTRUCTIVE],\n protected_write: [\n { kind: \"under\", value: \"\\\\.scopebond\", label: \"the hook's own policy and keys (.scopebond)\" },\n { kind: \"raw\", pattern: `(?!(?:.*/)?${ci(\"\\\\.claude/settings\")})`, label: \"Claude Code settings\" },\n { kind: \"under\", value: \"\\\\.claude/hooks\", label: \"Claude Code hooks\" },\n { kind: \"under\", value: \"\\\\.claude/agents\", label: \"Claude Code agents\" },\n { kind: \"raw\", pattern: `(?!(?:.*/)?${ci(\"\\\\.cursor/hooks\")})`, label: \"Cursor hook settings\" },\n { kind: \"named\", value: \"\\\\.codex/hooks\\\\.json\", label: \"Codex hook settings\" },\n { kind: \"named\", value: \"\\\\.codex/config\\\\.toml\", label: \"Codex config\" },\n { kind: \"named\", value: \"\\\\.mcp\\\\.json\", label: \".mcp.json\" },\n { kind: \"under\", value: \"\\\\.git/hooks\", label: \"git hooks\" },\n { kind: \"named\", value: \"\\\\.git/config\", label: \"git config\" },\n { kind: \"under\", value: \"\\\\.husky\", label: \"Husky hooks\" },\n { kind: \"under\", value: \"\\\\.github/workflows\", label: \"GitHub workflows\" },\n { kind: \"under\", value: \"\\\\.github/actions\", label: \"GitHub actions\" },\n { kind: \"named\", value: \"\\\\.gitlab-ci\\\\.yml\", label: \".gitlab-ci.yml\" },\n { kind: \"named\", value: \"\\\\.gitlab-ci\\\\.yaml\", label: \".gitlab-ci.yaml\" },\n { kind: \"under\", value: \"\\\\.circleci\", label: \"CircleCI config\" },\n { kind: \"named\", value: \"azure-pipelines\\\\.yml\", label: \"azure-pipelines.yml\" },\n { kind: \"named\", value: \"Jenkinsfile\", label: \"Jenkinsfile\" },\n ],\n protected_read: [\n { kind: \"under\", value: \"\\\\.scopebond\", label: \"the hook's own policy and keys (.scopebond)\" },\n { kind: \"raw\", pattern: `(?!.*${ci(\"\\\\.(?:key|pem|p12|pfx|jks|keystore)\")}$)`, label: \"signing keys and key containers (*.key, *.pem, *.p12, *.pfx, *.jks)\" },\n {\n kind: \"raw\",\n pattern: `(?!(?:.*/)?${ci(\"\\\\.env\")}(?!(?:\\\\.[^/]*)?\\\\.(?:${ci(\"example\")}|${ci(\"sample\")}|${ci(\"template\")}|${ci(\"dist\")})$)(?:\\\\.[^/]*)?$)`,\n label: \"environment secret files (.env, .env.* — except .example/.sample/.template/.dist)\",\n },\n { kind: \"named\", value: \"\\\\.envrc\", label: \".envrc\" },\n {\n kind: \"raw\",\n pattern: `(?!(?:.*/)?${ci(\"\\\\.ssh\")}(?:$|/(?!.*${ci(\"\\\\.pub\")}$)(?!${ci(\"known_hosts\")}$)(?!${ci(\"config\")}$)))`,\n label: \"SSH private keys and the .ssh directory (public keys, known_hosts and config are allowed)\",\n },\n { kind: \"raw\", pattern: `(?!(?:.*/)?${ci(\"\\\\.aws\")}(?:$|/(?!${ci(\"config\")}$)))`, label: \"AWS credentials (.aws, except .aws/config)\" },\n { kind: \"named\", value: \"\\\\.npmrc\", label: \".npmrc\" },\n { kind: \"named\", value: \"\\\\.pypirc\", label: \".pypirc\" },\n { kind: \"named\", value: \"\\\\.netrc\", label: \".netrc\" },\n { kind: \"named\", value: \"_netrc\", label: \"_netrc\" },\n { kind: \"named\", value: \"\\\\.git-credentials\", label: \".git-credentials\" },\n { kind: \"dir\", value: \"\\\\.kube\", label: \"the .kube directory\" },\n { kind: \"named\", value: \"\\\\.kube/config\", label: \".kube/config\" },\n { kind: \"dir\", value: \"\\\\.docker\", label: \"the .docker directory\" },\n { kind: \"named\", value: \"\\\\.docker/config\\\\.json\", label: \".docker/config.json\" },\n { kind: \"under\", value: \"\\\\.config/gcloud\", label: \"gcloud credentials\" },\n { kind: \"under\", value: \"\\\\.azure\", label: \"Azure credentials\" },\n { kind: \"under\", value: \"\\\\.gnupg\", label: \"GnuPG keys\" },\n { kind: \"dir\", value: \"\\\\.config/gh\", label: \"the GitHub CLI config directory\" },\n { kind: \"named\", value: \"\\\\.config/gh/hosts\\\\.yml\", label: \"the GitHub CLI's hosts.yml\" },\n { kind: \"named\", value: \"\\\\.claude/\\\\.credentials\\\\.json\", label: \"Claude Code's .credentials.json\" },\n ],\n observe: [\"net.fetch\", \"mcp.tool.call\"],\n };\n}\n\nexport const rulesPath = (dir: string): string => join(dir, RULES_FILE);\n\nexport function loadRules(configDir: string): RuleSet | null {\n const file = rulesPath(configDir);\n if (!existsSync(file)) return null;\n try {\n const parsed = JSON.parse(readFileSync(file, \"utf8\")) as RuleSet;\n if (parsed?.version !== 1 || !Array.isArray(parsed.protected_branches)) return null;\n return parsed;\n } catch { return null; }\n}\n\nexport function saveRules(configDir: string, rules: RuleSet): string {\n const file = rulesPath(configDir);\n writeFileSync(file, `${JSON.stringify(rules, null, 2)}\\n`);\n return file;\n}\n\n/** Plain English, for `scopebond-hook rules`. */\nexport function describeRules(rules: RuleSet): string {\n const lines: string[] = [];\n lines.push(\"Blocked before it runs:\");\n lines.push(\"\");\n lines.push(` pushes to ${rules.protected_branches.join(\", \")}`);\n lines.push(` and any push whose destination cannot be read`);\n lines.push(` programs ${rules.destructive_programs.join(\", \")}`);\n lines.push(\"\");\n lines.push(` writes to ${rules.protected_write.length} protected location(s):`);\n for (const rule of rules.protected_write) lines.push(` ${rule.label}`);\n lines.push(\"\");\n lines.push(` reads of ${rules.protected_read.length} protected location(s):`);\n for (const rule of rules.protected_read) lines.push(` ${rule.label}`);\n lines.push(\"\");\n lines.push(\"Recorded, not blocked:\");\n lines.push(` ${rules.observe.join(\", \")}`);\n return lines.join(\"\\n\");\n}\n"]}
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"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","sourcesContent":["// Rules you can actually read and change.\n//\n// The site says \"set rules in plain terms\" and \"it is a plain JSON file — edit the\n// limits\". What `init` wrote was 6.7 KB of generated regular expression: the `safe-shell`\n// clause alone is a ~700-character case-folded negative lookahead. Nobody edits that, so\n// in practice the starter policy was the *only* policy and \"set your own rules\" was not\n// true.\n//\n// The fix is not to weaken the patterns — they are careful, and the canonicalization\n// tests exist because bypasses are subtle. It is to stop making the pattern the\n// interface. The lists the patterns are built from live in `.scopebond/rules.json`, a\n// short readable file, and `policy.json` is compiled from it. The regex stays as the\n// compiled form.\n//\n// `compile()` reproduces the shipped starter policy's enforcement patterns exactly —\n// `rules.test.mjs` asserts that against `starterPolicy()`, so a change here cannot\n// quietly alter what is enforced.\n\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { ci, under, named, dir, DESTRUCTIVE } from \"./runtime.js\";\n\n/** One protected location. `under`/`named`/`dir` are the readable, editable shapes;\n * `raw` carries the few patterns with bespoke exceptions (`.env` templates are allowed,\n * `.ssh/config` and `*.pub` are allowed) that no simple shape expresses. */\nexport type PathRule =\n | { kind: \"under\"; value: string; label: string }\n | { kind: \"named\"; value: string; label: string }\n | { kind: \"dir\"; value: string; label: string }\n | { kind: \"raw\"; pattern: string; label: string };\n\nexport interface RuleSet {\n version: 1;\n /** Refs an agent may not push to. `release/*` means the prefix `release/`. */\n protected_branches: string[];\n /** Programs an agent may not run, in any case and with or without .exe. */\n destructive_programs: string[];\n protected_write: PathRule[];\n protected_read: PathRule[];\n /** Action types recorded but not blocked. */\n observe: string[];\n /** Optional workspace scope. When set, a write whose physical target (symlinks and\n * junctions followed, rename and link destinations included) is outside these roots,\n * or cannot be resolved, is denied. `.` means the project directory. Absent = not\n * enforced, which is the default. */\n allowed_roots?: string[];\n /** Optional. When true, a shell command that runs SQL against a remote database (a Wrangler\n * D1 command without --local, or psql to a host other than this machine) is denied before it\n * runs if the SQL drops a table, deletes or updates every row, drops or renames in an ALTER, or\n * cannot be read. Absent = not enforced, which is the default. */\n protect_remote_database?: boolean;\n}\n\nexport const RULES_FILE = \"rules.json\";\n\nconst pathPattern = (rule: PathRule): string =>\n rule.kind === \"under\" ? under(rule.value)\n : rule.kind === \"named\" ? named(rule.value)\n : rule.kind === \"dir\" ? dir(rule.value)\n : rule.pattern;\n\n/** Escape every regular-expression metacharacter, backslash included.\n *\n * A path the user typed is literal text, and this value goes straight into a rule that\n * decides what the agent may touch: if one metacharacter escapes escaping, the rule\n * silently protects something other than what they asked for. The class is therefore\n * complete on its own rather than relying on an earlier step having removed backslashes\n * (CodeQL js/incomplete-sanitization). */\nconst escapeRegex = (text: string): string => text.replace(/[\\\\^$.*+?()[\\]{}|/]/g, \"\\\\$&\");\n\n/** A protected-path rule from a plain path the user typed. A trailing `/` (or a path with\n * no dot in its last segment) reads as a directory; anything else as a file name.\n *\n * Normalisation is one obvious step at a time. The previous version folded separator\n * conversion and leading-`./` stripping into a single `^[./\\\\]+` replace with a callback,\n * which was both hard to read and quadratic on a path of many leading dots\n * (CodeQL js/polynomial-redos). */\nexport function pathRuleFor(input: string): PathRule {\n const trimmed = input.trim();\n const withForwardSlashes = trimmed.split(\"\\\\\").join(\"/\");\n const withoutDotSlash = withForwardSlashes.startsWith(\"./\") ? withForwardSlashes.slice(2) : withForwardSlashes;\n // Drop trailing separators without a quantifier, so the cost is plainly linear.\n let clean = withoutDotSlash;\n while (clean.endsWith(\"/\")) clean = clean.slice(0, -1);\n // A trailing slash is an explicit directory; so is a last segment with no extension.\n // Checked by string, not by `/\\.[^/]*$/`: that pattern is unanchored at the front, so it\n // retries at every position and goes quadratic on a long path.\n const lastSegment = clean.slice(clean.lastIndexOf(\"/\") + 1);\n const isDirectory = trimmed.endsWith(\"/\") || !lastSegment.includes(\".\");\n return isDirectory\n ? { kind: \"under\", value: escapeRegex(clean), label: `${clean}/ and everything in it` }\n : { kind: \"named\", value: escapeRegex(clean), label: clean };\n}\n\n/** The branch pattern: deny the listed refs, plus the \"every branch at once\" flags and a\n * push whose destination could not be read. `--tags` alone is allowed. */\nfunction branchPattern(branches: string[]): string {\n const exact = branches.filter((b) => !b.endsWith(\"/*\"));\n const prefixes = branches.filter((b) => b.endsWith(\"/*\")).map((b) => b.slice(0, -1));\n const parts = [\n exact.length ? `(?!(?:${exact.map(ci).join(\"|\")})$)` : \"\",\n ...prefixes.map((p) => `(?!${ci(p)})`),\n `(?!-(?!-${ci(\"tags\")}$))`,\n ];\n return `^${parts.join(\"\")}.+`;\n}\n\nconst programPattern = (programs: string[]): string =>\n `^(?!(?:${programs.map(ci).join(\"|\")})(?:${ci(\"\\\\.(?:exe|cmd|bat|com|ps1)\")})?$).+`;\n\nconst pathsPattern = (rules: PathRule[]): string => `^${rules.map(pathPattern).join(\"\")}.+`;\n\n/** A sentence listing what a clause covers, generated from the list rather than written by\n * hand — so it stays true after an edit. The block message quotes this, so a stale\n * description would be a lie told at the worst moment. */\nfunction sentence(items: string[], limit = 12): string {\n const shown = items.slice(0, limit);\n const rest = items.length - shown.length;\n return shown.join(\", \") + (rest > 0 ? `, and ${rest} more` : \"\");\n}\n\n/** Compile a rule set into the policy the gateway evaluates. */\nexport function compile(rules: RuleSet, agentKid: string): Record<string, unknown> {\n return {\n vocabulary_version: \"1.0\", policy_id: \"coding-agent\", version: 1,\n clauses: [\n {\n id: \"protect-branches\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"git.push\"],\n param_bounds: { ref: { pattern: branchPattern(rules.protected_branches) } },\n description: `Deny pushes to ${sentence(rules.protected_branches)} (any case, any refspec spelling), pushes of every branch at once (--all, --mirror) and pushes whose destination cannot be read from the command (a git alias, a configured push refspec, send-pack). A tags-only push (--tags) is allowed. Change these in .scopebond/${RULES_FILE}.`,\n },\n {\n id: \"safe-shell\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"shell.exec\"],\n param_bounds: { program: { pattern: programPattern(rules.destructive_programs) } },\n description: `Deny destructive programs (${sentence(rules.destructive_programs, 10)}) in any case and with or without .exe. An empty program — a command that could not be parsed, or whose program is only known at run time ($VAR, $(…), eval of a variable) — is denied. Argument-shaped deletion (find -delete, git clean) is not a program name and is not covered here. Change this list in .scopebond/${RULES_FILE}.`,\n },\n {\n id: \"protect-write\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.write\"],\n param_bounds: { path: { pattern: pathsPattern(rules.protected_write) } },\n description: `Allow workspace writes, but never to ${sentence(rules.protected_write.map((r) => r.label))}. Case-insensitive. Change this list in .scopebond/${RULES_FILE}.`,\n },\n {\n id: \"protect-read\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.read\"],\n param_bounds: { path: { pattern: pathsPattern(rules.protected_read) } },\n description: `Allow workspace reads, but never ${sentence(rules.protected_read.map((r) => r.label))}. Case-insensitive. Change this list in .scopebond/${RULES_FILE}.`,\n },\n {\n id: \"observe-net-mcp\", type: \"action_allowlist\", mode: \"monitor\",\n action_types: rules.observe,\n description: `Observe ${sentence(rules.observe)} — recorded, not blocked. Add bounds to enforce. Change this list in .scopebond/${RULES_FILE}.`,\n },\n ...(rules.allowed_roots?.length ? [{\n id: \"protect-root\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.write\"],\n param_bounds: { root_scope: { pattern: \"^inside$\" } },\n description: `Deny writes whose physical target is outside ${sentence(rules.allowed_roots)} — symlinks, junctions, rename and link destinations followed — and writes whose target cannot be resolved. Change these in .scopebond/${RULES_FILE}.`,\n }] : []),\n ...(rules.protect_remote_database ? [{\n id: \"protect-remote-database\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"db.exec\"],\n param_bounds: { risk: { pattern: \"^ordinary$\" } },\n description: `Deny shell commands that run SQL against a remote database (a wrangler d1 command without --local, or psql to a host other than this machine) when the SQL drops a table, deletes or updates every row, drops or renames inside an ALTER, or cannot be read. The hook cannot tell which database is production, so every remote database is covered. Change this in .scopebond/${RULES_FILE}.`,\n }] : []),\n { id: \"keys\", type: \"key_policy\", active_keys: [agentKid], description: \"Only the enrolled machine key may sign.\" },\n ],\n };\n}\n\n/** The shipped defaults. Every entry here reproduces one piece of the starter policy's\n * patterns; `rules.test.mjs` pins that equivalence. */\nexport function defaultRules(): RuleSet {\n return {\n version: 1,\n protected_branches: [\"main\", \"master\", \"release/*\"],\n destructive_programs: [...DESTRUCTIVE],\n protected_write: [\n { kind: \"under\", value: \"\\\\.scopebond\", label: \"the hook's own policy and keys (.scopebond)\" },\n { kind: \"raw\", pattern: `(?!(?:.*/)?${ci(\"\\\\.claude/settings\")})`, label: \"Claude Code settings\" },\n { kind: \"under\", value: \"\\\\.claude/hooks\", label: \"Claude Code hooks\" },\n { kind: \"under\", value: \"\\\\.claude/agents\", label: \"Claude Code agents\" },\n { kind: \"raw\", pattern: `(?!(?:.*/)?${ci(\"\\\\.cursor/hooks\")})`, label: \"Cursor hook settings\" },\n { kind: \"named\", value: \"\\\\.codex/hooks\\\\.json\", label: \"Codex hook settings\" },\n { kind: \"named\", value: \"\\\\.codex/config\\\\.toml\", label: \"Codex config\" },\n { kind: \"named\", value: \"\\\\.mcp\\\\.json\", label: \".mcp.json\" },\n { kind: \"under\", value: \"\\\\.git/hooks\", label: \"git hooks\" },\n { kind: \"named\", value: \"\\\\.git/config\", label: \"git config\" },\n { kind: \"under\", value: \"\\\\.husky\", label: \"Husky hooks\" },\n { kind: \"named\", value: \"\\\\.cursor/mcp\\\\.json\", label: \"Cursor MCP config\" },\n { kind: \"under\", value: \"\\\\.githooks\", label: \"git hooks (.githooks)\" },\n { kind: \"under\", value: \"\\\\.github/workflows\", label: \"GitHub workflows\" },\n { kind: \"under\", value: \"\\\\.github/actions\", label: \"GitHub actions\" },\n { kind: \"named\", value: \"\\\\.gitlab-ci\\\\.yml\", label: \".gitlab-ci.yml\" },\n { kind: \"named\", value: \"\\\\.gitlab-ci\\\\.yaml\", label: \".gitlab-ci.yaml\" },\n { kind: \"under\", value: \"\\\\.circleci\", label: \"CircleCI config\" },\n { kind: \"named\", value: \"azure-pipelines\\\\.yml\", label: \"azure-pipelines.yml\" },\n { kind: \"named\", value: \"Jenkinsfile\", label: \"Jenkinsfile\" },\n { kind: \"named\", value: \"azure-pipelines\\\\.yaml\", label: \"azure-pipelines.yaml\" },\n { kind: \"named\", value: \"bitbucket-pipelines\\\\.yml\", label: \"bitbucket-pipelines.yml\" },\n { kind: \"named\", value: \"\\\\.travis\\\\.yml\", label: \".travis.yml\" },\n { kind: \"named\", value: \"\\\\.drone\\\\.yml\", label: \".drone.yml\" },\n { kind: \"named\", value: \"cloudbuild\\\\.yaml\", label: \"cloudbuild.yaml\" },\n { kind: \"named\", value: \"cloudbuild\\\\.yml\", label: \"cloudbuild.yml\" },\n { kind: \"under\", value: \"\\\\.buildkite\", label: \"Buildkite pipelines\" },\n ],\n protected_read: [\n { kind: \"under\", value: \"\\\\.scopebond\", label: \"the hook's own policy and keys (.scopebond)\" },\n { kind: \"raw\", pattern: `(?!.*${ci(\"\\\\.(?:key|pem|p12|pfx|jks|keystore)\")}$)`, label: \"signing keys and key containers (*.key, *.pem, *.p12, *.pfx, *.jks)\" },\n {\n kind: \"raw\",\n pattern: `(?!(?:.*/)?${ci(\"\\\\.env\")}(?!(?:\\\\.[^/]*)?\\\\.(?:${ci(\"example\")}|${ci(\"sample\")}|${ci(\"template\")}|${ci(\"dist\")})$)(?:\\\\.[^/]*)?$)`,\n label: \"environment secret files (.env, .env.* — except .example/.sample/.template/.dist)\",\n },\n { kind: \"named\", value: \"\\\\.envrc\", label: \".envrc\" },\n {\n kind: \"raw\",\n pattern: `(?!(?:.*/)?${ci(\"\\\\.ssh\")}(?:$|/(?!.*${ci(\"\\\\.pub\")}$)(?!${ci(\"known_hosts\")}$)(?!${ci(\"config\")}$)))`,\n label: \"SSH private keys and the .ssh directory (public keys, known_hosts and config are allowed)\",\n },\n { kind: \"raw\", pattern: `(?!(?:.*/)?${ci(\"\\\\.aws\")}(?:$|/(?!${ci(\"config\")}$)))`, label: \"AWS credentials (.aws, except .aws/config)\" },\n { kind: \"named\", value: \"\\\\.npmrc\", label: \".npmrc\" },\n { kind: \"named\", value: \"\\\\.pypirc\", label: \".pypirc\" },\n { kind: \"named\", value: \"\\\\.netrc\", label: \".netrc\" },\n { kind: \"named\", value: \"_netrc\", label: \"_netrc\" },\n { kind: \"named\", value: \"\\\\.git-credentials\", label: \".git-credentials\" },\n { kind: \"dir\", value: \"\\\\.kube\", label: \"the .kube directory\" },\n { kind: \"named\", value: \"\\\\.kube/config\", label: \".kube/config\" },\n { kind: \"dir\", value: \"\\\\.docker\", label: \"the .docker directory\" },\n { kind: \"named\", value: \"\\\\.docker/config\\\\.json\", label: \".docker/config.json\" },\n { kind: \"under\", value: \"\\\\.config/gcloud\", label: \"gcloud credentials\" },\n { kind: \"under\", value: \"\\\\.azure\", label: \"Azure credentials\" },\n { kind: \"under\", value: \"\\\\.gnupg\", label: \"GnuPG keys\" },\n { kind: \"dir\", value: \"\\\\.config/gh\", label: \"the GitHub CLI config directory\" },\n { kind: \"named\", value: \"\\\\.config/gh/hosts\\\\.yml\", label: \"the GitHub CLI's hosts.yml\" },\n { kind: \"named\", value: \"\\\\.claude/\\\\.credentials\\\\.json\", label: \"Claude Code's .credentials.json\" },\n ],\n observe: [\"net.fetch\", \"mcp.tool.call\"],\n };\n}\n\nexport const rulesPath = (dir: string): string => join(dir, RULES_FILE);\n\nexport function loadRules(configDir: string): RuleSet | null {\n const file = rulesPath(configDir);\n if (!existsSync(file)) return null;\n try {\n const parsed = JSON.parse(readFileSync(file, \"utf8\")) as RuleSet;\n if (parsed?.version !== 1 || !Array.isArray(parsed.protected_branches)) return null;\n return parsed;\n } catch { return null; }\n}\n\nexport function saveRules(configDir: string, rules: RuleSet): string {\n const file = rulesPath(configDir);\n writeFileSync(file, `${JSON.stringify(rules, null, 2)}\\n`);\n return file;\n}\n\n/** Whether `path` is inside the location a protected-path rule names. This is the same\n * pattern the compiled policy evaluates (the rule allows a path when the pattern's\n * negative lookahead matches), tested rule by rule so a classification can say which\n * kind of location it was. */\nexport function pathRuleMatches(rule: PathRule, path: string): boolean {\n if (path === \"\") return false;\n return !new RegExp(`^${pathPattern(rule)}.+`).test(path);\n}\n\n/** Whether a shell program is on the destructive list, folded the way the compiled\n * policy folds it: case-insensitive, without an executable suffix. */\nexport function isDestructiveProgram(rules: RuleSet, program: string): boolean {\n const name = program.toLowerCase().replace(/\\.(?:exe|cmd|bat|com|ps1)$/, \"\");\n return name !== \"\" && rules.destructive_programs.some((p) => p.toLowerCase() === name);\n}\n\n/** Whether a push destination is one of the protected refs (`release/*` is a prefix). */\nexport function isProtectedBranch(rules: RuleSet, ref: string): boolean {\n const name = ref.toLowerCase();\n return rules.protected_branches.some((b) => {\n const p = b.toLowerCase();\n return p.endsWith(\"/*\") ? name.startsWith(p.slice(0, -1)) : name === p;\n });\n}\n\n/** Plain English, for `scopebond-hook rules`. */\nexport function describeRules(rules: RuleSet): string {\n const lines: string[] = [];\n lines.push(\"Blocked before it runs:\");\n lines.push(\"\");\n lines.push(` pushes to ${rules.protected_branches.join(\", \")}`);\n lines.push(` and any push whose destination cannot be read`);\n lines.push(` programs ${rules.destructive_programs.join(\", \")}`);\n lines.push(\"\");\n lines.push(` writes to ${rules.protected_write.length} protected location(s):`);\n for (const rule of rules.protected_write) lines.push(` ${rule.label}`);\n lines.push(\"\");\n lines.push(` reads of ${rules.protected_read.length} protected location(s):`);\n for (const rule of rules.protected_read) lines.push(` ${rule.label}`);\n lines.push(\"\");\n if (rules.protect_remote_database) {\n lines.push(` remote SQL that drops a table, deletes or updates every row, or cannot be read`);\n lines.push(\"\");\n }\n if (rules.allowed_roots?.length) {\n lines.push(` writes outside ${rules.allowed_roots.join(\", \")} (physical target; unresolved targets too)`);\n lines.push(\"\");\n }\n lines.push(\"Recorded, not blocked:\");\n lines.push(` ${rules.observe.join(\", \")}`);\n return lines.join(\"\\n\");\n}\n"]}
package/dist/runtime.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type CloudExporter } from "@scopebond/gateway";
2
2
  import type { Mapped } from "./map.js";
3
3
  import { type HookConnection } from "./cloud.js";
4
+ import { type DispatchDecision } from "@scopebond/gateway";
4
5
  export interface RuntimeConfig {
5
6
  policyPath: string;
6
7
  keyPath: string;
@@ -10,6 +11,9 @@ export interface RuntimeConfig {
10
11
  * allowlist) instead of observed. Fail-closed for tools with no taxonomy
11
12
  * mapping; default false (observe, matching the connector conformance vector). */
12
13
  strict?: boolean;
14
+ /** The working directory the harness reported, used to resolve workspace roots when
15
+ * the rule set lists `allowed_roots`. Defaults to the process directory. */
16
+ cwd?: string;
13
17
  /** When connected to a Cloud workspace, receipts are auto-exported to the portal.
14
18
  * Export is best-effort and never changes the local decision. */
15
19
  cloud?: {
@@ -27,11 +31,25 @@ export interface Decision {
27
31
  receipt?: unknown;
28
32
  /** Every receipt produced — one per simple command in a decomposed shell call. */
29
33
  receipts?: unknown[];
34
+ /** Each action as actually evaluated (after root scoping and grouping), with its
35
+ * receipt: what the observation emitters bind a request digest to. */
36
+ /** What the dispatch boundary spent or refused for this call, when one is configured. */
37
+ dispatch?: DispatchDecision;
38
+ dispatched?: Array<{
39
+ action: {
40
+ action_type: string;
41
+ params: Record<string, unknown>;
42
+ };
43
+ receipt?: unknown;
44
+ }>;
30
45
  }
31
46
  export declare const ci: (s: string) => string;
32
47
  export declare const under: (dir: string) => string;
33
48
  export declare const named: (file: string) => string;
34
49
  export declare const dir: (d: string) => string;
50
+ /** The write protection that is always on, whoever manages the rules: the hook's own settings and those of the agents it
51
+ * guards. A workspace can relax CI-configuration writes; it can never relax these. */
52
+ export declare const GUARDRAIL_WRITE_PATTERN: string;
35
53
  export declare const DESTRUCTIVE: string[];
36
54
  /** Upgrade the starter-policy patterns an older hook wrote, in memory, so a user who
37
55
  * installed an earlier version gets the current protections without re-running init.
@@ -61,6 +79,8 @@ export declare function createHookRuntime(config: RuntimeConfig): {
61
79
  evaluateOne(mapped: Mapped): Promise<Decision>;
62
80
  /** Decide a whole tool call. A shell call decomposes into several simple
63
81
  * commands; every one is recorded, and a single deny denies the call. */
64
- evaluate(mapped: Mapped | Mapped[]): Promise<Decision>;
82
+ evaluate(mapped: Mapped | Mapped[], options?: {
83
+ groupKey?: string;
84
+ }): Promise<Decision>;
65
85
  };
66
86
  //# sourceMappingURL=runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAOA,OAAO,EAA6C,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAK/E,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf;;uFAEmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;sEACkE;IAClE,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvF;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,eAAe,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAWD,eAAO,MAAM,EAAE,GAAI,GAAG,MAAM,KAAG,MAAiF,CAAC;AACjH,eAAO,MAAM,KAAK,GAAI,KAAK,MAAM,KAAG,MAAyC,CAAC;AAC9E,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,KAAG,MAAoC,CAAC;AAC1E,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,KAAG,MAAmC,CAAC;AA4BpE,eAAO,MAAM,WAAW,UAGvB,CAAC;AAuBF;;uFAEuF;AACvF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAQpD;AAED;;;0BAG0B;AAC1B,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgCvE;AAED;4EAC4E;AAC5E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa;;;;IA8BnD;qFACiF;aAClE,OAAO,CAAC,IAAI,CAAC;IAG5B;;;;2DAIuD;aAC9C,IAAI;IAIb,gEAAgE;wBACtC,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IA+BpD;8EAC0E;qBACnD,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;EAkB/D"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAOA,OAAO,EAA6C,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAO/E,OAAO,EAAoB,KAAK,gBAAgB,EAAsB,MAAM,oBAAoB,CAAC;AAEjG,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf;;uFAEmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;iFAC6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;sEACkE;IAClE,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvF;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,eAAe,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB;2EACuE;IACvE,yFAAyF;IACzF,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;SAAE,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC7G;AAWD,eAAO,MAAM,EAAE,GAAI,GAAG,MAAM,KAAG,MAAiF,CAAC;AACjH,eAAO,MAAM,KAAK,GAAI,KAAK,MAAM,KAAG,MAAyC,CAAC;AAC9E,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,KAAG,MAAoC,CAAC;AAC1E,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,KAAG,MAAmC,CAAC;AAuBpE;uFACuF;AACvF,eAAO,MAAM,uBAAuB,QAAwC,CAAC;AAoB7E,eAAO,MAAM,WAAW,UAGvB,CAAC;AAwBF;;uFAEuF;AACvF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAQpD;AAED;;;0BAG0B;AAC1B,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgCvE;AAED;4EAC4E;AAC5E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa;;;;IAoCnD;qFACiF;aAClE,OAAO,CAAC,IAAI,CAAC;IAG5B;;;;2DAIuD;aAC9C,IAAI;IAKb,gEAAgE;wBACtC,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IA+BpD;8EAC0E;qBACnD,MAAM,GAAG,MAAM,EAAE,YAAW;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAQ,OAAO,CAAC,QAAQ,CAAC;EAiDpG"}