@intutic/sync-daemon 1.10.0 → 2.0.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.
- package/README.md +2 -2
- package/dist/agentReporter.d.ts +205 -0
- package/dist/agentReporter.d.ts.map +1 -0
- package/dist/agentReporter.js +394 -0
- package/dist/agentReporter.js.map +1 -0
- package/dist/configReader.d.ts +39 -2
- package/dist/configReader.d.ts.map +1 -1
- package/dist/configReader.js +56 -2
- package/dist/configReader.js.map +1 -1
- package/dist/configWriter.d.ts +51 -1
- package/dist/configWriter.d.ts.map +1 -1
- package/dist/configWriter.js +490 -63
- package/dist/configWriter.js.map +1 -1
- package/dist/harness/antigravityHooks.d.ts.map +1 -1
- package/dist/harness/antigravityHooks.js +33 -55
- package/dist/harness/antigravityHooks.js.map +1 -1
- package/dist/harness/claudeCodeHooks.d.ts +47 -13
- package/dist/harness/claudeCodeHooks.d.ts.map +1 -1
- package/dist/harness/claudeCodeHooks.js +316 -54
- package/dist/harness/claudeCodeHooks.js.map +1 -1
- package/dist/harness/claudeDesktopHooks.d.ts.map +1 -1
- package/dist/harness/claudeDesktopHooks.js +35 -48
- package/dist/harness/claudeDesktopHooks.js.map +1 -1
- package/dist/harness/clineHooks.d.ts.map +1 -1
- package/dist/harness/clineHooks.js +42 -34
- package/dist/harness/clineHooks.js.map +1 -1
- package/dist/harness/codexHooks.d.ts +36 -0
- package/dist/harness/codexHooks.d.ts.map +1 -0
- package/dist/harness/codexHooks.js +227 -0
- package/dist/harness/codexHooks.js.map +1 -0
- package/dist/harness/continueHooks.d.ts +45 -5
- package/dist/harness/continueHooks.d.ts.map +1 -1
- package/dist/harness/continueHooks.js +225 -5
- package/dist/harness/continueHooks.js.map +1 -1
- package/dist/harness/cursorHooks.d.ts +31 -0
- package/dist/harness/cursorHooks.d.ts.map +1 -1
- package/dist/harness/cursorHooks.js +78 -49
- package/dist/harness/cursorHooks.js.map +1 -1
- package/dist/harness/dshHooks.d.ts +198 -0
- package/dist/harness/dshHooks.d.ts.map +1 -0
- package/dist/harness/dshHooks.js +569 -0
- package/dist/harness/dshHooks.js.map +1 -0
- package/dist/harness/gateBody.d.ts +422 -0
- package/dist/harness/gateBody.d.ts.map +1 -0
- package/dist/harness/gateBody.js +1816 -0
- package/dist/harness/gateBody.js.map +1 -0
- package/dist/harness/gateKind.d.ts +164 -0
- package/dist/harness/gateKind.d.ts.map +1 -0
- package/dist/harness/gateKind.js +143 -0
- package/dist/harness/gateKind.js.map +1 -0
- package/dist/harness/githubCopilotHooks.d.ts +54 -0
- package/dist/harness/githubCopilotHooks.d.ts.map +1 -0
- package/dist/harness/githubCopilotHooks.js +218 -0
- package/dist/harness/githubCopilotHooks.js.map +1 -0
- package/dist/harness/gooseHooks.d.ts.map +1 -1
- package/dist/harness/gooseHooks.js +67 -37
- package/dist/harness/gooseHooks.js.map +1 -1
- package/dist/harness/grokHooks.d.ts +77 -0
- package/dist/harness/grokHooks.d.ts.map +1 -0
- package/dist/harness/grokHooks.js +350 -0
- package/dist/harness/grokHooks.js.map +1 -0
- package/dist/harness/hermesHooks.d.ts.map +1 -1
- package/dist/harness/hermesHooks.js +26 -55
- package/dist/harness/hermesHooks.js.map +1 -1
- package/dist/harness/holdRedaction.d.ts +69 -0
- package/dist/harness/holdRedaction.d.ts.map +1 -0
- package/dist/harness/holdRedaction.js +211 -0
- package/dist/harness/holdRedaction.js.map +1 -0
- package/dist/harness/jetbrainsXmlConfig.d.ts +51 -0
- package/dist/harness/jetbrainsXmlConfig.d.ts.map +1 -0
- package/dist/harness/jetbrainsXmlConfig.js +151 -0
- package/dist/harness/jetbrainsXmlConfig.js.map +1 -0
- package/dist/harness/mcpAutoWrite.d.ts +100 -3
- package/dist/harness/mcpAutoWrite.d.ts.map +1 -1
- package/dist/harness/mcpAutoWrite.js +712 -90
- package/dist/harness/mcpAutoWrite.js.map +1 -1
- package/dist/harness/museHooks.d.ts +53 -0
- package/dist/harness/museHooks.d.ts.map +1 -0
- package/dist/harness/museHooks.js +273 -0
- package/dist/harness/museHooks.js.map +1 -0
- package/dist/harness/n8nHooks.d.ts +25 -9
- package/dist/harness/n8nHooks.d.ts.map +1 -1
- package/dist/harness/n8nHooks.js +174 -9
- package/dist/harness/n8nHooks.js.map +1 -1
- package/dist/harness/openCodeHooks.d.ts +93 -0
- package/dist/harness/openCodeHooks.d.ts.map +1 -0
- package/dist/harness/openCodeHooks.js +248 -0
- package/dist/harness/openCodeHooks.js.map +1 -0
- package/dist/harness/openWebuiHooks.d.ts.map +1 -1
- package/dist/harness/openWebuiHooks.js +56 -11
- package/dist/harness/openWebuiHooks.js.map +1 -1
- package/dist/harness/openclawHooks.d.ts.map +1 -1
- package/dist/harness/openclawHooks.js +30 -54
- package/dist/harness/openclawHooks.js.map +1 -1
- package/dist/harness/openhandsHooks.d.ts.map +1 -1
- package/dist/harness/openhandsHooks.js +35 -41
- package/dist/harness/openhandsHooks.js.map +1 -1
- package/dist/harness/piHooks.d.ts.map +1 -1
- package/dist/harness/piHooks.js +31 -58
- package/dist/harness/piHooks.js.map +1 -1
- package/dist/harness/protectedPaths.d.ts +353 -0
- package/dist/harness/protectedPaths.d.ts.map +1 -0
- package/dist/harness/protectedPaths.js +805 -0
- package/dist/harness/protectedPaths.js.map +1 -0
- package/dist/harness/rooCodeHooks.d.ts.map +1 -1
- package/dist/harness/rooCodeHooks.js +34 -48
- package/dist/harness/rooCodeHooks.js.map +1 -1
- package/dist/harness/windsurfHooks.d.ts +72 -0
- package/dist/harness/windsurfHooks.d.ts.map +1 -1
- package/dist/harness/windsurfHooks.js +172 -58
- package/dist/harness/windsurfHooks.js.map +1 -1
- package/dist/harness/windsurfJetBrainsProxy.d.ts +78 -0
- package/dist/harness/windsurfJetBrainsProxy.d.ts.map +1 -0
- package/dist/harness/windsurfJetBrainsProxy.js +159 -0
- package/dist/harness/windsurfJetBrainsProxy.js.map +1 -0
- package/dist/hashReporter.js +1 -1
- package/dist/hashReporter.js.map +1 -1
- package/dist/index.d.ts +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/dist/integrityStore.d.ts +0 -8
- package/dist/integrityStore.d.ts.map +1 -1
- package/dist/integrityStore.js +6 -29
- package/dist/integrityStore.js.map +1 -1
- package/dist/lib/approvedBypasses.d.ts +76 -0
- package/dist/lib/approvedBypasses.d.ts.map +1 -0
- package/dist/lib/approvedBypasses.js +140 -0
- package/dist/lib/approvedBypasses.js.map +1 -0
- package/dist/lib/complianceProbes.d.ts.map +1 -1
- package/dist/lib/complianceProbes.js +5 -0
- package/dist/lib/complianceProbes.js.map +1 -1
- package/dist/lib/decisionsDigest.d.ts +99 -0
- package/dist/lib/decisionsDigest.d.ts.map +1 -0
- package/dist/lib/decisionsDigest.js +199 -0
- package/dist/lib/decisionsDigest.js.map +1 -0
- package/dist/lib/egressPolicy.d.ts +69 -0
- package/dist/lib/egressPolicy.d.ts.map +1 -0
- package/dist/lib/egressPolicy.js +126 -0
- package/dist/lib/egressPolicy.js.map +1 -0
- package/dist/lib/gitWorktrees.d.ts +140 -0
- package/dist/lib/gitWorktrees.d.ts.map +1 -0
- package/dist/lib/gitWorktrees.js +191 -0
- package/dist/lib/gitWorktrees.js.map +1 -0
- package/dist/lib/policySnapshot.d.ts +224 -0
- package/dist/lib/policySnapshot.d.ts.map +1 -0
- package/dist/lib/policySnapshot.js +637 -0
- package/dist/lib/policySnapshot.js.map +1 -0
- package/dist/lib/processPoller.d.ts +44 -0
- package/dist/lib/processPoller.d.ts.map +1 -1
- package/dist/lib/processPoller.js +176 -1
- package/dist/lib/processPoller.js.map +1 -1
- package/dist/lib/runtimeEnv.d.ts +8 -0
- package/dist/lib/runtimeEnv.d.ts.map +1 -1
- package/dist/lib/runtimeEnv.js +3 -0
- package/dist/lib/runtimeEnv.js.map +1 -1
- package/dist/localTrajectoryHeuristics.d.ts +51 -0
- package/dist/localTrajectoryHeuristics.d.ts.map +1 -0
- package/dist/localTrajectoryHeuristics.js +90 -0
- package/dist/localTrajectoryHeuristics.js.map +1 -0
- package/dist/sessionReporter.d.ts +70 -0
- package/dist/sessionReporter.d.ts.map +1 -0
- package/dist/sessionReporter.js +140 -0
- package/dist/sessionReporter.js.map +1 -0
- package/dist/skillWriter.d.ts +1 -1
- package/dist/skillWriter.d.ts.map +1 -1
- package/dist/skillWriter.js +5 -2
- package/dist/skillWriter.js.map +1 -1
- package/dist/syncLoop.d.ts +105 -3
- package/dist/syncLoop.d.ts.map +1 -1
- package/dist/syncLoop.js +397 -65
- package/dist/syncLoop.js.map +1 -1
- package/dist/trajectoryMonitor.d.ts +2 -0
- package/dist/trajectoryMonitor.d.ts.map +1 -1
- package/dist/trajectoryMonitor.js +57 -0
- package/dist/trajectoryMonitor.js.map +1 -1
- package/dist/watcher/driftWatcher.d.ts.map +1 -1
- package/dist/watcher/driftWatcher.js +12 -3
- package/dist/watcher/driftWatcher.js.map +1 -1
- package/dist/watcher/settingsGuard.d.ts +23 -0
- package/dist/watcher/settingsGuard.d.ts.map +1 -1
- package/dist/watcher/settingsGuard.js +202 -17
- package/dist/watcher/settingsGuard.js.map +1 -1
- package/package.json +37 -6
- package/dist/brainIndexer.d.ts +0 -51
- package/dist/brainIndexer.d.ts.map +0 -1
- package/dist/brainIndexer.js +0 -148
- package/dist/brainIndexer.js.map +0 -1
- package/dist/harness/cursorHooksJson.d.ts +0 -29
- package/dist/harness/cursorHooksJson.d.ts.map +0 -1
- package/dist/harness/cursorHooksJson.js +0 -130
- package/dist/harness/cursorHooksJson.js.map +0 -1
- package/dist/harness/gooseHooksWriter.d.ts +0 -27
- package/dist/harness/gooseHooksWriter.d.ts.map +0 -1
- package/dist/harness/gooseHooksWriter.js +0 -147
- package/dist/harness/gooseHooksWriter.js.map +0 -1
- package/dist/harness/openhandsHooksWriter.d.ts +0 -23
- package/dist/harness/openhandsHooksWriter.d.ts.map +0 -1
- package/dist/harness/openhandsHooksWriter.js +0 -146
- package/dist/harness/openhandsHooksWriter.js.map +0 -1
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* policySnapshot.ts — ships workspace policy *to* the machine, so the gate does
|
|
3
|
+
* not have to ship the decision off it.
|
|
4
|
+
*
|
|
5
|
+
* # Why
|
|
6
|
+
*
|
|
7
|
+
* TD-307: `POST /api/v1/hook-gate` was called by two of thirteen gates, so DLP,
|
|
8
|
+
* `BLOCK:` SOPs, SSO policy and promoted findings reached two of thirteen. The
|
|
9
|
+
* obvious repair — route the other eleven through the gate — is the wrong one:
|
|
10
|
+
* those eleven enforce locally and **fail closed**, the two network callers were
|
|
11
|
+
* the only ones that **fail open**, and the gate's allow path is five or six
|
|
12
|
+
* sequential Postgres round-trips. Routing everyone through it would trade
|
|
13
|
+
* fail-closed for fail-open and put a control-plane outage on every `Bash`.
|
|
14
|
+
*
|
|
15
|
+
* Almost none of that decision needs the network. So the daemon resolves policy
|
|
16
|
+
* once per sync cycle and writes it here; every gate reads a local file.
|
|
17
|
+
*
|
|
18
|
+
* # Two tiers, one invariant
|
|
19
|
+
*
|
|
20
|
+
* The **static floor** (`staticFloorPatterns()`) is compiled into each gate and
|
|
21
|
+
* always enforced. The **dynamic tier** is this file, and it is *additive only*:
|
|
22
|
+
*
|
|
23
|
+
* > For every harness and every snapshot state, the set of calls blocked after
|
|
24
|
+
* > this change is a superset of the set blocked before.
|
|
25
|
+
*
|
|
26
|
+
* That is what makes the failure modes tractable. A missing or corrupt snapshot
|
|
27
|
+
* costs only rules that, for eleven of the thirteen gates, did not exist at all
|
|
28
|
+
* before — so degrading returns to yesterday's behaviour rather than opening a
|
|
29
|
+
* new hole. It is also why an absent snapshot does **not** fail closed: that
|
|
30
|
+
* would brick every agent on every machine at install time, to replace
|
|
31
|
+
* protection the floor already provides.
|
|
32
|
+
*
|
|
33
|
+
* # Where it lives, and why that matters
|
|
34
|
+
*
|
|
35
|
+
* `~/.intutic/hooks/policy-snapshot.{json,rules}`. The directory is load-bearing:
|
|
36
|
+
* `.intutic/hooks` is already in `UNIVERSAL_PROTECTED_PATHS`, so all thirteen
|
|
37
|
+
* gates refuse an agent's attempt to write the snapshot on day one, with no new
|
|
38
|
+
* entry and no new code.
|
|
39
|
+
*
|
|
40
|
+
* # The ` WHERE ` clause survives this pipeline now
|
|
41
|
+
*
|
|
42
|
+
* A SOP titled `BLOCK:^shell$ WHERE kubectl\s+apply(?!.*@sha256:):reason`
|
|
43
|
+
* resolves to `{toolPattern, argPattern}` at `GET /api/v1/policy/resolve`
|
|
44
|
+
* (the demo doctor's "argPattern served" check proves that half). This module
|
|
45
|
+
* used to be where the other half died: `ResolvedPolicy` did not declare
|
|
46
|
+
* `argPattern`, `toGuardPattern` did not carry it, and the rule reached all
|
|
47
|
+
* twelve tool-call gates as "block `shell` unconditionally" — simultaneously
|
|
48
|
+
* failing to enforce the argument condition and re-manufacturing the exact
|
|
49
|
+
* over-blocking (`make test` refused) the WHERE grammar was invented to
|
|
50
|
+
* eliminate. It now travels: verbatim in the JSON's `sopRules` and `rules`,
|
|
51
|
+
* base64-encoded in the `.rules` projection's seventh column (see
|
|
52
|
+
* `RULES_COLUMNS` in gateBody.ts for the encoding contract), and every gate
|
|
53
|
+
* family conditions a name-matched rule on it before firing.
|
|
54
|
+
*
|
|
55
|
+
* @module
|
|
56
|
+
*/
|
|
57
|
+
import * as fs from 'node:fs/promises';
|
|
58
|
+
import * as path from 'node:path';
|
|
59
|
+
import * as os from 'node:os';
|
|
60
|
+
import { createHash } from 'node:crypto';
|
|
61
|
+
import { createLogger } from '@intutic/logger';
|
|
62
|
+
import { toRulesLine, GATE_VERSION, RULES_COLUMNS } from '../harness/gateBody.js';
|
|
63
|
+
import { DESTRUCTIVE_COMMAND_PATTERNS, SKILL_SURFACE_PATTERNS, assertPortableEre, } from '../harness/protectedPaths.js';
|
|
64
|
+
const log = createLogger('sync-policy-snapshot');
|
|
65
|
+
/** Directory every gate looks in. Inside `.intutic/hooks`, which is already
|
|
66
|
+
* protected — see the module note. */
|
|
67
|
+
export const DEFAULT_SNAPSHOT_DIR = path.join(os.homedir(), '.intutic', 'hooks');
|
|
68
|
+
export const SNAPSHOT_JSON = 'policy-snapshot.json';
|
|
69
|
+
export const SNAPSHOT_RULES = 'policy-snapshot.rules';
|
|
70
|
+
/**
|
|
71
|
+
* Whether the destructive tier ships as `block` or as `warn`.
|
|
72
|
+
*
|
|
73
|
+
* `warn`. These seven patterns qualify for `block` on the merits — every one is
|
|
74
|
+
* unrecoverable without a reinstall or a backup — but they have never run
|
|
75
|
+
* against real developer traffic, and they would land on thirteen harnesses at
|
|
76
|
+
* once. A false positive here does not merely annoy: the fastest workaround
|
|
77
|
+
* available to a blocked developer is `chflags nouchg` on the hook, which is the
|
|
78
|
+
* exact bypass `GOVERNANCE_BYPASS_PATTERNS` exists to stop. Shipping too hard
|
|
79
|
+
* manufactures our own adversary.
|
|
80
|
+
*
|
|
81
|
+
* So they ride the advisory tier first and earn promotion from
|
|
82
|
+
* `tool_flagged` telemetry — the same discipline `sslGateEvaluator.ts` follows
|
|
83
|
+
* for SSL enforcement, and the same one `packages/proxy/src/plugins/anomaly`
|
|
84
|
+
* states as the promotion rule. Flipping this constant is the promotion, and it
|
|
85
|
+
* belongs in a commit alongside the measurement that licenses it.
|
|
86
|
+
*
|
|
87
|
+
* Shipping them *through the snapshot* rather than compiling them into the gate
|
|
88
|
+
* is the other half: the set we are least sure about is the set we can retract
|
|
89
|
+
* in one sync cycle instead of one release.
|
|
90
|
+
*/
|
|
91
|
+
export const DESTRUCTIVE_TIER_SEVERITY = 'warn';
|
|
92
|
+
/**
|
|
93
|
+
* Whether the skill-directory-write tier ships as `block` or as `warn`.
|
|
94
|
+
*
|
|
95
|
+
* `block` — deliberately not following `DESTRUCTIVE_TIER_SEVERITY`'s warn-first
|
|
96
|
+
* ramp, because the thing that ramp is buying time for does not exist on this
|
|
97
|
+
* surface. `DESTRUCTIVE_COMMAND_PATTERNS` rides at `warn` because those seven
|
|
98
|
+
* families are unmeasured against real developer traffic and a false positive
|
|
99
|
+
* has a real cost (see that constant's own comment). `SKILL_SURFACE_PATTERNS`
|
|
100
|
+
* is not that kind of rule: it does not run `scanSkillContent` or judge
|
|
101
|
+
* anything about what a skill file says, only WHERE a write's target path
|
|
102
|
+
* points — `.agents/skills/**` or `.claude/skills/**`, a deterministic,
|
|
103
|
+
* zero-ambiguity match. TD-358 (`docs/TECH_DEBT.md`) says this about exactly
|
|
104
|
+
* that distinction: "Path-matching is the one exception, and it is not
|
|
105
|
+
* actually an exception to the measurement requirement — it never needed
|
|
106
|
+
* one." There is no false-positive rate to earn a promotion by measuring,
|
|
107
|
+
* because there is nothing probabilistic here to measure in the first place.
|
|
108
|
+
*
|
|
109
|
+
* The second half of the argument is what `warn` actually buys on this
|
|
110
|
+
* surface, and the answer is nothing. `SECRET_CONTENT_PATTERNS`
|
|
111
|
+
* (`harness/protectedPaths.ts`) skips the warn-first ramp for the same reason
|
|
112
|
+
* and states the principle this constant now follows: "A warn that lets the
|
|
113
|
+
* write proceed IS the incident." A poisoned skill file written under
|
|
114
|
+
* warn-only is not a near-miss sitting in telemetry waiting for a human to
|
|
115
|
+
* notice — it is a file on disk that the very next agent session loads and
|
|
116
|
+
* trusts as instructions, the same way it trusts its own system prompt. Warn
|
|
117
|
+
* logs the write and lets it land anyway, so there is no window in which the
|
|
118
|
+
* advisory tier protected anything. That is what makes this different from
|
|
119
|
+
* `DESTRUCTIVE_COMMAND_PATTERNS`: a destructive *command*'s warn tier buys a
|
|
120
|
+
* chance for a human to notice before the damage is done; a skill file, once
|
|
121
|
+
* written, already is the damage.
|
|
122
|
+
*
|
|
123
|
+
* Shipped *through the snapshot* rather than promoted in place in
|
|
124
|
+
* `staticFloorPatterns()`, for the same operational reason
|
|
125
|
+
* `DESTRUCTIVE_TIER_SEVERITY` is: the rollout, not the mechanism, is the thing
|
|
126
|
+
* still short on field time, and the snapshot channel is what makes it
|
|
127
|
+
* retractable in one sync cycle instead of one release if that turns out
|
|
128
|
+
* wrong. `staticFloorPatterns()` keeps its two `SKILL_SURFACE_PATTERNS`
|
|
129
|
+
* entries at `warn` — unchanged by this constant — as the degraded-mode
|
|
130
|
+
* baseline for a workspace with no snapshot, or an invalid one; this constant
|
|
131
|
+
* is what promotes the steady state to `block` everywhere a valid snapshot has
|
|
132
|
+
* landed. Flipping it back to `'warn'` is the retraction, exactly as it is for
|
|
133
|
+
* `DESTRUCTIVE_TIER_SEVERITY`.
|
|
134
|
+
*/
|
|
135
|
+
export const SKILL_SURFACE_TIER_SEVERITY = 'block';
|
|
136
|
+
/**
|
|
137
|
+
* Tool names used to detect a rule that blocks everything.
|
|
138
|
+
*
|
|
139
|
+
* A pattern matching all of these is a catch-all whatever it looks like, so this
|
|
140
|
+
* catches `.*`, `.+`, `[A-Za-z]*` and anything else someone reaches for — rather
|
|
141
|
+
* than a denylist of the three spellings we happened to think of.
|
|
142
|
+
*/
|
|
143
|
+
const CANARY_TOOLS = [
|
|
144
|
+
'Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep', 'Task', 'WebFetch',
|
|
145
|
+
'NotebookEdit', 'TodoWrite', 'run_command', 'str_replace_editor',
|
|
146
|
+
// M3: two `mcp__<server>__<tool>`-shaped canaries, so a rule that is
|
|
147
|
+
// scoped to one MCP server (e.g. `mcp__github__.*`) is exercised against
|
|
148
|
+
// this same threshold check as every other tool-name pattern, and a rule
|
|
149
|
+
// that reaches all the way into MCP-tool-shaped names (not just the
|
|
150
|
+
// native tool names above) is still caught as a catch-all.
|
|
151
|
+
'mcp__github__create_issue', 'mcp__filesystem__read_file',
|
|
152
|
+
];
|
|
153
|
+
/**
|
|
154
|
+
* Strips a repeated character from one end of a string, in linear time.
|
|
155
|
+
*
|
|
156
|
+
* `s.replace(/\$+$/, '')` is the obvious spelling and it is quadratic. The
|
|
157
|
+
* quantifier is greedy and the anchor is at the *end*, so on a string that does
|
|
158
|
+
* not finish with the character, every start offset consumes the whole run and
|
|
159
|
+
* then backtracks through it: measured on `'$'.repeat(n) + 'a'`, 20 KB took
|
|
160
|
+
* 130 ms and 80 KB took 2 seconds. A string that *does* end with it matches at
|
|
161
|
+
* the first offset and returns instantly, which is why the obvious test for
|
|
162
|
+
* this passes — CodeQL flagged all three call sites as `js/polynomial-redos`.
|
|
163
|
+
*
|
|
164
|
+
* Two of them run over `toolPattern`, which arrives from the control plane and
|
|
165
|
+
* is authored per workspace, on the path that decides whether a policy rule
|
|
166
|
+
* ships to the blocking gate.
|
|
167
|
+
*/
|
|
168
|
+
function stripEnd(s, ch) {
|
|
169
|
+
let end = s.length;
|
|
170
|
+
while (end > 0 && s[end - 1] === ch)
|
|
171
|
+
end -= 1;
|
|
172
|
+
return s.slice(0, end);
|
|
173
|
+
}
|
|
174
|
+
/** The same, from the front. Anchored at the start, so this one is linear. */
|
|
175
|
+
function stripStart(s, ch) {
|
|
176
|
+
let i = 0;
|
|
177
|
+
while (i < s.length && s[i] === ch)
|
|
178
|
+
i += 1;
|
|
179
|
+
return s.slice(i);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Decides whether a control-plane rule is safe to put on a blocking path.
|
|
183
|
+
*
|
|
184
|
+
* Returns the reason for rejection, or `null` if the rule is fine. Rejections
|
|
185
|
+
* are logged rather than thrown: one bad SOP must not cost the workspace every
|
|
186
|
+
* other rule.
|
|
187
|
+
*/
|
|
188
|
+
export function validateRule(toolPattern, ruleId) {
|
|
189
|
+
const raw = (toolPattern ?? '').trim();
|
|
190
|
+
if (!raw)
|
|
191
|
+
return 'empty toolPattern';
|
|
192
|
+
// Anchors are meaningless once wrapped for token matching, and are the most
|
|
193
|
+
// common thing a SOP author writes out of habit.
|
|
194
|
+
const stripped = stripEnd(stripStart(raw, '^'), '$');
|
|
195
|
+
if (!stripped)
|
|
196
|
+
return 'toolPattern was only anchors';
|
|
197
|
+
const wrapped = ` (${stripped}) `;
|
|
198
|
+
try {
|
|
199
|
+
assertPortableEre(wrapped, ruleId);
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
return `not portable across grep and JS — ${err instanceof Error ? err.message : String(err)}`;
|
|
203
|
+
}
|
|
204
|
+
let re;
|
|
205
|
+
try {
|
|
206
|
+
re = new RegExp(wrapped);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return 'does not compile';
|
|
210
|
+
}
|
|
211
|
+
// A threshold rather than "matches all of them". `[A-Za-z]*` matches ten of
|
|
212
|
+
// these fourteen — it misses only the four with an underscore — and shipping
|
|
213
|
+
// it to a blocking path would stop essentially every tool call. Requiring a
|
|
214
|
+
// clean sweep would have let it through on a technicality.
|
|
215
|
+
//
|
|
216
|
+
// The ratio is 0.7, not 0.8, and that is a deliberate M3 adjustment, not a
|
|
217
|
+
// drive-by tweak. Before M3, CANARY_TOOLS held 12 entries, 2 of them
|
|
218
|
+
// underscored (`run_command`, `str_replace_editor`), so `[A-Za-z]*` hit 10 of
|
|
219
|
+
// 12 (83%) — above an 0.8 threshold, correctly rejected. M3 added two more
|
|
220
|
+
// *underscored* canaries (`mcp__github__create_issue`,
|
|
221
|
+
// `mcp__filesystem__read_file`, both required by name — see CANARY_TOOLS)
|
|
222
|
+
// without adding any non-underscored ones, so the same `[A-Za-z]*` pattern
|
|
223
|
+
// now hits 10 of 14 (71%): still every alphabetic-only tool name there is,
|
|
224
|
+
// but under an unchanged 0.8 threshold that stops being "a catch-all" and
|
|
225
|
+
// starts being "accepted" — silently weakening a case
|
|
226
|
+
// `harnessProtectedPaths`... `policySnapshot.test.ts` pins by name. Lowering
|
|
227
|
+
// the ratio to 0.7 keeps the absolute hit-count threshold at 10 (`Math.ceil`
|
|
228
|
+
// of both 12*0.8 and 14*0.7), so `[A-Za-z]*` is rejected exactly as before —
|
|
229
|
+
// the ratio moved so the THRESHOLD would not.
|
|
230
|
+
const hits = CANARY_TOOLS.filter((t) => re.test(` ${t} `)).length;
|
|
231
|
+
const limit = Math.ceil(CANARY_TOOLS.length * 0.7);
|
|
232
|
+
if (hits >= limit) {
|
|
233
|
+
return (`matches ${hits} of ${CANARY_TOOLS.length} common tool names — this is a ` +
|
|
234
|
+
`catch-all and would block the workspace`);
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
/** Converts one resolved SOP rule into a gate pattern, or null if unusable. */
|
|
239
|
+
function toGuardPattern(rule, shadow) {
|
|
240
|
+
// Only `block`. The control plane also emits `{toolPattern: '.*', action:
|
|
241
|
+
// 'warn'}` for every HIGH/CRITICAL-risk SOP (evaluate.ts) — a live landmine
|
|
242
|
+
// for anything that ships rules to a blocking path, and the reason this filter
|
|
243
|
+
// is the first line rather than an afterthought.
|
|
244
|
+
//
|
|
245
|
+
// One exception (LLD #71): a rule the control plane projects from a cited
|
|
246
|
+
// policy guardrail in SHADOW carries `origin: 'guardrail'` and `action:
|
|
247
|
+
// 'warn'`. It ships at severity `warn` — the gate logs `tool_flagged` and
|
|
248
|
+
// allows — so the rule can earn its evidence. That is the documented meaning
|
|
249
|
+
// of `warn` ("has not earned the right to block yet"), and it is still
|
|
250
|
+
// additive: a warn rule blocks nothing. `validateRule` below still applies,
|
|
251
|
+
// so a catch-all guardrail is refused the same way.
|
|
252
|
+
//
|
|
253
|
+
// And `require_approval` (a `REQUIRE_APPROVAL:` SOP title): shipped at
|
|
254
|
+
// severity `hold` since gate body v8. Refuses like block, records the call
|
|
255
|
+
// for review, and an approved bypass lets the exact call through once.
|
|
256
|
+
const guardrail = rule.origin === 'guardrail';
|
|
257
|
+
const hold = rule.action === 'require_approval';
|
|
258
|
+
if (rule.action !== 'block' && !hold && !(guardrail && rule.action === 'warn'))
|
|
259
|
+
return null;
|
|
260
|
+
const why = validateRule(rule.toolPattern, rule.id);
|
|
261
|
+
if (why) {
|
|
262
|
+
log.warn({ action: 'policy_rule_rejected', ruleId: rule.id, toolPattern: rule.toolPattern, reason: why }, `Policy rule rejected and NOT shipped to the gate: ${why}`);
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
// The ` WHERE ` clause travels with the rule. It is validated only for JS
|
|
266
|
+
// compilability — it is a JS regex matched against serialized tool input,
|
|
267
|
+
// never a grep pattern, so `validateRule`'s portable-ERE discipline does not
|
|
268
|
+
// apply to it. One that does not compile is stripped (the rule ships
|
|
269
|
+
// name-only, today's behaviour) and logged, NOT dropped with its rule: the
|
|
270
|
+
// clause narrows a block, so losing the clause must widen enforcement, and
|
|
271
|
+
// losing the rule would open it.
|
|
272
|
+
let argPattern;
|
|
273
|
+
if (rule.argPattern) {
|
|
274
|
+
try {
|
|
275
|
+
new RegExp(rule.argPattern);
|
|
276
|
+
argPattern = rule.argPattern;
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
log.warn({ action: 'policy_rule_arg_pattern_dropped', ruleId: rule.id, argPattern: rule.argPattern }, 'argPattern does not compile as a JS RegExp — rule shipped name-only');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const stripped = stripEnd(stripStart(rule.toolPattern.trim(), '^'), '$');
|
|
283
|
+
return {
|
|
284
|
+
id: `sop.${rule.id}`,
|
|
285
|
+
// Wrapped in spaces so the pattern matches a whole tool token: the gate
|
|
286
|
+
// tests against a space-padded string, so ` (Bash) ` matches the tool `Bash`
|
|
287
|
+
// and not a tool called `BashHistory`.
|
|
288
|
+
source: ` (${stripped}) `,
|
|
289
|
+
subject: 'tool',
|
|
290
|
+
// `shadow`, not `warn`. Both allow the call, but they mean different
|
|
291
|
+
// things: `warn` is "this rule has not earned the right to block yet",
|
|
292
|
+
// `shadow` is "this rule is certain and the workspace asked us not to act".
|
|
293
|
+
// Collapsing them makes a SILENT_LOG rollout unmeasurable, because you
|
|
294
|
+
// cannot tell which flags would have been blocks. A guardrail in SHADOW
|
|
295
|
+
// is the one rule that ships as `warn` on purpose (see above); under
|
|
296
|
+
// SILENT_LOG it demotes to `shadow` with everything else.
|
|
297
|
+
severity: shadow
|
|
298
|
+
? 'shadow'
|
|
299
|
+
: hold
|
|
300
|
+
? 'hold'
|
|
301
|
+
: rule.action === 'warn'
|
|
302
|
+
? 'warn'
|
|
303
|
+
: 'block',
|
|
304
|
+
reason: hold
|
|
305
|
+
? `Held for human review: ${rule.reason || `SOP ${rule.id}`}`
|
|
306
|
+
: rule.reason || `Blocked by SOP ${rule.id}`,
|
|
307
|
+
rationale: guardrail
|
|
308
|
+
? 'Projected from a cited policy guardrail by the control plane (LLD #71).'
|
|
309
|
+
: hold
|
|
310
|
+
? 'Resolved from a REQUIRE_APPROVAL: SOP title by the control plane.'
|
|
311
|
+
: 'Resolved from a BLOCK: SOP title by the control plane.',
|
|
312
|
+
matches: [],
|
|
313
|
+
notMatches: [],
|
|
314
|
+
...(argPattern ? { argPattern } : {}),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/** Fetches resolved policy for the workspace. Returns null on any failure —
|
|
318
|
+
* the caller keeps the previous snapshot rather than replacing it with nothing. */
|
|
319
|
+
export async function fetchResolvedPolicy(opts) {
|
|
320
|
+
const url = `${stripEnd(opts.controlPlaneUrl, '/')}/api/v1/policy/resolve` +
|
|
321
|
+
`?workspaceId=${encodeURIComponent(opts.workspaceId)}`;
|
|
322
|
+
try {
|
|
323
|
+
const res = await fetch(url, {
|
|
324
|
+
headers: { Authorization: `Bearer ${opts.apiKey}` },
|
|
325
|
+
signal: AbortSignal.timeout(10_000),
|
|
326
|
+
});
|
|
327
|
+
if (!res.ok) {
|
|
328
|
+
log.warn({ action: 'policy_fetch_failed', status: res.status }, 'Policy resolve returned non-OK');
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
const body = (await res.json());
|
|
332
|
+
if (typeof body !== 'object' || body === null)
|
|
333
|
+
return null;
|
|
334
|
+
const rec = body;
|
|
335
|
+
const rules = Array.isArray(rec.sopRules) ? rec.sopRules : [];
|
|
336
|
+
return {
|
|
337
|
+
workspaceId: typeof rec.workspaceId === 'string' ? rec.workspaceId : opts.workspaceId,
|
|
338
|
+
interventionMode: typeof rec.interventionMode === 'string' ? rec.interventionMode : 'TRANSPARENT',
|
|
339
|
+
sopRules: rules.filter((r) => {
|
|
340
|
+
if (typeof r !== 'object' || r === null)
|
|
341
|
+
return false;
|
|
342
|
+
const x = r;
|
|
343
|
+
return (typeof x.id === 'string' &&
|
|
344
|
+
typeof x.toolPattern === 'string' &&
|
|
345
|
+
typeof x.action === 'string' &&
|
|
346
|
+
// Optional, but if present it must be a string — a non-string here
|
|
347
|
+
// would flow into `new RegExp` and the base64 encoder downstream.
|
|
348
|
+
(x.argPattern === undefined || typeof x.argPattern === 'string'));
|
|
349
|
+
}),
|
|
350
|
+
// `allowedServers` — the exact field name `evaluate.ts` and
|
|
351
|
+
// `lib/mcpCuration.ts` both use. Absent or wrongly-typed degrades to
|
|
352
|
+
// `[]`, same as `sopRules` above: unrestricted, not a fetch failure.
|
|
353
|
+
mcpAllowedServers: Array.isArray(rec.allowedServers)
|
|
354
|
+
? rec.allowedServers.filter((s) => typeof s === 'string')
|
|
355
|
+
: [],
|
|
356
|
+
sqlDropStrictBlock: rec.sqlDropStrictBlock === true,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
log.warn({ action: 'policy_fetch_failed', err }, 'Policy resolve unreachable');
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* SILENT_LOG means observe, do not intervene (docs/guides/policies.md: the
|
|
366
|
+
* call is permitted to run, the trace is tagged for audit). It demotes the
|
|
367
|
+
* dynamic tier to the advisory severity — it does NOT reach the static floor,
|
|
368
|
+
* which stays enforced. If one settings string could disarm the floor, the
|
|
369
|
+
* floor would not be a floor.
|
|
370
|
+
*
|
|
371
|
+
* This used to compare against 'SHADOW', a value intervention_mode_type
|
|
372
|
+
* (TRANSPARENT|OPAQUE|SILENT_LOG — packages/db/src/enums.ts) can never
|
|
373
|
+
* produce, so the observe-only branch was dead and a SILENT_LOG workspace
|
|
374
|
+
* shipped fully-blocking snapshots.
|
|
375
|
+
*
|
|
376
|
+
* Extracted so `buildSnapshotRules` and `writePolicySnapshot`'s `#mcpservers`
|
|
377
|
+
* header compute "is this workspace observe-only" the same way once, rather
|
|
378
|
+
* than as two copies of the comparison that could drift.
|
|
379
|
+
*/
|
|
380
|
+
function isSilentLogMode(policy) {
|
|
381
|
+
return policy.interventionMode.toUpperCase() === 'SILENT_LOG';
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Drops MCP server names that would corrupt the comma-joined `#mcpservers`
|
|
385
|
+
* `.rules` header line — a name carrying a comma would be misread as two
|
|
386
|
+
* server names, and a tab or other whitespace would collide with the
|
|
387
|
+
* `.rules` file's own column separator the moment anyone looked at the line
|
|
388
|
+
* next to a rule row. Logged, not silently dropped: a server an operator
|
|
389
|
+
* configured and then watched vanish from enforcement needs to know why,
|
|
390
|
+
* the same discipline `validateRule` follows for a rejected SOP pattern.
|
|
391
|
+
*/
|
|
392
|
+
function sanitizeMcpServerNames(names) {
|
|
393
|
+
const out = [];
|
|
394
|
+
// Defensive against a caller that built a ResolvedPolicy by hand without
|
|
395
|
+
// TypeScript actually checking it — this repo's test files are outside
|
|
396
|
+
// `tsconfig.json`'s `include`, so `tsc --noEmit` does not catch a test
|
|
397
|
+
// constructing one with `mcpAllowedServers` omitted, and a JS consumer of
|
|
398
|
+
// this exported function is not checked at all. `Array.isArray` costs
|
|
399
|
+
// nothing on the real path, where `fetchResolvedPolicy` already guarantees
|
|
400
|
+
// an array.
|
|
401
|
+
if (!Array.isArray(names))
|
|
402
|
+
return out;
|
|
403
|
+
for (const raw of names) {
|
|
404
|
+
if (typeof raw !== 'string')
|
|
405
|
+
continue;
|
|
406
|
+
const name = raw.trim();
|
|
407
|
+
if (!name)
|
|
408
|
+
continue;
|
|
409
|
+
if (/[\s,]/.test(name)) {
|
|
410
|
+
log.warn({ action: 'mcp_server_name_rejected', name: raw }, 'MCP server name contains whitespace or a comma — dropped rather than corrupting the .rules #mcpservers header');
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
out.push(name);
|
|
414
|
+
}
|
|
415
|
+
return out;
|
|
416
|
+
}
|
|
417
|
+
/** Builds the rule set a snapshot would carry, without writing it. Exported so
|
|
418
|
+
* a test can assert the contents rather than re-deriving them. */
|
|
419
|
+
export function buildSnapshotRules(policy, localHoldTokens = []) {
|
|
420
|
+
// `shadow` names the advisory GuardPattern severity these rules are demoted
|
|
421
|
+
// to; the workspace-level trigger is intervention mode SILENT_LOG.
|
|
422
|
+
const shadow = isSilentLogMode(policy);
|
|
423
|
+
const sopRules = policy.sopRules
|
|
424
|
+
.map((r) => toGuardPattern(r, shadow))
|
|
425
|
+
.filter((r) => r !== null);
|
|
426
|
+
const localHolds = [...new Set(localHoldTokens.map((t) => t.trim()).filter(Boolean))]
|
|
427
|
+
.map((t) => localHoldPattern(t, shadow))
|
|
428
|
+
.filter((r) => r !== null);
|
|
429
|
+
const destructive = DESTRUCTIVE_COMMAND_PATTERNS.map((p) => {
|
|
430
|
+
// Wave 7 (audit-remediation): `destructive.sql_drop` is a per-rule
|
|
431
|
+
// override, evaluated BEFORE the tier-wide ramp below — it was never
|
|
432
|
+
// part of `DESTRUCTIVE_TIER_SEVERITY`'s promotion (its own static
|
|
433
|
+
// `severity` is `'warn'`, not `'block'`, so that ternary never reaches
|
|
434
|
+
// it), and this flag is opt-in per workspace, not evidence-gated the way
|
|
435
|
+
// the six-pattern ramp is. Shadow mode still wins over both: SILENT_LOG
|
|
436
|
+
// means observe-only across the whole dynamic tier, no exceptions.
|
|
437
|
+
if (p.id === 'destructive.sql_drop' && !shadow && policy.sqlDropStrictBlock) {
|
|
438
|
+
return { ...p, severity: 'block' };
|
|
439
|
+
}
|
|
440
|
+
return {
|
|
441
|
+
...p,
|
|
442
|
+
// A `warn` pattern stays `warn` regardless; only the `block` ones are held
|
|
443
|
+
// back by the tier gate.
|
|
444
|
+
severity: shadow
|
|
445
|
+
? 'shadow'
|
|
446
|
+
: p.severity === 'block'
|
|
447
|
+
? DESTRUCTIVE_TIER_SEVERITY
|
|
448
|
+
: 'warn',
|
|
449
|
+
};
|
|
450
|
+
});
|
|
451
|
+
// The snapshot-delivered promotion of the skill-directory-write floor rules
|
|
452
|
+
// to `block` — see SKILL_SURFACE_TIER_SEVERITY. `.tier` suffixed onto the
|
|
453
|
+
// floor's own id (`skill_surface.agents_skills_write` ->
|
|
454
|
+
// `skill_surface.agents_skills_write.tier`) so this entry can never collide
|
|
455
|
+
// with `staticFloorPatterns()`'s compiled-in copy of the same pattern: both
|
|
456
|
+
// are meant to be present and matching the same path at once, one warn (the
|
|
457
|
+
// degraded-mode baseline) and one block (the steady state), and
|
|
458
|
+
// `assertGuardTableSane`'s uniqueness check would reject an accidental
|
|
459
|
+
// duplicate id the moment either table tried to load. Mirrors the
|
|
460
|
+
// destructive tier's own shadow handling: SILENT_LOG means observe, don't
|
|
461
|
+
// act, on the dynamic tier only — the floor's warn rule is unaffected
|
|
462
|
+
// either way.
|
|
463
|
+
const skillSurface = SKILL_SURFACE_PATTERNS.map((p) => ({
|
|
464
|
+
...p,
|
|
465
|
+
id: `${p.id}.tier`,
|
|
466
|
+
severity: shadow ? 'shadow' : SKILL_SURFACE_TIER_SEVERITY,
|
|
467
|
+
}));
|
|
468
|
+
return [...sopRules, ...localHolds, ...destructive, ...skillSurface];
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* A local `review_before:` token as a hold rule. `action:*` tokens match the
|
|
472
|
+
* gate's action subject (the tokens it classifies a shell command to);
|
|
473
|
+
* anything else is a tool name. Case-insensitive, as the bespoke Claude Code
|
|
474
|
+
* hold was. A token that is not a plain identifier is refused with a log line
|
|
475
|
+
* rather than escaped into a regex nobody can read back.
|
|
476
|
+
*/
|
|
477
|
+
function localHoldPattern(token, shadow) {
|
|
478
|
+
if (!/^[A-Za-z0-9_:.-]+$/.test(token)) {
|
|
479
|
+
log.warn({ action: 'local_hold_token_rejected', token }, 'review_before token is not a plain identifier — not shipped to the gate');
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
// The identifier check above already excludes every metacharacter; the
|
|
483
|
+
// full escape is still applied so the pattern is safe by construction and
|
|
484
|
+
// not by the guard three lines up.
|
|
485
|
+
const source = ` (${token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}) `;
|
|
486
|
+
return {
|
|
487
|
+
id: `sop.local.review_before.${token}`,
|
|
488
|
+
source,
|
|
489
|
+
subject: token.startsWith('action:') ? 'action' : 'tool',
|
|
490
|
+
ignoreCase: true,
|
|
491
|
+
severity: shadow ? 'shadow' : 'hold',
|
|
492
|
+
reason: `Held for human review: ${token} — declared in review_before:`,
|
|
493
|
+
rationale: 'A local SOP or the workspace settings asked to see this action before it runs.',
|
|
494
|
+
matches: [],
|
|
495
|
+
notMatches: [],
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Writes the snapshot atomically.
|
|
500
|
+
*
|
|
501
|
+
* Two artifacts from one source: the canonical JSON, and a tab-separated
|
|
502
|
+
* `.rules` projection so the five bash gates need no JSON parser on the decision
|
|
503
|
+
* path. Both carry the same digest, so a test — and the gate — can tell that the
|
|
504
|
+
* projection really came from the JSON.
|
|
505
|
+
*/
|
|
506
|
+
export async function writePolicySnapshot(policy, snapshotDir = DEFAULT_SNAPSHOT_DIR, localHoldTokens = []) {
|
|
507
|
+
const rules = buildSnapshotRules(policy, localHoldTokens);
|
|
508
|
+
const lines = rules.map(toRulesLine);
|
|
509
|
+
const digest = createHash('sha256').update(lines.join('\n')).digest('hex').slice(0, 32);
|
|
510
|
+
// One timestamp shared by both artifacts. Two `new Date()` calls would put
|
|
511
|
+
// different values in the JSON and the .rules, so a reader comparing them
|
|
512
|
+
// would see drift that is not there.
|
|
513
|
+
const generatedAt = new Date().toISOString();
|
|
514
|
+
// M3: the per-server MCP allowlist, sanitised once and reused for both
|
|
515
|
+
// artifacts so the JSON and the `.rules` header can never disagree about
|
|
516
|
+
// which names survived. Severity follows SILENT_LOG the same way the
|
|
517
|
+
// dynamic tier's rules do: certain, just not acted on.
|
|
518
|
+
//
|
|
519
|
+
// Deliberately OUTSIDE `lines`/`digest` above — the digest covers RULE lines
|
|
520
|
+
// only, matching the trust model the `#workspace` header already has (parsed
|
|
521
|
+
// and integrity-checked by workspace-id comparison, not by the digest). A
|
|
522
|
+
// `#mcpservers` header is the same kind of metadata line, not a rule, so it
|
|
523
|
+
// must not change what `lines.join('\n')` hashes to.
|
|
524
|
+
const mcpServers = sanitizeMcpServerNames(policy.mcpAllowedServers);
|
|
525
|
+
const mcpSeverity = isSilentLogMode(policy) ? 'shadow' : 'block';
|
|
526
|
+
await fs.mkdir(snapshotDir, { recursive: true });
|
|
527
|
+
const json = JSON.stringify({
|
|
528
|
+
_comment: 'Intutic policy snapshot — auto-generated. DO NOT EDIT.',
|
|
529
|
+
version: 1,
|
|
530
|
+
/**
|
|
531
|
+
* Which emitted evaluator these rules were compiled for.
|
|
532
|
+
*
|
|
533
|
+
* The `.rules` column layout and the normalisation contract are part of
|
|
534
|
+
* the gate body, not of this file, so a snapshot is only meaningful to a
|
|
535
|
+
* gate of the same generation. Recording it here is what lets a support
|
|
536
|
+
* conversation start from "which gate wrote this" instead of guessing —
|
|
537
|
+
* and it is the reason `GATE_VERSION` exists at all rather than being a
|
|
538
|
+
* constant nothing reads.
|
|
539
|
+
*/
|
|
540
|
+
gateVersion: GATE_VERSION,
|
|
541
|
+
workspaceId: policy.workspaceId,
|
|
542
|
+
generatedAt,
|
|
543
|
+
interventionMode: policy.interventionMode,
|
|
544
|
+
digest,
|
|
545
|
+
// M3: sanitised, not `policy.mcpAllowedServers` verbatim — a name this
|
|
546
|
+
// module rejected for the `.rules` header must not silently survive in
|
|
547
|
+
// the JSON, or the two artifacts would disagree about what is enforced.
|
|
548
|
+
// Empty means unrestricted, same convention as `allowedServers` at the
|
|
549
|
+
// control plane (`readMcpCurationSettings`).
|
|
550
|
+
mcpAllowedServers: mcpServers,
|
|
551
|
+
/**
|
|
552
|
+
* The resolve response verbatim, alongside the gate projection below.
|
|
553
|
+
*
|
|
554
|
+
* These two are **not** interchangeable and the difference has teeth.
|
|
555
|
+
* `rules` is a *gate artifact*: patterns rewritten into space-padded EREs,
|
|
556
|
+
* `warn` and `require_approval` already discarded, ids prefixed `sop.`.
|
|
557
|
+
* `sopRules` is what the control plane actually said — `{id, toolPattern,
|
|
558
|
+
* action, reason}`, every action.
|
|
559
|
+
*
|
|
560
|
+
* Carrying both exists because the MCP proxy's policy cache consumes
|
|
561
|
+
* `{toolPattern, action}` and its `isSopRule` guard drops anything else.
|
|
562
|
+
* Feeding it `rules` would pass the outer parse and then silently yield
|
|
563
|
+
* *zero* enforceable rules — a cache that reports entries and enforces
|
|
564
|
+
* nothing, which is worse than the cold fetch it replaced. This field is
|
|
565
|
+
* what makes seeding safe; see `seedFromSnapshot` in policyCache.ts.
|
|
566
|
+
*/
|
|
567
|
+
sopRules: policy.sopRules,
|
|
568
|
+
rules: rules.map((r) => ({
|
|
569
|
+
id: r.id,
|
|
570
|
+
source: r.source,
|
|
571
|
+
subject: r.subject ?? 'any',
|
|
572
|
+
severity: r.severity,
|
|
573
|
+
ignoreCase: r.ignoreCase === true,
|
|
574
|
+
reason: r.reason,
|
|
575
|
+
// Plain text here; base64 only in the `.rules` projection, where a tab
|
|
576
|
+
// separator forces the encoding. JSON needs no such armour.
|
|
577
|
+
...(r.argPattern ? { argPattern: r.argPattern } : {}),
|
|
578
|
+
})),
|
|
579
|
+
}, null, 2);
|
|
580
|
+
// `#generated` is not decoration. Without it the five bash gates and the
|
|
581
|
+
// Python one physically cannot compute the snapshot's age from the file they
|
|
582
|
+
// read — only the JSON carries a timestamp, and they never open the JSON. A
|
|
583
|
+
// snapshot from last year enforced identically to one written a second ago,
|
|
584
|
+
// and nothing anywhere could say so.
|
|
585
|
+
// `#mcpservers <severity> <comma-joined-server-names>` — OMITTED entirely
|
|
586
|
+
// when the (sanitised) list is empty. This is the mandatory-mechanism/
|
|
587
|
+
// opt-in-effect split M3 is built on: the header-parsing and per-call
|
|
588
|
+
// allowlist check ship to every gate unconditionally, but they are a no-op
|
|
589
|
+
// until a workspace actually configures `mcpAllowedServers` — and "no
|
|
590
|
+
// header line" is how a v6 gate (and, for forward-compat, a hypothetical
|
|
591
|
+
// v5-reading-a-v6-file) tells "unrestricted" apart from "restricted to
|
|
592
|
+
// zero servers", which would otherwise block every MCP call by omission.
|
|
593
|
+
const mcpHeader = mcpServers.length > 0 ? `#mcpservers ${mcpSeverity} ${mcpServers.join(',')}\n` : '';
|
|
594
|
+
const rulesText = `# Intutic policy snapshot (projection of ${SNAPSHOT_JSON}) — DO NOT EDIT.\n` +
|
|
595
|
+
`# Columns: ${RULES_COLUMNS.join('\t')}\n` +
|
|
596
|
+
`#digest ${digest}\n` +
|
|
597
|
+
`#workspace ${policy.workspaceId}\n` +
|
|
598
|
+
`#generated ${generatedAt}\n` +
|
|
599
|
+
mcpHeader +
|
|
600
|
+
lines.join('\n') +
|
|
601
|
+
'\n';
|
|
602
|
+
await writeAtomic(path.join(snapshotDir, SNAPSHOT_JSON), json);
|
|
603
|
+
await writeAtomic(path.join(snapshotDir, SNAPSHOT_RULES), rulesText);
|
|
604
|
+
log.debug({ action: 'policy_snapshot_written', digest, ruleCount: rules.length, dir: snapshotDir }, 'Policy snapshot refreshed');
|
|
605
|
+
return { digest, ruleCount: rules.length };
|
|
606
|
+
}
|
|
607
|
+
async function writeAtomic(target, content) {
|
|
608
|
+
const tmp = target + '.tmp';
|
|
609
|
+
await fs.writeFile(tmp, content, { encoding: 'utf-8' });
|
|
610
|
+
// 0444: the daemon replaces this by rename, so it never needs write access to
|
|
611
|
+
// the file itself, and a read-only file is one more small obstacle to an agent
|
|
612
|
+
// editing it in place. The real protection is that every gate refuses to touch
|
|
613
|
+
// `.intutic/hooks` at all.
|
|
614
|
+
await fs.chmod(tmp, 0o444);
|
|
615
|
+
await fs.rename(tmp, target);
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Fetch and write in one call. Safe to run on every sync cycle.
|
|
619
|
+
*
|
|
620
|
+
* Never throws: policy refresh must not be able to take down the sync loop. A
|
|
621
|
+
* failed fetch leaves the previous snapshot in place, which is the correct
|
|
622
|
+
* degradation — stale rules stay enforced. If they expired into permissiveness,
|
|
623
|
+
* "kill the daemon and wait" would be a supported way to disarm governance.
|
|
624
|
+
*/
|
|
625
|
+
export async function refreshPolicySnapshot(opts) {
|
|
626
|
+
const policy = await fetchResolvedPolicy(opts);
|
|
627
|
+
if (!policy)
|
|
628
|
+
return null;
|
|
629
|
+
try {
|
|
630
|
+
return await writePolicySnapshot(policy, opts.snapshotDir ?? DEFAULT_SNAPSHOT_DIR, opts.localHoldTokens ?? []);
|
|
631
|
+
}
|
|
632
|
+
catch (err) {
|
|
633
|
+
log.warn({ action: 'policy_snapshot_write_failed', err }, 'Could not write policy snapshot');
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
//# sourceMappingURL=policySnapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policySnapshot.js","sourceRoot":"","sources":["../../src/lib/policySnapshot.ts"],"names":[],"mappings":"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"}
|