patchwork-os 1.2.0-beta.2.canary.781 → 1.2.0-beta.2.canary.783
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/dist/activityLog.js +6 -1
- package/dist/activityLog.js.map +1 -1
- package/dist/approvalQueue.js +15 -0
- package/dist/approvalQueue.js.map +1 -1
- package/dist/bridge.js +32 -1
- package/dist/bridge.js.map +1 -1
- package/dist/claudeDriver.d.ts +10 -0
- package/dist/claudeDriver.js +27 -4
- package/dist/claudeDriver.js.map +1 -1
- package/dist/claudeOrchestrator.d.ts +5 -0
- package/dist/claudeOrchestrator.js +94 -6
- package/dist/claudeOrchestrator.js.map +1 -1
- package/dist/commands/patchworkInit.js +10 -0
- package/dist/commands/patchworkInit.js.map +1 -1
- package/dist/commands/policyExplain.d.ts +50 -0
- package/dist/commands/policyExplain.js +221 -0
- package/dist/commands/policyExplain.js.map +1 -0
- package/dist/commands/profile.d.ts +17 -0
- package/dist/commands/profile.js +85 -0
- package/dist/commands/profile.js.map +1 -0
- package/dist/commands/recipe.d.ts +8 -0
- package/dist/commands/recipe.js +64 -0
- package/dist/commands/recipe.js.map +1 -1
- package/dist/commands/recipeInstall.js +19 -0
- package/dist/commands/recipeInstall.js.map +1 -1
- package/dist/connectors/baseConnector.js +5 -0
- package/dist/connectors/baseConnector.js.map +1 -1
- package/dist/connectors/secrets.js +10 -2
- package/dist/connectors/secrets.js.map +1 -1
- package/dist/connectors/tokenStorage.d.ts +9 -0
- package/dist/connectors/tokenStorage.js +27 -0
- package/dist/connectors/tokenStorage.js.map +1 -1
- package/dist/decisionTraceLog.js +4 -3
- package/dist/decisionTraceLog.js.map +1 -1
- package/dist/drivers/claude/envSanitizer.d.ts +39 -0
- package/dist/drivers/claude/envSanitizer.js +114 -0
- package/dist/drivers/claude/envSanitizer.js.map +1 -1
- package/dist/drivers/claude/subprocess.d.ts +12 -0
- package/dist/drivers/claude/subprocess.js +114 -25
- package/dist/drivers/claude/subprocess.js.map +1 -1
- package/dist/drivers/claude/subprocessSettings.d.ts +4 -1
- package/dist/drivers/claude/subprocessSettings.js +24 -7
- package/dist/drivers/claude/subprocessSettings.js.map +1 -1
- package/dist/drivers/codex/subprocess.d.ts +19 -0
- package/dist/drivers/codex/subprocess.js +40 -8
- package/dist/drivers/codex/subprocess.js.map +1 -1
- package/dist/drivers/gemini/index.d.ts +24 -0
- package/dist/drivers/gemini/index.js +76 -13
- package/dist/drivers/gemini/index.js.map +1 -1
- package/dist/drivers/types.d.ts +14 -0
- package/dist/drivers/types.js.map +1 -1
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/governance/doctorReport.d.ts +45 -0
- package/dist/governance/doctorReport.js +251 -0
- package/dist/governance/doctorReport.js.map +1 -0
- package/dist/governance/effectivePolicy.d.ts +134 -0
- package/dist/governance/effectivePolicy.js +322 -0
- package/dist/governance/effectivePolicy.js.map +1 -0
- package/dist/governance/killSwitchPolicy.d.ts +30 -0
- package/dist/governance/killSwitchPolicy.js +52 -0
- package/dist/governance/killSwitchPolicy.js.map +1 -0
- package/dist/governance/pluginPolicy.d.ts +111 -0
- package/dist/governance/pluginPolicy.js +247 -0
- package/dist/governance/pluginPolicy.js.map +1 -0
- package/dist/governance/profile.d.ts +153 -0
- package/dist/governance/profile.js +181 -0
- package/dist/governance/profile.js.map +1 -0
- package/dist/governance/secretValues.d.ts +89 -0
- package/dist/governance/secretValues.js +286 -0
- package/dist/governance/secretValues.js.map +1 -0
- package/dist/governance/toolFacts.d.ts +11 -0
- package/dist/governance/toolFacts.js +46 -0
- package/dist/governance/toolFacts.js.map +1 -0
- package/dist/governance/untrustedContent.d.ts +43 -0
- package/dist/governance/untrustedContent.js +81 -0
- package/dist/governance/untrustedContent.js.map +1 -0
- package/dist/index.js +73 -5
- package/dist/index.js.map +1 -1
- package/dist/logger.js +7 -6
- package/dist/logger.js.map +1 -1
- package/dist/patchworkConfig.d.ts +22 -0
- package/dist/patchworkConfig.js.map +1 -1
- package/dist/pluginLoader.d.ts +10 -2
- package/dist/pluginLoader.js +12 -8
- package/dist/pluginLoader.js.map +1 -1
- package/dist/recipeOrchestration.d.ts +0 -6
- package/dist/recipeOrchestration.js +80 -23
- package/dist/recipeOrchestration.js.map +1 -1
- package/dist/recipeRoutes.js +52 -0
- package/dist/recipeRoutes.js.map +1 -1
- package/dist/recipes/agentExecutor.d.ts +43 -3
- package/dist/recipes/agentExecutor.js +55 -7
- package/dist/recipes/agentExecutor.js.map +1 -1
- package/dist/recipes/approvalRequest.d.ts +8 -0
- package/dist/recipes/approvalRequest.js.map +1 -1
- package/dist/recipes/chainedRunner.d.ts +18 -5
- package/dist/recipes/chainedRunner.js +107 -12
- package/dist/recipes/chainedRunner.js.map +1 -1
- package/dist/recipes/haltCategory.d.ts +4 -0
- package/dist/recipes/haltCategory.js +4 -0
- package/dist/recipes/haltCategory.js.map +1 -1
- package/dist/recipes/schema.d.ts +5 -1
- package/dist/recipes/schemaGenerator.js +12 -1
- package/dist/recipes/schemaGenerator.js.map +1 -1
- package/dist/recipes/stepObservation.d.ts +15 -0
- package/dist/recipes/stepObservation.js +27 -22
- package/dist/recipes/stepObservation.js.map +1 -1
- package/dist/recipes/templateEngine.d.ts +11 -1
- package/dist/recipes/templateEngine.js +12 -2
- package/dist/recipes/templateEngine.js.map +1 -1
- package/dist/recipes/toolRegistry.d.ts +3 -1
- package/dist/recipes/toolRegistry.js +9 -3
- package/dist/recipes/toolRegistry.js.map +1 -1
- package/dist/recipes/tools/http.d.ts +5 -2
- package/dist/recipes/tools/http.js +29 -10
- package/dist/recipes/tools/http.js.map +1 -1
- package/dist/recipes/validation.d.ts +10 -1
- package/dist/recipes/validation.js +51 -1
- package/dist/recipes/validation.js.map +1 -1
- package/dist/recipes/yamlRunner.d.ts +46 -5
- package/dist/recipes/yamlRunner.js +173 -21
- package/dist/recipes/yamlRunner.js.map +1 -1
- package/dist/schemas/recipe.v1.json +60 -3
- package/dist/server.js +15 -0
- package/dist/server.js.map +1 -1
- package/dist/ssrfGuard.d.ts +89 -5
- package/dist/ssrfGuard.js +250 -5
- package/dist/ssrfGuard.js.map +1 -1
- package/dist/tools/httpClient.js +23 -165
- package/dist/tools/httpClient.js.map +1 -1
- package/dist/transport.js +41 -0
- package/dist/transport.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governance profile — the ONE concept that turns Patchwork's opt-in controls
|
|
3
|
+
* into a coherent posture.
|
|
4
|
+
*
|
|
5
|
+
* Phase 0 finding: every headline control (approval gate, automated-trigger
|
|
6
|
+
* gating, worker authority, policy matrix, agent sandbox, plugin loading, the
|
|
7
|
+
* kill-switch failure mode) was independently opt-in and independently
|
|
8
|
+
* fail-open, so a fresh install enforced nothing while looking governed on the
|
|
9
|
+
* dashboard. This module resolves a single `profile` setting into the existing
|
|
10
|
+
* primitives — it adds no new decision function of its own.
|
|
11
|
+
*
|
|
12
|
+
* Two modes, deliberately only two:
|
|
13
|
+
*
|
|
14
|
+
* - `compat` — byte-identical to pre-profile behaviour. This is what an
|
|
15
|
+
* install with no `profile` key resolves to, so an existing
|
|
16
|
+
* installation never changes behaviour by upgrading.
|
|
17
|
+
* - `governed` — conservative defaults suitable for real business data.
|
|
18
|
+
* `patchwork init` writes this for NEW installs;
|
|
19
|
+
* `patchwork profile governed` opts an existing one in.
|
|
20
|
+
*
|
|
21
|
+
* The resolved profile is a plain value. Runtime enforcement, `patchwork
|
|
22
|
+
* policy explain` and `patchwork doctor` all read the SAME resolved value, so
|
|
23
|
+
* the explanation cannot describe a posture the runtime is not applying.
|
|
24
|
+
*
|
|
25
|
+
* Every field below maps to something that already exists; the comment on
|
|
26
|
+
* each names the primitive it feeds. Nothing here is a new flag.
|
|
27
|
+
*/
|
|
28
|
+
export const PROFILE_MODES = ["governed", "compat"];
|
|
29
|
+
/**
|
|
30
|
+
* Pure: config → profile. Unknown or absent `profile` ⇒ compat (never a
|
|
31
|
+
* guess). An explicit but unrecognised value is reported by `doctor`, not
|
|
32
|
+
* silently promoted to governed.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveProfile(cfg) {
|
|
35
|
+
const raw = cfg?.profile;
|
|
36
|
+
const declared = raw === "governed" || raw === "compat";
|
|
37
|
+
const mode = raw === "governed" ? "governed" : "compat";
|
|
38
|
+
const configuredGate = cfg?.approvalGate === "high" ||
|
|
39
|
+
cfg?.approvalGate === "all" ||
|
|
40
|
+
cfg?.approvalGate === "off"
|
|
41
|
+
? cfg.approvalGate
|
|
42
|
+
: "off";
|
|
43
|
+
if (mode === "compat") {
|
|
44
|
+
return {
|
|
45
|
+
mode,
|
|
46
|
+
declared,
|
|
47
|
+
approvalGate: configuredGate,
|
|
48
|
+
gateAutomatedRuns: false,
|
|
49
|
+
workerAuthority: false,
|
|
50
|
+
policyEnforce: false,
|
|
51
|
+
agentContainment: "opt-in",
|
|
52
|
+
pluginPolicy: "open",
|
|
53
|
+
killSwitchFailClosed: false,
|
|
54
|
+
unknownWriteTools: "allow",
|
|
55
|
+
unregisteredTools: "skip",
|
|
56
|
+
untrustedEnvelope: false,
|
|
57
|
+
recipeOptOutHonoured: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
mode,
|
|
62
|
+
declared,
|
|
63
|
+
approvalGate: configuredGate === "all" ? "all" : "high",
|
|
64
|
+
gateAutomatedRuns: true,
|
|
65
|
+
workerAuthority: true,
|
|
66
|
+
policyEnforce: true,
|
|
67
|
+
agentContainment: "enforced",
|
|
68
|
+
pluginPolicy: "allowlist",
|
|
69
|
+
killSwitchFailClosed: true,
|
|
70
|
+
unknownWriteTools: "gate",
|
|
71
|
+
unregisteredTools: "refuse",
|
|
72
|
+
untrustedEnvelope: true,
|
|
73
|
+
recipeOptOutHonoured: false,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export const COMPAT_PROFILE = Object.freeze(resolveProfile(undefined));
|
|
77
|
+
export const GOVERNED_PROFILE = Object.freeze(resolveProfile({ profile: "governed" }));
|
|
78
|
+
export function isGoverned(p) {
|
|
79
|
+
return p?.mode === "governed";
|
|
80
|
+
}
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Process-wide active profile.
|
|
83
|
+
//
|
|
84
|
+
// Set ONCE by the bridge at startup from the loaded config (and by the CLI
|
|
85
|
+
// verbs that need it). Everything that enforces reads this rather than
|
|
86
|
+
// re-deriving from config, so a mid-session config edit cannot leave two
|
|
87
|
+
// components disagreeing about the posture. Defaults to compat, which is the
|
|
88
|
+
// no-behaviour-change value.
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
let ACTIVE = COMPAT_PROFILE;
|
|
91
|
+
export function setActiveProfile(p) {
|
|
92
|
+
ACTIVE = p;
|
|
93
|
+
}
|
|
94
|
+
export function activeProfile() {
|
|
95
|
+
return ACTIVE;
|
|
96
|
+
}
|
|
97
|
+
/** Test seam. */
|
|
98
|
+
export function _resetActiveProfileForTesting() {
|
|
99
|
+
ACTIVE = COMPAT_PROFILE;
|
|
100
|
+
}
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Agent containment — what a spawned CLI agent may touch under a profile.
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
/**
|
|
105
|
+
* Tools a contained agent step may use. Read-only by construction: nothing
|
|
106
|
+
* here writes a file, runs a command, or reaches the network. Names are the
|
|
107
|
+
* Claude Code built-in tool names; other drivers map them to their own
|
|
108
|
+
* equivalents (Gemini/Codex have coarser knobs — see each driver).
|
|
109
|
+
*/
|
|
110
|
+
export const CONTAINED_AGENT_ALLOWED_TOOLS = Object.freeze([
|
|
111
|
+
"Read",
|
|
112
|
+
"Glob",
|
|
113
|
+
"Grep",
|
|
114
|
+
"LS",
|
|
115
|
+
]);
|
|
116
|
+
/**
|
|
117
|
+
* Tools that are DENIED in every mode when the profile is governed, even when
|
|
118
|
+
* a recipe widens `allowedTools`. Network egress and shell are the two
|
|
119
|
+
* capabilities that turn a prompt injection into exfiltration; a recipe that
|
|
120
|
+
* genuinely needs them must say so with `sandbox: { network: true }` /
|
|
121
|
+
* `sandbox: { shell: true }`, which `policy explain` reports as a widening.
|
|
122
|
+
*/
|
|
123
|
+
export const CONTAINED_AGENT_DENIED_TOOLS = Object.freeze([
|
|
124
|
+
"WebFetch",
|
|
125
|
+
"WebSearch",
|
|
126
|
+
"Bash",
|
|
127
|
+
]);
|
|
128
|
+
/**
|
|
129
|
+
* Pure: (profile, step request) → containment the driver must apply.
|
|
130
|
+
*
|
|
131
|
+
* Under `compat` this reproduces today's behaviour exactly: containment only
|
|
132
|
+
* when the step opted in via `sandbox: true`. Under `governed` the default is
|
|
133
|
+
* contained, and each widening is recorded so the explanation can show it.
|
|
134
|
+
*/
|
|
135
|
+
export function resolveAgentContainment(profile, req) {
|
|
136
|
+
const widenings = [];
|
|
137
|
+
if (profile.agentContainment !== "enforced") {
|
|
138
|
+
const enforced = req?.sandbox === true;
|
|
139
|
+
return {
|
|
140
|
+
enforced,
|
|
141
|
+
allowedTools: enforced ? [...(req?.allowedTools ?? [])] : [],
|
|
142
|
+
deniedTools: [...(req?.disallowedTools ?? [])],
|
|
143
|
+
envAllowlist: false,
|
|
144
|
+
mcpAccess: req?.mcpAccess === true,
|
|
145
|
+
widenings,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const allowed = new Set(CONTAINED_AGENT_ALLOWED_TOOLS);
|
|
149
|
+
for (const t of req?.allowedTools ?? []) {
|
|
150
|
+
if (!allowed.has(t))
|
|
151
|
+
widenings.push(`allowedTools+${t}`);
|
|
152
|
+
allowed.add(t);
|
|
153
|
+
}
|
|
154
|
+
const denied = new Set(CONTAINED_AGENT_DENIED_TOOLS);
|
|
155
|
+
for (const t of req?.disallowedTools ?? [])
|
|
156
|
+
denied.add(t);
|
|
157
|
+
if (req?.network === true) {
|
|
158
|
+
denied.delete("WebFetch");
|
|
159
|
+
denied.delete("WebSearch");
|
|
160
|
+
widenings.push("network");
|
|
161
|
+
}
|
|
162
|
+
if (req?.shell === true) {
|
|
163
|
+
denied.delete("Bash");
|
|
164
|
+
widenings.push("shell");
|
|
165
|
+
}
|
|
166
|
+
// A deny always beats an allow — a recipe cannot re-allow what it is denied.
|
|
167
|
+
for (const t of denied)
|
|
168
|
+
allowed.delete(t);
|
|
169
|
+
const mcpAccess = req?.mcpAccess === true;
|
|
170
|
+
if (mcpAccess)
|
|
171
|
+
widenings.push("mcpAccess");
|
|
172
|
+
return {
|
|
173
|
+
enforced: true,
|
|
174
|
+
allowedTools: [...allowed],
|
|
175
|
+
deniedTools: [...denied],
|
|
176
|
+
envAllowlist: true,
|
|
177
|
+
mcpAccess,
|
|
178
|
+
widenings,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/governance/profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAU,CAAC;AAuE7D;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAmC;IAEnC,MAAM,GAAG,GAAG,GAAG,EAAE,OAAO,CAAC;IACzB,MAAM,QAAQ,GAAG,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,QAAQ,CAAC;IACxD,MAAM,IAAI,GAAgB,GAAG,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC;IACrE,MAAM,cAAc,GAClB,GAAG,EAAE,YAAY,KAAK,MAAM;QAC5B,GAAG,EAAE,YAAY,KAAK,KAAK;QAC3B,GAAG,EAAE,YAAY,KAAK,KAAK;QACzB,CAAC,CAAC,GAAG,CAAC,YAAY;QAClB,CAAC,CAAC,KAAK,CAAC;IACZ,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO;YACL,IAAI;YACJ,QAAQ;YACR,YAAY,EAAE,cAAc;YAC5B,iBAAiB,EAAE,KAAK;YACxB,eAAe,EAAE,KAAK;YACtB,aAAa,EAAE,KAAK;YACpB,gBAAgB,EAAE,QAAQ;YAC1B,YAAY,EAAE,MAAM;YACpB,oBAAoB,EAAE,KAAK;YAC3B,iBAAiB,EAAE,OAAO;YAC1B,iBAAiB,EAAE,MAAM;YACzB,iBAAiB,EAAE,KAAK;YACxB,oBAAoB,EAAE,IAAI;SAC3B,CAAC;IACJ,CAAC;IACD,OAAO;QACL,IAAI;QACJ,QAAQ;QACR,YAAY,EAAE,cAAc,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;QACvD,iBAAiB,EAAE,IAAI;QACvB,eAAe,EAAE,IAAI;QACrB,aAAa,EAAE,IAAI;QACnB,gBAAgB,EAAE,UAAU;QAC5B,YAAY,EAAE,WAAW;QACzB,oBAAoB,EAAE,IAAI;QAC1B,iBAAiB,EAAE,MAAM;QACzB,iBAAiB,EAAE,QAAQ;QAC3B,iBAAiB,EAAE,IAAI;QACvB,oBAAoB,EAAE,KAAK;KAC5B,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAsB,MAAM,CAAC,MAAM,CAC5D,cAAc,CAAC,SAAS,CAAC,CAC1B,CAAC;AACF,MAAM,CAAC,MAAM,gBAAgB,GAAsB,MAAM,CAAC,MAAM,CAC9D,cAAc,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CACxC,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,CAAgC;IACzD,OAAO,CAAC,EAAE,IAAI,KAAK,UAAU,CAAC;AAChC,CAAC;AAED,8EAA8E;AAC9E,+BAA+B;AAC/B,EAAE;AACF,2EAA2E;AAC3E,uEAAuE;AACvE,yEAAyE;AACzE,6EAA6E;AAC7E,6BAA6B;AAC7B,8EAA8E;AAE9E,IAAI,MAAM,GAAsB,cAAc,CAAC;AAE/C,MAAM,UAAU,gBAAgB,CAAC,CAAoB;IACnD,MAAM,GAAG,CAAC,CAAC;AACb,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,iBAAiB;AACjB,MAAM,UAAU,6BAA6B;IAC3C,MAAM,GAAG,cAAc,CAAC;AAC1B,CAAC;AAED,8EAA8E;AAC9E,0EAA0E;AAC1E,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAsB,MAAM,CAAC,MAAM,CAAC;IAC5E,MAAM;IACN,MAAM;IACN,MAAM;IACN,IAAI;CACL,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAsB,MAAM,CAAC,MAAM,CAAC;IAC3E,UAAU;IACV,WAAW;IACX,MAAM;CACP,CAAC,CAAC;AA0BH;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAA0B,EAC1B,GAAmC;IAEnC,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,IAAI,OAAO,CAAC,gBAAgB,KAAK,UAAU,EAAE,CAAC;QAC5C,MAAM,QAAQ,GAAG,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;QACvC,OAAO;YACL,QAAQ;YACR,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;YAC5D,WAAW,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,eAAe,IAAI,EAAE,CAAC,CAAC;YAC9C,YAAY,EAAE,KAAK;YACnB,SAAS,EAAE,GAAG,EAAE,SAAS,KAAK,IAAI;YAClC,SAAS;SACV,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,6BAA6B,CAAC,CAAC;IAC/D,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,YAAY,IAAI,EAAE,EAAE,CAAC;QACxC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,SAAS,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QACzD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAS,4BAA4B,CAAC,CAAC;IAC7D,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,eAAe,IAAI,EAAE;QAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC1D,IAAI,GAAG,EAAE,OAAO,KAAK,IAAI,EAAE,CAAC;QAC1B,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC1B,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC3B,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5B,CAAC;IACD,IAAI,GAAG,EAAE,KAAK,KAAK,IAAI,EAAE,CAAC;QACxB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACtB,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1B,CAAC;IACD,6EAA6E;IAC7E,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1C,MAAM,SAAS,GAAG,GAAG,EAAE,SAAS,KAAK,IAAI,CAAC;IAC1C,IAAI,SAAS;QAAE,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3C,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC;QAC1B,WAAW,EAAE,CAAC,GAAG,MAAM,CAAC;QACxB,YAAY,EAAE,IAAI;QAClB,SAAS;QACT,SAAS;KACV,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret-value registry — VALUE-based redaction, composed after key-based.
|
|
3
|
+
*
|
|
4
|
+
* `redactSensitive` in `src/recipes/stepObservation.ts` walks object KEYS:
|
|
5
|
+
* `authorization`, `api_key`, `token` … A secret that has been interpolated
|
|
6
|
+
* into a string — `body: '{"key":"{{env.API_KEY}}"}'`, a URL query, a nested
|
|
7
|
+
* JSON string, a model prompt, an error message quoting the request — has no
|
|
8
|
+
* sensitive key left to match and lands in `runs.jsonl`, the approval
|
|
9
|
+
* payload, the activity log and the orchestrator's task file in clear text.
|
|
10
|
+
*
|
|
11
|
+
* This module holds the VALUES the process has been handed as secrets and
|
|
12
|
+
* substitutes them wherever they reappear, whatever the surrounding key.
|
|
13
|
+
*
|
|
14
|
+
* Constraints, all deliberate:
|
|
15
|
+
*
|
|
16
|
+
* - The registry is process-local and memory-only. It is never persisted,
|
|
17
|
+
* never logged, and `JSON.stringify` / `util.inspect` of it reveal
|
|
18
|
+
* nothing — a registry that leaks is the largest secret index on the box.
|
|
19
|
+
* - Values shorter than `MIN_SECRET_LENGTH` are ignored. Redacting `abc`
|
|
20
|
+
* would rewrite ordinary prose, and a 7-character credential is not a
|
|
21
|
+
* credential this layer can protect without making every log unreadable.
|
|
22
|
+
* - Longest match first, so a value that is a prefix of another never
|
|
23
|
+
* leaves the tail of the longer one exposed.
|
|
24
|
+
* - Common encodings of each value are matched too: URL-encoded, base64,
|
|
25
|
+
* base64url and JSON-escaped. Cheap to compute once at registration;
|
|
26
|
+
* impossible to reconstruct at the sink.
|
|
27
|
+
* - The replacement names the SOURCE (`env`, `connector:gmail`), never the
|
|
28
|
+
* key and never any part of the value.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MIN_SECRET_LENGTH = 8;
|
|
31
|
+
/**
|
|
32
|
+
* Key-name patterns shared with `redactSensitive`. Normalised: lowercase with
|
|
33
|
+
* `-`, `_`, `.` and whitespace stripped, so `api_key`, `api-key`, `apiKey`
|
|
34
|
+
* and `API_KEY` all reduce to `apikey`.
|
|
35
|
+
*/
|
|
36
|
+
export declare const SENSITIVE_KEY_PATTERNS: readonly string[];
|
|
37
|
+
export declare function isSensitiveKeyName(key: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Register one secret value. `source` is a short label — `env`,
|
|
40
|
+
* `connector:gmail`, `config:apiKeys` — used verbatim in the replacement
|
|
41
|
+
* marker, so it must never carry the key name or any part of the value.
|
|
42
|
+
*/
|
|
43
|
+
export declare function registerSecretValue(value: string, source: string): void;
|
|
44
|
+
/**
|
|
45
|
+
* Walk an object and register every string value found under a key that
|
|
46
|
+
* matches `SENSITIVE_KEY_PATTERNS`. Nested objects and arrays are walked;
|
|
47
|
+
* cycles are tolerated. The key name itself is NOT recorded anywhere.
|
|
48
|
+
*/
|
|
49
|
+
export declare function registerSecretValues(obj: unknown, source: string, opts?: {
|
|
50
|
+
allKeys?: boolean;
|
|
51
|
+
}): void;
|
|
52
|
+
/**
|
|
53
|
+
* Register the values of a recipe's declared `type: env` block. Every value
|
|
54
|
+
* in that block is a secret by declaration — the recipe asked for it from the
|
|
55
|
+
* environment — regardless of its key name.
|
|
56
|
+
*
|
|
57
|
+
* To be called by the runners (`declaredRecipeEnv` callers) — see the report
|
|
58
|
+
* accompanying this module for the exact lines.
|
|
59
|
+
*/
|
|
60
|
+
export declare function registerEnvBlock(env: Record<string, string | undefined>): void;
|
|
61
|
+
/**
|
|
62
|
+
* Register a recipe's `vars:` — only values under sensitive key names, since
|
|
63
|
+
* ordinary vars (`repo`, `channel`) are not secrets and redacting them would
|
|
64
|
+
* hollow out the run log.
|
|
65
|
+
*/
|
|
66
|
+
export declare function registerSensitiveVars(vars: Record<string, unknown>): void;
|
|
67
|
+
/** The bridge's own bearer token. `bridge.ts` should call this at startup. */
|
|
68
|
+
export declare function registerBridgeToken(token: string): void;
|
|
69
|
+
export declare function secretValueCount(): number;
|
|
70
|
+
export declare function _resetSecretValuesForTesting(): void;
|
|
71
|
+
/**
|
|
72
|
+
* Replace every registered secret (and its cheap encodings) in `text`.
|
|
73
|
+
* Returns the input reference unchanged when nothing is registered or
|
|
74
|
+
* nothing matches, so the hot path allocates nothing.
|
|
75
|
+
*/
|
|
76
|
+
export declare function redactKnownSecrets(text: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* Walk any value and redact registered secrets inside every string, including
|
|
79
|
+
* object keys and strings that are themselves JSON. Structure is preserved;
|
|
80
|
+
* non-string leaves pass through; cycles become `"[circular]"`.
|
|
81
|
+
*
|
|
82
|
+
* A string that parses as JSON is parsed, redacted structurally and
|
|
83
|
+
* re-stringified — this catches a secret that sits inside a nested JSON
|
|
84
|
+
* document under a JSON-escaped form the flat pass might otherwise handle
|
|
85
|
+
* only partially. Anything that fails to parse falls back to the plain
|
|
86
|
+
* substring pass, which is sufficient on its own; the parse is belt and
|
|
87
|
+
* braces, never a requirement.
|
|
88
|
+
*/
|
|
89
|
+
export declare function redactKnownSecretsDeep(value: unknown): unknown;
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret-value registry — VALUE-based redaction, composed after key-based.
|
|
3
|
+
*
|
|
4
|
+
* `redactSensitive` in `src/recipes/stepObservation.ts` walks object KEYS:
|
|
5
|
+
* `authorization`, `api_key`, `token` … A secret that has been interpolated
|
|
6
|
+
* into a string — `body: '{"key":"{{env.API_KEY}}"}'`, a URL query, a nested
|
|
7
|
+
* JSON string, a model prompt, an error message quoting the request — has no
|
|
8
|
+
* sensitive key left to match and lands in `runs.jsonl`, the approval
|
|
9
|
+
* payload, the activity log and the orchestrator's task file in clear text.
|
|
10
|
+
*
|
|
11
|
+
* This module holds the VALUES the process has been handed as secrets and
|
|
12
|
+
* substitutes them wherever they reappear, whatever the surrounding key.
|
|
13
|
+
*
|
|
14
|
+
* Constraints, all deliberate:
|
|
15
|
+
*
|
|
16
|
+
* - The registry is process-local and memory-only. It is never persisted,
|
|
17
|
+
* never logged, and `JSON.stringify` / `util.inspect` of it reveal
|
|
18
|
+
* nothing — a registry that leaks is the largest secret index on the box.
|
|
19
|
+
* - Values shorter than `MIN_SECRET_LENGTH` are ignored. Redacting `abc`
|
|
20
|
+
* would rewrite ordinary prose, and a 7-character credential is not a
|
|
21
|
+
* credential this layer can protect without making every log unreadable.
|
|
22
|
+
* - Longest match first, so a value that is a prefix of another never
|
|
23
|
+
* leaves the tail of the longer one exposed.
|
|
24
|
+
* - Common encodings of each value are matched too: URL-encoded, base64,
|
|
25
|
+
* base64url and JSON-escaped. Cheap to compute once at registration;
|
|
26
|
+
* impossible to reconstruct at the sink.
|
|
27
|
+
* - The replacement names the SOURCE (`env`, `connector:gmail`), never the
|
|
28
|
+
* key and never any part of the value.
|
|
29
|
+
*/
|
|
30
|
+
import { inspect } from "node:util";
|
|
31
|
+
export const MIN_SECRET_LENGTH = 8;
|
|
32
|
+
const MAX_SECRET_LENGTH = 16 * 1024;
|
|
33
|
+
/**
|
|
34
|
+
* Key-name patterns shared with `redactSensitive`. Normalised: lowercase with
|
|
35
|
+
* `-`, `_`, `.` and whitespace stripped, so `api_key`, `api-key`, `apiKey`
|
|
36
|
+
* and `API_KEY` all reduce to `apikey`.
|
|
37
|
+
*/
|
|
38
|
+
export const SENSITIVE_KEY_PATTERNS = [
|
|
39
|
+
"authorization",
|
|
40
|
+
"xapikey",
|
|
41
|
+
"apikey",
|
|
42
|
+
"password",
|
|
43
|
+
"passwd",
|
|
44
|
+
"secret",
|
|
45
|
+
"token",
|
|
46
|
+
"cookie",
|
|
47
|
+
"session",
|
|
48
|
+
"privatekey",
|
|
49
|
+
"clientsecret",
|
|
50
|
+
"refreshtoken",
|
|
51
|
+
"accesstoken",
|
|
52
|
+
];
|
|
53
|
+
export function isSensitiveKeyName(key) {
|
|
54
|
+
const normalised = key.toLowerCase().replace(/[-_\s.]/g, "");
|
|
55
|
+
for (const pattern of SENSITIVE_KEY_PATTERNS) {
|
|
56
|
+
if (normalised.includes(pattern))
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
class SecretRegistry {
|
|
62
|
+
/** value → source label. A value registered twice keeps its first label. */
|
|
63
|
+
values = new Map();
|
|
64
|
+
/** Derived needles (raw + encodings), longest first. Rebuilt lazily. */
|
|
65
|
+
needles = null;
|
|
66
|
+
add(value, source) {
|
|
67
|
+
if (typeof value !== "string")
|
|
68
|
+
return;
|
|
69
|
+
if (value.length < MIN_SECRET_LENGTH || value.length > MAX_SECRET_LENGTH)
|
|
70
|
+
return;
|
|
71
|
+
if (value.trim().length < MIN_SECRET_LENGTH)
|
|
72
|
+
return;
|
|
73
|
+
if (this.values.has(value))
|
|
74
|
+
return;
|
|
75
|
+
this.values.set(value, sanitiseSource(source));
|
|
76
|
+
this.needles = null;
|
|
77
|
+
}
|
|
78
|
+
count() {
|
|
79
|
+
return this.values.size;
|
|
80
|
+
}
|
|
81
|
+
clear() {
|
|
82
|
+
this.values.clear();
|
|
83
|
+
this.needles = null;
|
|
84
|
+
}
|
|
85
|
+
getNeedles() {
|
|
86
|
+
if (this.needles)
|
|
87
|
+
return this.needles;
|
|
88
|
+
const seen = new Map();
|
|
89
|
+
for (const [value, source] of this.values) {
|
|
90
|
+
for (const enc of encodings(value)) {
|
|
91
|
+
if (enc.length < MIN_SECRET_LENGTH)
|
|
92
|
+
continue;
|
|
93
|
+
if (!seen.has(enc))
|
|
94
|
+
seen.set(enc, source);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const out = [];
|
|
98
|
+
for (const [text, source] of seen)
|
|
99
|
+
out.push({ text, source });
|
|
100
|
+
out.sort((a, b) => b.text.length - a.text.length);
|
|
101
|
+
this.needles = out;
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
// ── Never reveal contents ──────────────────────────────────────────────
|
|
105
|
+
toJSON() {
|
|
106
|
+
return { secretValues: this.values.size };
|
|
107
|
+
}
|
|
108
|
+
toString() {
|
|
109
|
+
return `[SecretRegistry ${this.values.size} value(s)]`;
|
|
110
|
+
}
|
|
111
|
+
[inspect.custom]() {
|
|
112
|
+
return this.toString();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function sanitiseSource(source) {
|
|
116
|
+
const s = String(source ?? "").trim();
|
|
117
|
+
if (!s)
|
|
118
|
+
return "secret";
|
|
119
|
+
// Keep labels short and free of characters that would break the marker.
|
|
120
|
+
return s.replace(/[[\]\r\n]/g, "").slice(0, 48);
|
|
121
|
+
}
|
|
122
|
+
/** Every cheap encoding under which a value might reappear in text. */
|
|
123
|
+
function encodings(value) {
|
|
124
|
+
const out = new Set([value]);
|
|
125
|
+
try {
|
|
126
|
+
out.add(encodeURIComponent(value));
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
/* lone surrogates — skip */
|
|
130
|
+
}
|
|
131
|
+
const b64 = Buffer.from(value, "utf8").toString("base64");
|
|
132
|
+
out.add(b64);
|
|
133
|
+
out.add(b64.replace(/=+$/, ""));
|
|
134
|
+
out.add(Buffer.from(value, "utf8").toString("base64url"));
|
|
135
|
+
// JSON-escaped form (what `JSON.stringify` embeds inside a string), minus
|
|
136
|
+
// the surrounding quotes. Differs from the raw value only when the value
|
|
137
|
+
// contains characters JSON escapes.
|
|
138
|
+
out.add(JSON.stringify(value).slice(1, -1));
|
|
139
|
+
return [...out];
|
|
140
|
+
}
|
|
141
|
+
const registry = new SecretRegistry();
|
|
142
|
+
/**
|
|
143
|
+
* Register one secret value. `source` is a short label — `env`,
|
|
144
|
+
* `connector:gmail`, `config:apiKeys` — used verbatim in the replacement
|
|
145
|
+
* marker, so it must never carry the key name or any part of the value.
|
|
146
|
+
*/
|
|
147
|
+
export function registerSecretValue(value, source) {
|
|
148
|
+
registry.add(value, source);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Walk an object and register every string value found under a key that
|
|
152
|
+
* matches `SENSITIVE_KEY_PATTERNS`. Nested objects and arrays are walked;
|
|
153
|
+
* cycles are tolerated. The key name itself is NOT recorded anywhere.
|
|
154
|
+
*/
|
|
155
|
+
export function registerSecretValues(obj, source, opts = {}) {
|
|
156
|
+
const seen = new WeakSet();
|
|
157
|
+
const walk = (v, keyIsSensitive) => {
|
|
158
|
+
if (typeof v === "string") {
|
|
159
|
+
if (keyIsSensitive)
|
|
160
|
+
registry.add(v, source);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (v === null || typeof v !== "object")
|
|
164
|
+
return;
|
|
165
|
+
if (seen.has(v))
|
|
166
|
+
return;
|
|
167
|
+
seen.add(v);
|
|
168
|
+
if (Array.isArray(v)) {
|
|
169
|
+
for (const item of v)
|
|
170
|
+
walk(item, keyIsSensitive);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
for (const [k, item] of Object.entries(v)) {
|
|
174
|
+
walk(item, opts.allKeys === true || keyIsSensitive || isSensitiveKeyName(k));
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
walk(obj, opts.allKeys === true);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Register the values of a recipe's declared `type: env` block. Every value
|
|
181
|
+
* in that block is a secret by declaration — the recipe asked for it from the
|
|
182
|
+
* environment — regardless of its key name.
|
|
183
|
+
*
|
|
184
|
+
* To be called by the runners (`declaredRecipeEnv` callers) — see the report
|
|
185
|
+
* accompanying this module for the exact lines.
|
|
186
|
+
*/
|
|
187
|
+
export function registerEnvBlock(env) {
|
|
188
|
+
for (const v of Object.values(env)) {
|
|
189
|
+
if (typeof v === "string")
|
|
190
|
+
registry.add(v, "env");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Register a recipe's `vars:` — only values under sensitive key names, since
|
|
195
|
+
* ordinary vars (`repo`, `channel`) are not secrets and redacting them would
|
|
196
|
+
* hollow out the run log.
|
|
197
|
+
*/
|
|
198
|
+
export function registerSensitiveVars(vars) {
|
|
199
|
+
registerSecretValues(vars, "vars");
|
|
200
|
+
}
|
|
201
|
+
/** The bridge's own bearer token. `bridge.ts` should call this at startup. */
|
|
202
|
+
export function registerBridgeToken(token) {
|
|
203
|
+
registry.add(token, "bridge-token");
|
|
204
|
+
}
|
|
205
|
+
export function secretValueCount() {
|
|
206
|
+
return registry.count();
|
|
207
|
+
}
|
|
208
|
+
export function _resetSecretValuesForTesting() {
|
|
209
|
+
registry.clear();
|
|
210
|
+
}
|
|
211
|
+
function marker(source) {
|
|
212
|
+
return `[REDACTED:${source}]`;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Replace every registered secret (and its cheap encodings) in `text`.
|
|
216
|
+
* Returns the input reference unchanged when nothing is registered or
|
|
217
|
+
* nothing matches, so the hot path allocates nothing.
|
|
218
|
+
*/
|
|
219
|
+
export function redactKnownSecrets(text) {
|
|
220
|
+
if (typeof text !== "string" || text.length < MIN_SECRET_LENGTH)
|
|
221
|
+
return text;
|
|
222
|
+
const needles = registry.getNeedles();
|
|
223
|
+
if (needles.length === 0)
|
|
224
|
+
return text;
|
|
225
|
+
let out = text;
|
|
226
|
+
for (const n of needles) {
|
|
227
|
+
if (out.includes(n.text))
|
|
228
|
+
out = out.split(n.text).join(marker(n.source));
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Walk any value and redact registered secrets inside every string, including
|
|
234
|
+
* object keys and strings that are themselves JSON. Structure is preserved;
|
|
235
|
+
* non-string leaves pass through; cycles become `"[circular]"`.
|
|
236
|
+
*
|
|
237
|
+
* A string that parses as JSON is parsed, redacted structurally and
|
|
238
|
+
* re-stringified — this catches a secret that sits inside a nested JSON
|
|
239
|
+
* document under a JSON-escaped form the flat pass might otherwise handle
|
|
240
|
+
* only partially. Anything that fails to parse falls back to the plain
|
|
241
|
+
* substring pass, which is sufficient on its own; the parse is belt and
|
|
242
|
+
* braces, never a requirement.
|
|
243
|
+
*/
|
|
244
|
+
export function redactKnownSecretsDeep(value) {
|
|
245
|
+
if (registry.count() === 0)
|
|
246
|
+
return value;
|
|
247
|
+
return deep(value, new WeakSet());
|
|
248
|
+
}
|
|
249
|
+
function deep(value, seen) {
|
|
250
|
+
if (typeof value === "string")
|
|
251
|
+
return redactString(value);
|
|
252
|
+
if (value === null || typeof value !== "object")
|
|
253
|
+
return value;
|
|
254
|
+
if (seen.has(value))
|
|
255
|
+
return "[circular]";
|
|
256
|
+
seen.add(value);
|
|
257
|
+
if (Array.isArray(value))
|
|
258
|
+
return value.map((v) => deep(v, seen));
|
|
259
|
+
if (value instanceof Date || Buffer.isBuffer(value))
|
|
260
|
+
return value;
|
|
261
|
+
const out = {};
|
|
262
|
+
for (const [k, v] of Object.entries(value)) {
|
|
263
|
+
out[redactKnownSecrets(k)] = deep(v, seen);
|
|
264
|
+
}
|
|
265
|
+
return out;
|
|
266
|
+
}
|
|
267
|
+
function redactString(s) {
|
|
268
|
+
const flat = redactKnownSecrets(s);
|
|
269
|
+
// Only attempt the structural pass on something that looks like JSON and
|
|
270
|
+
// is not already clean — keeps the common case a single `includes` scan.
|
|
271
|
+
if (flat === s)
|
|
272
|
+
return s;
|
|
273
|
+
const t = s.trim();
|
|
274
|
+
if (!(t.startsWith("{") || t.startsWith("[")))
|
|
275
|
+
return flat;
|
|
276
|
+
try {
|
|
277
|
+
const parsed = JSON.parse(s);
|
|
278
|
+
if (parsed === null || typeof parsed !== "object")
|
|
279
|
+
return flat;
|
|
280
|
+
return JSON.stringify(deep(parsed, new WeakSet()));
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return flat;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=secretValues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secretValues.js","sourceRoot":"","sources":["../../src/governance/secretValues.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolFacts from the live recipe tool registry — the single place that turns
|
|
3
|
+
* a tool id into the facts `computeEffectivePolicy` needs. Used by the
|
|
4
|
+
* runners at the consult point AND by `patchwork policy explain`, so both
|
|
5
|
+
* describe the same tool the same way.
|
|
6
|
+
*/
|
|
7
|
+
import type { ToolFacts } from "./effectivePolicy.js";
|
|
8
|
+
import type { AgentContainment } from "./profile.js";
|
|
9
|
+
export declare function toolFactsFor(toolId: string, agent?: {
|
|
10
|
+
containment: AgentContainment;
|
|
11
|
+
} | undefined): ToolFacts;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolFacts from the live recipe tool registry — the single place that turns
|
|
3
|
+
* a tool id into the facts `computeEffectivePolicy` needs. Used by the
|
|
4
|
+
* runners at the consult point AND by `patchwork policy explain`, so both
|
|
5
|
+
* describe the same tool the same way.
|
|
6
|
+
*/
|
|
7
|
+
import { getTool } from "../recipes/toolRegistry.js";
|
|
8
|
+
import { classifyTool } from "../riskTier.js";
|
|
9
|
+
import { classifyActionClass } from "../workers/actionClass.js";
|
|
10
|
+
export function toolFactsFor(toolId, agent) {
|
|
11
|
+
if (toolId === "agent") {
|
|
12
|
+
return {
|
|
13
|
+
id: "agent",
|
|
14
|
+
tier: classifyTool("agent"),
|
|
15
|
+
tierDeclared: false,
|
|
16
|
+
registered: true,
|
|
17
|
+
isAgentStep: true,
|
|
18
|
+
...(agent && {
|
|
19
|
+
agentContained: agent.containment.enforced,
|
|
20
|
+
agentWidenings: agent.containment.widenings,
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const reg = getTool(toolId);
|
|
25
|
+
if (!reg) {
|
|
26
|
+
return {
|
|
27
|
+
id: toolId,
|
|
28
|
+
tier: classifyTool(toolId),
|
|
29
|
+
tierDeclared: false,
|
|
30
|
+
registered: false,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
id: toolId,
|
|
35
|
+
tier: classifyTool(toolId),
|
|
36
|
+
// The registry resolver supplies riskDefault to classifyTool, so a
|
|
37
|
+
// registered tool's tier is declared by construction. Plugin tools are
|
|
38
|
+
// registered through `registerPluginTools` with a capped default and are
|
|
39
|
+
// flagged there (see toolRegistry.ts) — they carry `pluginTier: true`.
|
|
40
|
+
tierDeclared: !reg.fromPlugin,
|
|
41
|
+
isWrite: reg.isWrite,
|
|
42
|
+
registered: true,
|
|
43
|
+
reversibility: classifyActionClass(toolId, {}).reversibility,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=toolFacts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolFacts.js","sourceRoot":"","sources":["../../src/governance/toolFacts.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAIhE,MAAM,UAAU,YAAY,CAC1B,MAAc,EACd,KAAqD;IAErD,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO;YACL,EAAE,EAAE,OAAO;YACX,IAAI,EAAE,YAAY,CAAC,OAAO,CAAC;YAC3B,YAAY,EAAE,KAAK;YACnB,UAAU,EAAE,IAAI;YAChB,WAAW,EAAE,IAAI;YACjB,GAAG,CAAC,KAAK,IAAI;gBACX,cAAc,EAAE,KAAK,CAAC,WAAW,CAAC,QAAQ;gBAC1C,cAAc,EAAE,KAAK,CAAC,WAAW,CAAC,SAAS;aAC5C,CAAC;SACH,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO;YACL,EAAE,EAAE,MAAM;YACV,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC;YAC1B,YAAY,EAAE,KAAK;YACnB,UAAU,EAAE,KAAK;SAClB,CAAC;IACJ,CAAC;IACD,OAAO;QACL,EAAE,EAAE,MAAM;QACV,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC;QAC1B,mEAAmE;QACnE,uEAAuE;QACvE,yEAAyE;QACzE,uEAAuE;QACvE,YAAY,EAAE,CAAE,GAAgC,CAAC,UAAU;QAC3D,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,UAAU,EAAE,IAAI;QAChB,aAAa,EAAE,mBAAmB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,aAAa;KAC7D,CAAC;AACJ,CAAC"}
|