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,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin policy — may THIS recipe load THAT plugin?
|
|
3
|
+
*
|
|
4
|
+
* A recipe's `servers:` list names code that is imported into the bridge
|
|
5
|
+
* process and handed the tool registry. Before the governed profile,
|
|
6
|
+
* installing a recipe therefore equalled arbitrary in-process code execution:
|
|
7
|
+
* nothing between "a YAML file arrived" and "its `index.mjs` ran" asked the
|
|
8
|
+
* operator anything. Under `governed` (`profile.pluginPolicy === "allowlist"`)
|
|
9
|
+
* a spec is loaded only when it appears in `config.plugins.allow`, and the
|
|
10
|
+
* check is made at FOUR points — install, dashboard save, `recipe lint` and
|
|
11
|
+
* runtime load — because a file on disk may have arrived by any route, so the
|
|
12
|
+
* runtime never trusts that something upstream validated it.
|
|
13
|
+
*
|
|
14
|
+
* Under `compat` every spec is allowed, byte-identical to the previous
|
|
15
|
+
* behaviour. This module holds no state: every verdict is a pure function of
|
|
16
|
+
* (spec, profile, allowlist) plus, for integrity, the entrypoint bytes.
|
|
17
|
+
*/
|
|
18
|
+
import type { PatchworkConfig } from "../patchworkConfig.js";
|
|
19
|
+
import type { GovernanceProfile } from "./profile.js";
|
|
20
|
+
export interface AllowEntry {
|
|
21
|
+
spec: string;
|
|
22
|
+
version?: string;
|
|
23
|
+
integrity?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface PluginPolicyInput {
|
|
26
|
+
profile: Pick<GovernanceProfile, "mode" | "pluginPolicy">;
|
|
27
|
+
allow: AllowEntry[] | undefined;
|
|
28
|
+
}
|
|
29
|
+
export interface PluginVerdict {
|
|
30
|
+
spec: string;
|
|
31
|
+
allowed: boolean;
|
|
32
|
+
reason: string;
|
|
33
|
+
entry?: AllowEntry;
|
|
34
|
+
}
|
|
35
|
+
/** The governed posture, for "what WOULD happen" evaluations under compat. */
|
|
36
|
+
export declare const GOVERNED_PLUGIN_POLICY_PROFILE: PluginPolicyInput["profile"];
|
|
37
|
+
export declare const PLUGIN_NOT_ALLOWLISTED = "plugin_not_allowlisted";
|
|
38
|
+
export declare const PLUGIN_INTEGRITY_MISMATCH = "plugin_integrity_mismatch";
|
|
39
|
+
/** Error thrown by the runtime when a spec is refused. `code` is stable. */
|
|
40
|
+
export declare class PluginPolicyError extends Error {
|
|
41
|
+
readonly code: string;
|
|
42
|
+
readonly specs: string[];
|
|
43
|
+
constructor(code: string, message: string, specs: string[]);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Canonical form of a spec for comparison. Path specs resolve to an absolute
|
|
47
|
+
* path with the trailing separator dropped, so `./x`, `x/` written as `./x/`
|
|
48
|
+
* and the absolute form all agree. Package specs are compared verbatim after
|
|
49
|
+
* trimming — a package name has no equivalent spellings.
|
|
50
|
+
*/
|
|
51
|
+
export declare function normalisePluginSpec(spec: string, cwd?: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Pure: (spec, profile, allowlist) → verdict. Exact match on the normalised
|
|
54
|
+
* spec; no globbing, no prefix matching — a policy that matches loosely is
|
|
55
|
+
* a policy an attacker can satisfy by naming a sibling directory.
|
|
56
|
+
*/
|
|
57
|
+
export declare function evaluatePluginSpec(spec: string, input: PluginPolicyInput, cwd?: string): PluginVerdict;
|
|
58
|
+
/** Per-spec verdicts for `policy explain` / `doctor`. */
|
|
59
|
+
export declare function explainPluginPolicy(specs: string[], cfg: {
|
|
60
|
+
profile: PluginPolicyInput["profile"];
|
|
61
|
+
allow?: AllowEntry[];
|
|
62
|
+
}, cwd?: string): PluginVerdict[];
|
|
63
|
+
/** Convenience: the refused subset, for callers that build one error. */
|
|
64
|
+
export declare function refusedPluginSpecs(specs: string[], input: PluginPolicyInput, cwd?: string): PluginVerdict[];
|
|
65
|
+
/** Build the runtime error for a set of refused verdicts. */
|
|
66
|
+
export declare function pluginNotAllowlistedError(refused: PluginVerdict[]): PluginPolicyError;
|
|
67
|
+
export interface IntegrityResult {
|
|
68
|
+
ok: boolean;
|
|
69
|
+
/** "verified" | "skipped" | "mismatch" | "unreadable" | "malformed" */
|
|
70
|
+
status: "verified" | "skipped" | "mismatch" | "unreadable" | "malformed";
|
|
71
|
+
reason: string;
|
|
72
|
+
actual?: string;
|
|
73
|
+
}
|
|
74
|
+
export declare function sha256Integrity(bytes: Buffer): string;
|
|
75
|
+
/**
|
|
76
|
+
* `sha256-<base64>` over the entrypoint file bytes. Absent integrity is
|
|
77
|
+
* SKIPPED (and says so) rather than failed: the allowlist entry itself is the
|
|
78
|
+
* operator's decision, and integrity is an optional tightening of it.
|
|
79
|
+
*/
|
|
80
|
+
export declare function verifyPluginIntegrity(entrypointPath: string, integrity: string | undefined): IntegrityResult;
|
|
81
|
+
/** Extract `servers:` from a parsed recipe object; non-list ⇒ []. */
|
|
82
|
+
export declare function pluginSpecsOf(recipe: unknown): string[];
|
|
83
|
+
/** Extract `servers:` from recipe YAML text; unparseable ⇒ []. */
|
|
84
|
+
export declare function pluginSpecsOfYaml(text: string): string[];
|
|
85
|
+
export declare function policyInputFromConfig(profile: PluginPolicyInput["profile"], cfg: Pick<PatchworkConfig, "plugins"> | undefined): PluginPolicyInput;
|
|
86
|
+
export interface RecipePluginScanRow {
|
|
87
|
+
/** Recipe file, relative to `recipesDir`. */
|
|
88
|
+
file: string;
|
|
89
|
+
/** Recipe `name:` when parseable. */
|
|
90
|
+
name?: string;
|
|
91
|
+
verdicts: PluginVerdict[];
|
|
92
|
+
}
|
|
93
|
+
export interface RecipePluginScan {
|
|
94
|
+
recipesDir: string;
|
|
95
|
+
/** Recipe files inspected (the denominator). */
|
|
96
|
+
recipesScanned: number;
|
|
97
|
+
/** Recipes declaring at least one `servers:` entry. */
|
|
98
|
+
recipesWithPlugins: number;
|
|
99
|
+
refusedSpecs: number;
|
|
100
|
+
rows: RecipePluginScanRow[];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Every installed recipe's `servers:` specs with their verdicts, for the
|
|
104
|
+
* doctor governance section. Reports only recipes that declare plugins, but
|
|
105
|
+
* counts every file read so an empty result is distinguishable from an
|
|
106
|
+
* unreadable directory.
|
|
107
|
+
*/
|
|
108
|
+
export declare function scanInstalledRecipePlugins(recipesDir: string, cfg: {
|
|
109
|
+
profile: PluginPolicyInput["profile"];
|
|
110
|
+
allow?: AllowEntry[];
|
|
111
|
+
}, cwd?: string): RecipePluginScan;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin policy — may THIS recipe load THAT plugin?
|
|
3
|
+
*
|
|
4
|
+
* A recipe's `servers:` list names code that is imported into the bridge
|
|
5
|
+
* process and handed the tool registry. Before the governed profile,
|
|
6
|
+
* installing a recipe therefore equalled arbitrary in-process code execution:
|
|
7
|
+
* nothing between "a YAML file arrived" and "its `index.mjs` ran" asked the
|
|
8
|
+
* operator anything. Under `governed` (`profile.pluginPolicy === "allowlist"`)
|
|
9
|
+
* a spec is loaded only when it appears in `config.plugins.allow`, and the
|
|
10
|
+
* check is made at FOUR points — install, dashboard save, `recipe lint` and
|
|
11
|
+
* runtime load — because a file on disk may have arrived by any route, so the
|
|
12
|
+
* runtime never trusts that something upstream validated it.
|
|
13
|
+
*
|
|
14
|
+
* Under `compat` every spec is allowed, byte-identical to the previous
|
|
15
|
+
* behaviour. This module holds no state: every verdict is a pure function of
|
|
16
|
+
* (spec, profile, allowlist) plus, for integrity, the entrypoint bytes.
|
|
17
|
+
*/
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { parse as parseYaml } from "yaml";
|
|
22
|
+
/** The governed posture, for "what WOULD happen" evaluations under compat. */
|
|
23
|
+
export const GOVERNED_PLUGIN_POLICY_PROFILE = Object.freeze({ mode: "governed", pluginPolicy: "allowlist" });
|
|
24
|
+
export const PLUGIN_NOT_ALLOWLISTED = "plugin_not_allowlisted";
|
|
25
|
+
export const PLUGIN_INTEGRITY_MISMATCH = "plugin_integrity_mismatch";
|
|
26
|
+
/** Error thrown by the runtime when a spec is refused. `code` is stable. */
|
|
27
|
+
export class PluginPolicyError extends Error {
|
|
28
|
+
code;
|
|
29
|
+
specs;
|
|
30
|
+
constructor(code, message, specs) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "PluginPolicyError";
|
|
33
|
+
this.code = code;
|
|
34
|
+
this.specs = specs;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function isPathSpec(spec) {
|
|
38
|
+
return (spec.startsWith("./") ||
|
|
39
|
+
spec.startsWith("../") ||
|
|
40
|
+
spec === "." ||
|
|
41
|
+
spec === ".." ||
|
|
42
|
+
path.isAbsolute(spec));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Canonical form of a spec for comparison. Path specs resolve to an absolute
|
|
46
|
+
* path with the trailing separator dropped, so `./x`, `x/` written as `./x/`
|
|
47
|
+
* and the absolute form all agree. Package specs are compared verbatim after
|
|
48
|
+
* trimming — a package name has no equivalent spellings.
|
|
49
|
+
*/
|
|
50
|
+
export function normalisePluginSpec(spec, cwd = process.cwd()) {
|
|
51
|
+
const trimmed = spec.trim();
|
|
52
|
+
if (isPathSpec(trimmed)) {
|
|
53
|
+
const resolved = path.resolve(cwd, trimmed);
|
|
54
|
+
return resolved.length > 1 ? resolved.replace(/[\\/]+$/, "") : resolved;
|
|
55
|
+
}
|
|
56
|
+
return trimmed;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Pure: (spec, profile, allowlist) → verdict. Exact match on the normalised
|
|
60
|
+
* spec; no globbing, no prefix matching — a policy that matches loosely is
|
|
61
|
+
* a policy an attacker can satisfy by naming a sibling directory.
|
|
62
|
+
*/
|
|
63
|
+
export function evaluatePluginSpec(spec, input, cwd = process.cwd()) {
|
|
64
|
+
const trimmed = spec.trim();
|
|
65
|
+
if (input.profile.pluginPolicy !== "allowlist") {
|
|
66
|
+
return { spec: trimmed, allowed: true, reason: "compat profile: open" };
|
|
67
|
+
}
|
|
68
|
+
const wanted = normalisePluginSpec(trimmed, cwd);
|
|
69
|
+
for (const entry of input.allow ?? []) {
|
|
70
|
+
if (typeof entry?.spec !== "string")
|
|
71
|
+
continue;
|
|
72
|
+
if (normalisePluginSpec(entry.spec, cwd) === wanted) {
|
|
73
|
+
return {
|
|
74
|
+
spec: trimmed,
|
|
75
|
+
allowed: true,
|
|
76
|
+
reason: `allowlisted as "${entry.spec}"`,
|
|
77
|
+
entry,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const size = (input.allow ?? []).length;
|
|
82
|
+
return {
|
|
83
|
+
spec: trimmed,
|
|
84
|
+
allowed: false,
|
|
85
|
+
reason: size === 0
|
|
86
|
+
? "governed profile: plugins.allow is empty — no recipe plugin may load"
|
|
87
|
+
: `governed profile: "${trimmed}" is not in plugins.allow (${size} entr${size === 1 ? "y" : "ies"})`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Per-spec verdicts for `policy explain` / `doctor`. */
|
|
91
|
+
export function explainPluginPolicy(specs, cfg, cwd = process.cwd()) {
|
|
92
|
+
return specs.map((s) => evaluatePluginSpec(s, { profile: cfg.profile, allow: cfg.allow }, cwd));
|
|
93
|
+
}
|
|
94
|
+
/** Convenience: the refused subset, for callers that build one error. */
|
|
95
|
+
export function refusedPluginSpecs(specs, input, cwd = process.cwd()) {
|
|
96
|
+
return specs
|
|
97
|
+
.map((s) => evaluatePluginSpec(s, input, cwd))
|
|
98
|
+
.filter((v) => !v.allowed);
|
|
99
|
+
}
|
|
100
|
+
/** Build the runtime error for a set of refused verdicts. */
|
|
101
|
+
export function pluginNotAllowlistedError(refused) {
|
|
102
|
+
const names = refused.map((v) => v.spec);
|
|
103
|
+
return new PluginPolicyError(PLUGIN_NOT_ALLOWLISTED, `recipe plugin${names.length === 1 ? "" : "s"} ${names.map((n) => `"${n}"`).join(", ")} ` +
|
|
104
|
+
"not allowlisted under the governed profile — add to config.json " +
|
|
105
|
+
"`plugins.allow` (spec, optional integrity) or switch to the compat profile", names);
|
|
106
|
+
}
|
|
107
|
+
export function sha256Integrity(bytes) {
|
|
108
|
+
return `sha256-${createHash("sha256").update(bytes).digest("base64")}`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* `sha256-<base64>` over the entrypoint file bytes. Absent integrity is
|
|
112
|
+
* SKIPPED (and says so) rather than failed: the allowlist entry itself is the
|
|
113
|
+
* operator's decision, and integrity is an optional tightening of it.
|
|
114
|
+
*/
|
|
115
|
+
export function verifyPluginIntegrity(entrypointPath, integrity) {
|
|
116
|
+
if (integrity === undefined || integrity === null || integrity === "") {
|
|
117
|
+
return {
|
|
118
|
+
ok: true,
|
|
119
|
+
status: "skipped",
|
|
120
|
+
reason: "no integrity recorded on the allowlist entry",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const m = /^sha256-([A-Za-z0-9+/]+={0,2})$/.exec(integrity.trim());
|
|
124
|
+
if (!m) {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
status: "malformed",
|
|
128
|
+
reason: `integrity must be "sha256-<base64>", got ${JSON.stringify(integrity)}`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
let bytes;
|
|
132
|
+
try {
|
|
133
|
+
bytes = readFileSync(entrypointPath);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
return {
|
|
137
|
+
ok: false,
|
|
138
|
+
status: "unreadable",
|
|
139
|
+
reason: `cannot read entrypoint ${entrypointPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const actual = sha256Integrity(bytes);
|
|
143
|
+
if (actual !== `sha256-${m[1]}`) {
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
status: "mismatch",
|
|
147
|
+
reason: `entrypoint ${entrypointPath} hashes to ${actual}, allowlist says ${integrity.trim()}`,
|
|
148
|
+
actual,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return { ok: true, status: "verified", reason: "sha256 matches", actual };
|
|
152
|
+
}
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Recipe scanning (install / save / doctor)
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
/** Extract `servers:` from a parsed recipe object; non-list ⇒ []. */
|
|
157
|
+
export function pluginSpecsOf(recipe) {
|
|
158
|
+
if (!recipe || typeof recipe !== "object" || Array.isArray(recipe))
|
|
159
|
+
return [];
|
|
160
|
+
const servers = recipe.servers;
|
|
161
|
+
if (!Array.isArray(servers))
|
|
162
|
+
return [];
|
|
163
|
+
return servers.filter((s) => typeof s === "string");
|
|
164
|
+
}
|
|
165
|
+
/** Extract `servers:` from recipe YAML text; unparseable ⇒ []. */
|
|
166
|
+
export function pluginSpecsOfYaml(text) {
|
|
167
|
+
try {
|
|
168
|
+
return pluginSpecsOf(parseYaml(text));
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export function policyInputFromConfig(profile, cfg) {
|
|
175
|
+
return { profile, allow: cfg?.plugins?.allow };
|
|
176
|
+
}
|
|
177
|
+
function walkRecipeFiles(dir, out, depth = 0) {
|
|
178
|
+
if (depth > 4)
|
|
179
|
+
return;
|
|
180
|
+
let entries;
|
|
181
|
+
try {
|
|
182
|
+
entries = readdirSync(dir);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
for (const name of entries) {
|
|
188
|
+
if (name.startsWith("."))
|
|
189
|
+
continue;
|
|
190
|
+
const full = path.join(dir, name);
|
|
191
|
+
let st;
|
|
192
|
+
try {
|
|
193
|
+
st = statSync(full);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (st.isDirectory())
|
|
199
|
+
walkRecipeFiles(full, out, depth + 1);
|
|
200
|
+
else if (/\.(ya?ml|json)$/i.test(name))
|
|
201
|
+
out.push(full);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Every installed recipe's `servers:` specs with their verdicts, for the
|
|
206
|
+
* doctor governance section. Reports only recipes that declare plugins, but
|
|
207
|
+
* counts every file read so an empty result is distinguishable from an
|
|
208
|
+
* unreadable directory.
|
|
209
|
+
*/
|
|
210
|
+
export function scanInstalledRecipePlugins(recipesDir, cfg, cwd = process.cwd()) {
|
|
211
|
+
const files = [];
|
|
212
|
+
if (existsSync(recipesDir))
|
|
213
|
+
walkRecipeFiles(recipesDir, files);
|
|
214
|
+
const rows = [];
|
|
215
|
+
let refused = 0;
|
|
216
|
+
for (const file of files) {
|
|
217
|
+
let parsed;
|
|
218
|
+
try {
|
|
219
|
+
const text = readFileSync(file, "utf-8");
|
|
220
|
+
parsed = file.toLowerCase().endsWith(".json")
|
|
221
|
+
? JSON.parse(text)
|
|
222
|
+
: parseYaml(text);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const specs = pluginSpecsOf(parsed);
|
|
228
|
+
if (specs.length === 0)
|
|
229
|
+
continue;
|
|
230
|
+
const verdicts = explainPluginPolicy(specs, cfg, cwd);
|
|
231
|
+
refused += verdicts.filter((v) => !v.allowed).length;
|
|
232
|
+
const name = parsed.name;
|
|
233
|
+
rows.push({
|
|
234
|
+
file: path.relative(recipesDir, file),
|
|
235
|
+
...(typeof name === "string" ? { name } : {}),
|
|
236
|
+
verdicts,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
recipesDir,
|
|
241
|
+
recipesScanned: files.length,
|
|
242
|
+
recipesWithPlugins: rows.length,
|
|
243
|
+
refusedSpecs: refused,
|
|
244
|
+
rows,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=pluginPolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pluginPolicy.js","sourceRoot":"","sources":["../../src/governance/pluginPolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAsB1C,8EAA8E;AAC9E,MAAM,CAAC,MAAM,8BAA8B,GACzC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC;AAEjE,MAAM,CAAC,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAC/D,MAAM,CAAC,MAAM,yBAAyB,GAAG,2BAA2B,CAAC;AAErE,4EAA4E;AAC5E,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,IAAI,CAAS;IACb,KAAK,CAAW;IACzB,YAAY,IAAY,EAAE,OAAe,EAAE,KAAe;QACxD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,CACL,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QACrB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QACtB,IAAI,KAAK,GAAG;QACZ,IAAI,KAAK,IAAI;QACb,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CACtB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IACnE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC5C,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC1E,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,KAAwB,EACxB,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAEnB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,KAAK,WAAW,EAAE,CAAC;QAC/C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;IAC1E,CAAC;IACD,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACjD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;QACtC,IAAI,OAAO,KAAK,EAAE,IAAI,KAAK,QAAQ;YAAE,SAAS;QAC9C,IAAI,mBAAmB,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,MAAM,EAAE,CAAC;YACpD,OAAO;gBACL,IAAI,EAAE,OAAO;gBACb,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,mBAAmB,KAAK,CAAC,IAAI,GAAG;gBACxC,KAAK;aACN,CAAC;QACJ,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;IACxC,OAAO;QACL,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,KAAK;QACd,MAAM,EACJ,IAAI,KAAK,CAAC;YACR,CAAC,CAAC,sEAAsE;YACxE,CAAC,CAAC,sBAAsB,OAAO,8BAA8B,IAAI,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG;KACzG,CAAC;AACJ,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,mBAAmB,CACjC,KAAe,EACf,GAAoE,EACpE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAEnB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACrB,kBAAkB,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,GAAG,CAAC,CACvE,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,kBAAkB,CAChC,KAAe,EACf,KAAwB,EACxB,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAEnB,OAAO,KAAK;SACT,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;SAC7C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAC/B,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,yBAAyB,CACvC,OAAwB;IAExB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,IAAI,iBAAiB,CAC1B,sBAAsB,EACtB,gBAAgB,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;QACvF,kEAAkE;QAClE,4EAA4E,EAC9E,KAAK,CACN,CAAC;AACJ,CAAC;AAcD,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;AACzE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,cAAsB,EACtB,SAA6B;IAE7B,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,EAAE,EAAE,CAAC;QACtE,OAAO;YACL,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,8CAA8C;SACvD,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,GAAG,iCAAiC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;IACnE,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,WAAW;YACnB,MAAM,EAAE,4CAA4C,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE;SAChF,CAAC;IACJ,CAAC;IACD,IAAI,KAAa,CAAC;IAClB,IAAI,CAAC;QACH,KAAK,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,YAAY;YACpB,MAAM,EAAE,0BAA0B,cAAc,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;SACxG,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACtC,IAAI,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAChC,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,cAAc,cAAc,cAAc,MAAM,oBAAoB,SAAS,CAAC,IAAI,EAAE,EAAE;YAC9F,MAAM;SACP,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,CAAC;AAC5E,CAAC;AAED,8EAA8E;AAC9E,4CAA4C;AAC5C,8EAA8E;AAE9E,qEAAqE;AACrE,MAAM,UAAU,aAAa,CAAC,MAAe;IAC3C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9E,MAAM,OAAO,GAAI,MAAkC,CAAC,OAAO,CAAC;IAC5D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IACvC,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;AACnE,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,CAAC;QACH,OAAO,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,OAAqC,EACrC,GAAiD;IAEjD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AACjD,CAAC;AAoBD,SAAS,eAAe,CAAC,GAAW,EAAE,GAAa,EAAE,KAAK,GAAG,CAAC;IAC5D,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO;IACtB,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAClC,IAAI,EAA+B,CAAC;QACpC,IAAI,CAAC;YACH,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,EAAE,CAAC,WAAW,EAAE;YAAE,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;aACvD,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CACxC,UAAkB,EAClB,GAAoE,EACpE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAEnB,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,UAAU,CAAC,UAAU,CAAC;QAAE,eAAe,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAC/D,MAAM,IAAI,GAA0B,EAAE,CAAC;IACvC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACzC,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAC3C,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;gBAClB,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACjC,MAAM,QAAQ,GAAG,mBAAmB,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACtD,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;QACrD,MAAM,IAAI,GAAI,MAAkC,CAAC,IAAI,CAAC;QACtD,IAAI,CAAC,IAAI,CAAC;YACR,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;YACrC,GAAG,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,QAAQ;SACT,CAAC,CAAC;IACL,CAAC;IACD,OAAO;QACL,UAAU;QACV,cAAc,EAAE,KAAK,CAAC,MAAM;QAC5B,kBAAkB,EAAE,IAAI,CAAC,MAAM;QAC/B,YAAY,EAAE,OAAO;QACrB,IAAI;KACL,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,153 @@
|
|
|
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 declare const PROFILE_MODES: readonly ["governed", "compat"];
|
|
29
|
+
export type ProfileMode = (typeof PROFILE_MODES)[number];
|
|
30
|
+
export interface GovernanceProfile {
|
|
31
|
+
mode: ProfileMode;
|
|
32
|
+
/** Whether `profile:` was explicitly present in config (vs defaulted). */
|
|
33
|
+
declared: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Feeds `Server.approvalGate` / `makeRecipeApprovalFn`. Governed raises the
|
|
36
|
+
* operator's setting to at least `"high"`; it never lowers an explicit
|
|
37
|
+
* `"all"`.
|
|
38
|
+
*/
|
|
39
|
+
approvalGate: "off" | "high" | "all";
|
|
40
|
+
/**
|
|
41
|
+
* Feeds `RunnerDeps.gateAutomatedRuns`. Governed: cron / webhook / file-watch
|
|
42
|
+
* / git-hook / test-run triggers are consulted exactly like a manual run.
|
|
43
|
+
* Compat: manual only (the worker gate may still set it, as before).
|
|
44
|
+
*/
|
|
45
|
+
gateAutomatedRuns: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Feeds `FLAG_WORKER_AUTONOMY`: a worker manifest that owns a recipe governs
|
|
48
|
+
* it. Compat leaves the flag as the operator set it.
|
|
49
|
+
*/
|
|
50
|
+
workerAuthority: boolean;
|
|
51
|
+
/** Feeds `FLAG_ENFORCE_POLICY` (`patchwork.policy.yml`). */
|
|
52
|
+
policyEnforce: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Feeds the subprocess drivers (`src/drivers/*`): under `enforced` an agent
|
|
55
|
+
* step that declares no `sandbox:` runs contained — read-only tool
|
|
56
|
+
* allowlist, no WebFetch/WebSearch/Bash, allowlisted environment, no bridge
|
|
57
|
+
* MCP access. A recipe may widen this EXPLICITLY per step, and the widening
|
|
58
|
+
* is visible in `policy explain`.
|
|
59
|
+
*/
|
|
60
|
+
agentContainment: "enforced" | "opt-in";
|
|
61
|
+
/**
|
|
62
|
+
* Feeds `loadRecipeServers` / recipe install / dashboard save / lint:
|
|
63
|
+
* `allowlist` refuses any `servers:` entry not in `config.plugins.allow`.
|
|
64
|
+
*/
|
|
65
|
+
pluginPolicy: "allowlist" | "open";
|
|
66
|
+
/**
|
|
67
|
+
* Feeds `readKillSwitch`: when the kill-switch state cannot be read, treat
|
|
68
|
+
* it as ENGAGED (refuse) rather than released.
|
|
69
|
+
*/
|
|
70
|
+
killSwitchFailClosed: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* A write tool whose tier was INFERRED from its name (no explicit
|
|
73
|
+
* `riskDefault`, or a plugin / MCP tool) is queued for approval rather than
|
|
74
|
+
* trusted on the heuristic.
|
|
75
|
+
*/
|
|
76
|
+
unknownWriteTools: "gate" | "allow";
|
|
77
|
+
/**
|
|
78
|
+
* A tool id nothing is registered under: governed halts the run (a plugin
|
|
79
|
+
* that failed to load must not produce a green run that did nothing);
|
|
80
|
+
* compat keeps the documented forward-compat SKIP.
|
|
81
|
+
*/
|
|
82
|
+
unregisteredTools: "refuse" | "skip";
|
|
83
|
+
/** Connector-derived values are wrapped in an untrusted-content envelope. */
|
|
84
|
+
untrustedEnvelope: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Whether a recipe's own `requireApproval: false` may switch off the tier
|
|
87
|
+
* gate. Governed: no — a recipe cannot opt itself out of the workspace
|
|
88
|
+
* policy.
|
|
89
|
+
*/
|
|
90
|
+
recipeOptOutHonoured: boolean;
|
|
91
|
+
}
|
|
92
|
+
export interface ProfileConfigInput {
|
|
93
|
+
profile?: unknown;
|
|
94
|
+
approvalGate?: unknown;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Pure: config → profile. Unknown or absent `profile` ⇒ compat (never a
|
|
98
|
+
* guess). An explicit but unrecognised value is reported by `doctor`, not
|
|
99
|
+
* silently promoted to governed.
|
|
100
|
+
*/
|
|
101
|
+
export declare function resolveProfile(cfg: ProfileConfigInput | undefined): GovernanceProfile;
|
|
102
|
+
export declare const COMPAT_PROFILE: GovernanceProfile;
|
|
103
|
+
export declare const GOVERNED_PROFILE: GovernanceProfile;
|
|
104
|
+
export declare function isGoverned(p: GovernanceProfile | undefined): boolean;
|
|
105
|
+
export declare function setActiveProfile(p: GovernanceProfile): void;
|
|
106
|
+
export declare function activeProfile(): GovernanceProfile;
|
|
107
|
+
/** Test seam. */
|
|
108
|
+
export declare function _resetActiveProfileForTesting(): void;
|
|
109
|
+
/**
|
|
110
|
+
* Tools a contained agent step may use. Read-only by construction: nothing
|
|
111
|
+
* here writes a file, runs a command, or reaches the network. Names are the
|
|
112
|
+
* Claude Code built-in tool names; other drivers map them to their own
|
|
113
|
+
* equivalents (Gemini/Codex have coarser knobs — see each driver).
|
|
114
|
+
*/
|
|
115
|
+
export declare const CONTAINED_AGENT_ALLOWED_TOOLS: readonly string[];
|
|
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 declare const CONTAINED_AGENT_DENIED_TOOLS: readonly string[];
|
|
124
|
+
export interface AgentContainment {
|
|
125
|
+
/** True when the driver must apply the allowlist (not merely the deny list). */
|
|
126
|
+
enforced: boolean;
|
|
127
|
+
allowedTools: string[];
|
|
128
|
+
deniedTools: string[];
|
|
129
|
+
/** Pass only an explicit environment allowlist to the child process. */
|
|
130
|
+
envAllowlist: boolean;
|
|
131
|
+
/** Whether the child may reach the bridge's own MCP tool surface. */
|
|
132
|
+
mcpAccess: boolean;
|
|
133
|
+
/** Human-readable widenings the recipe requested, for `policy explain`. */
|
|
134
|
+
widenings: string[];
|
|
135
|
+
}
|
|
136
|
+
export interface StepSandboxRequest {
|
|
137
|
+
/** Recipe-declared `sandbox: true` / `allowedTools:` (existing fields). */
|
|
138
|
+
sandbox?: boolean;
|
|
139
|
+
allowedTools?: string[];
|
|
140
|
+
disallowedTools?: string[];
|
|
141
|
+
/** Explicit widenings a recipe may request under a governed profile. */
|
|
142
|
+
network?: boolean;
|
|
143
|
+
shell?: boolean;
|
|
144
|
+
mcpAccess?: boolean;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Pure: (profile, step request) → containment the driver must apply.
|
|
148
|
+
*
|
|
149
|
+
* Under `compat` this reproduces today's behaviour exactly: containment only
|
|
150
|
+
* when the step opted in via `sandbox: true`. Under `governed` the default is
|
|
151
|
+
* contained, and each widening is recorded so the explanation can show it.
|
|
152
|
+
*/
|
|
153
|
+
export declare function resolveAgentContainment(profile: GovernanceProfile, req: StepSandboxRequest | undefined): AgentContainment;
|