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.
Files changed (135) hide show
  1. package/dist/activityLog.js +6 -1
  2. package/dist/activityLog.js.map +1 -1
  3. package/dist/approvalQueue.js +15 -0
  4. package/dist/approvalQueue.js.map +1 -1
  5. package/dist/bridge.js +32 -1
  6. package/dist/bridge.js.map +1 -1
  7. package/dist/claudeDriver.d.ts +10 -0
  8. package/dist/claudeDriver.js +27 -4
  9. package/dist/claudeDriver.js.map +1 -1
  10. package/dist/claudeOrchestrator.d.ts +5 -0
  11. package/dist/claudeOrchestrator.js +94 -6
  12. package/dist/claudeOrchestrator.js.map +1 -1
  13. package/dist/commands/patchworkInit.js +10 -0
  14. package/dist/commands/patchworkInit.js.map +1 -1
  15. package/dist/commands/policyExplain.d.ts +50 -0
  16. package/dist/commands/policyExplain.js +221 -0
  17. package/dist/commands/policyExplain.js.map +1 -0
  18. package/dist/commands/profile.d.ts +17 -0
  19. package/dist/commands/profile.js +85 -0
  20. package/dist/commands/profile.js.map +1 -0
  21. package/dist/commands/recipe.d.ts +8 -0
  22. package/dist/commands/recipe.js +64 -0
  23. package/dist/commands/recipe.js.map +1 -1
  24. package/dist/commands/recipeInstall.js +19 -0
  25. package/dist/commands/recipeInstall.js.map +1 -1
  26. package/dist/connectors/baseConnector.js +5 -0
  27. package/dist/connectors/baseConnector.js.map +1 -1
  28. package/dist/connectors/secrets.js +10 -2
  29. package/dist/connectors/secrets.js.map +1 -1
  30. package/dist/connectors/tokenStorage.d.ts +9 -0
  31. package/dist/connectors/tokenStorage.js +27 -0
  32. package/dist/connectors/tokenStorage.js.map +1 -1
  33. package/dist/decisionTraceLog.js +4 -3
  34. package/dist/decisionTraceLog.js.map +1 -1
  35. package/dist/drivers/claude/envSanitizer.d.ts +39 -0
  36. package/dist/drivers/claude/envSanitizer.js +114 -0
  37. package/dist/drivers/claude/envSanitizer.js.map +1 -1
  38. package/dist/drivers/claude/subprocess.d.ts +12 -0
  39. package/dist/drivers/claude/subprocess.js +114 -25
  40. package/dist/drivers/claude/subprocess.js.map +1 -1
  41. package/dist/drivers/claude/subprocessSettings.d.ts +4 -1
  42. package/dist/drivers/claude/subprocessSettings.js +24 -7
  43. package/dist/drivers/claude/subprocessSettings.js.map +1 -1
  44. package/dist/drivers/codex/subprocess.d.ts +19 -0
  45. package/dist/drivers/codex/subprocess.js +40 -8
  46. package/dist/drivers/codex/subprocess.js.map +1 -1
  47. package/dist/drivers/gemini/index.d.ts +24 -0
  48. package/dist/drivers/gemini/index.js +76 -13
  49. package/dist/drivers/gemini/index.js.map +1 -1
  50. package/dist/drivers/types.d.ts +14 -0
  51. package/dist/drivers/types.js.map +1 -1
  52. package/dist/errors.d.ts +1 -0
  53. package/dist/errors.js +1 -0
  54. package/dist/errors.js.map +1 -1
  55. package/dist/governance/doctorReport.d.ts +45 -0
  56. package/dist/governance/doctorReport.js +251 -0
  57. package/dist/governance/doctorReport.js.map +1 -0
  58. package/dist/governance/effectivePolicy.d.ts +134 -0
  59. package/dist/governance/effectivePolicy.js +322 -0
  60. package/dist/governance/effectivePolicy.js.map +1 -0
  61. package/dist/governance/killSwitchPolicy.d.ts +30 -0
  62. package/dist/governance/killSwitchPolicy.js +52 -0
  63. package/dist/governance/killSwitchPolicy.js.map +1 -0
  64. package/dist/governance/pluginPolicy.d.ts +111 -0
  65. package/dist/governance/pluginPolicy.js +247 -0
  66. package/dist/governance/pluginPolicy.js.map +1 -0
  67. package/dist/governance/profile.d.ts +153 -0
  68. package/dist/governance/profile.js +181 -0
  69. package/dist/governance/profile.js.map +1 -0
  70. package/dist/governance/secretValues.d.ts +89 -0
  71. package/dist/governance/secretValues.js +286 -0
  72. package/dist/governance/secretValues.js.map +1 -0
  73. package/dist/governance/toolFacts.d.ts +11 -0
  74. package/dist/governance/toolFacts.js +46 -0
  75. package/dist/governance/toolFacts.js.map +1 -0
  76. package/dist/governance/untrustedContent.d.ts +43 -0
  77. package/dist/governance/untrustedContent.js +81 -0
  78. package/dist/governance/untrustedContent.js.map +1 -0
  79. package/dist/index.js +73 -5
  80. package/dist/index.js.map +1 -1
  81. package/dist/logger.js +7 -6
  82. package/dist/logger.js.map +1 -1
  83. package/dist/patchworkConfig.d.ts +22 -0
  84. package/dist/patchworkConfig.js.map +1 -1
  85. package/dist/pluginLoader.d.ts +10 -2
  86. package/dist/pluginLoader.js +12 -8
  87. package/dist/pluginLoader.js.map +1 -1
  88. package/dist/recipeOrchestration.d.ts +0 -6
  89. package/dist/recipeOrchestration.js +80 -23
  90. package/dist/recipeOrchestration.js.map +1 -1
  91. package/dist/recipeRoutes.js +52 -0
  92. package/dist/recipeRoutes.js.map +1 -1
  93. package/dist/recipes/agentExecutor.d.ts +43 -3
  94. package/dist/recipes/agentExecutor.js +55 -7
  95. package/dist/recipes/agentExecutor.js.map +1 -1
  96. package/dist/recipes/approvalRequest.d.ts +8 -0
  97. package/dist/recipes/approvalRequest.js.map +1 -1
  98. package/dist/recipes/chainedRunner.d.ts +18 -5
  99. package/dist/recipes/chainedRunner.js +107 -12
  100. package/dist/recipes/chainedRunner.js.map +1 -1
  101. package/dist/recipes/haltCategory.d.ts +4 -0
  102. package/dist/recipes/haltCategory.js +4 -0
  103. package/dist/recipes/haltCategory.js.map +1 -1
  104. package/dist/recipes/schema.d.ts +5 -1
  105. package/dist/recipes/schemaGenerator.js +12 -1
  106. package/dist/recipes/schemaGenerator.js.map +1 -1
  107. package/dist/recipes/stepObservation.d.ts +15 -0
  108. package/dist/recipes/stepObservation.js +27 -22
  109. package/dist/recipes/stepObservation.js.map +1 -1
  110. package/dist/recipes/templateEngine.d.ts +11 -1
  111. package/dist/recipes/templateEngine.js +12 -2
  112. package/dist/recipes/templateEngine.js.map +1 -1
  113. package/dist/recipes/toolRegistry.d.ts +3 -1
  114. package/dist/recipes/toolRegistry.js +9 -3
  115. package/dist/recipes/toolRegistry.js.map +1 -1
  116. package/dist/recipes/tools/http.d.ts +5 -2
  117. package/dist/recipes/tools/http.js +29 -10
  118. package/dist/recipes/tools/http.js.map +1 -1
  119. package/dist/recipes/validation.d.ts +10 -1
  120. package/dist/recipes/validation.js +51 -1
  121. package/dist/recipes/validation.js.map +1 -1
  122. package/dist/recipes/yamlRunner.d.ts +46 -5
  123. package/dist/recipes/yamlRunner.js +173 -21
  124. package/dist/recipes/yamlRunner.js.map +1 -1
  125. package/dist/schemas/recipe.v1.json +60 -3
  126. package/dist/server.js +15 -0
  127. package/dist/server.js.map +1 -1
  128. package/dist/ssrfGuard.d.ts +89 -5
  129. package/dist/ssrfGuard.js +250 -5
  130. package/dist/ssrfGuard.js.map +1 -1
  131. package/dist/tools/httpClient.js +23 -165
  132. package/dist/tools/httpClient.js.map +1 -1
  133. package/dist/transport.js +41 -0
  134. package/dist/transport.js.map +1 -1
  135. 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":"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"}
@@ -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"}