claude-flow 3.48.0 → 3.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/.proven-config-version +1 -0
- package/.claude/helpers/hook-handler.cjs +20 -4
- package/.claude/helpers/memory.cjs +1 -1
- package/.claude/helpers/router.cjs +1 -1
- package/.claude/helpers/session.cjs +1 -1
- package/.claude/proven-config.json +42 -0
- package/.claude-plugin/marketplace.json +26 -1
- package/README.md +1 -53
- package/README.zh-CN.md +1 -53
- package/node_modules/@claude-flow/codex/package.json +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +2 -6
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +35 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +18 -0
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +2 -2
- package/v3/@claude-flow/cli/README.md +3 -53
- package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
- package/v3/@claude-flow/cli/dist/src/commands/doctor.d.ts +19 -1
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +88 -9
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +7 -4
- package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/commands/init.js +20 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +30 -7
- package/v3/@claude-flow/cli/dist/src/commands/mods.d.ts +13 -0
- package/v3/@claude-flow/cli/dist/src/commands/mods.js +126 -0
- package/v3/@claude-flow/cli/dist/src/commands/plugins.js +44 -7
- package/v3/@claude-flow/cli/dist/src/commands/policy.js +5 -2
- package/v3/@claude-flow/cli/dist/src/commands/session.js +128 -21
- package/v3/@claude-flow/cli/dist/src/commands/swarm.js +11 -11
- package/v3/@claude-flow/cli/dist/src/index.js +10 -1
- package/v3/@claude-flow/cli/dist/src/init/executor.js +11 -5
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-companions.js +34 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-integrity.d.ts +21 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-integrity.js +62 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.d.ts +18 -11
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.js +56 -13
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +22 -10
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +16 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +87 -14
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.d.ts +7 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +43 -6
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +236 -50
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +6 -3
- package/v3/@claude-flow/cli/dist/src/mods/claude-installs.d.ts +29 -0
- package/v3/@claude-flow/cli/dist/src/mods/claude-installs.js +83 -0
- package/v3/@claude-flow/cli/dist/src/mods/install.d.ts +64 -0
- package/v3/@claude-flow/cli/dist/src/mods/install.js +135 -0
- package/v3/@claude-flow/cli/dist/src/mods/policy-projection.d.ts +39 -0
- package/v3/@claude-flow/cli/dist/src/mods/policy-projection.js +65 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.d.ts +28 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.js +118 -0
- package/v3/@claude-flow/cli/dist/src/plugins/manager.d.ts +37 -10
- package/v3/@claude-flow/cli/dist/src/plugins/manager.js +106 -20
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.d.ts +61 -0
- package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.js +84 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +6 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +42 -2
- package/v3/@claude-flow/cli/package.json +2 -2
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
* Bridges discovery service with file system persistence
|
|
5
5
|
*/
|
|
6
6
|
import * as fs from 'fs';
|
|
7
|
+
import * as os from 'os';
|
|
7
8
|
import * as path from 'path';
|
|
8
9
|
import { execFile } from 'child_process';
|
|
9
10
|
import { promisify } from 'util';
|
|
11
|
+
import { evaluatePluginTrust, parseSha256Checksum, readDeclaredTrust, sha256Hex, shouldRunInstallScripts, } from './trust-policy.js';
|
|
10
12
|
const execFileAsync = promisify(execFile);
|
|
11
13
|
// On Windows, `npm` is a shell script (no `.exe`) and `npm.cmd` is a batch
|
|
12
14
|
// wrapper. Since Node 18.20.2 / 20.12.2 (CVE-2024-27980) the runtime refuses
|
|
@@ -30,6 +32,30 @@ function validatePackageName(spec) {
|
|
|
30
32
|
throw new Error(`Invalid package name: ${spec}`);
|
|
31
33
|
}
|
|
32
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Apply the #3557 trust policy to a plugin's package.json: record its declared
|
|
37
|
+
* trust and permissions, and withhold hooks/commands the policy doesn't allow.
|
|
38
|
+
*/
|
|
39
|
+
function applyTrustPolicy(pkg, opts) {
|
|
40
|
+
const block = (pkg['claude-flow'] ?? {});
|
|
41
|
+
const commands = Array.isArray(block.commands) ? block.commands : [];
|
|
42
|
+
const hooks = Array.isArray(block.hooks) ? block.hooks : [];
|
|
43
|
+
const declared = readDeclaredTrust(pkg);
|
|
44
|
+
const permissions = [...new Set([...(opts.registryPermissions ?? []), ...declared.permissions])];
|
|
45
|
+
const trustLevel = opts.registryTrustLevel ?? declared.trustLevel;
|
|
46
|
+
const decision = evaluatePluginTrust({ trustLevel: declared.trustLevel, permissions }, { verify: opts.verify !== false, trust: opts.trust === true, registryTrustLevel: opts.registryTrustLevel });
|
|
47
|
+
if (decision.allowed) {
|
|
48
|
+
return { commands, hooks, trustLevel, permissions, decision };
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
commands: [],
|
|
52
|
+
hooks: [],
|
|
53
|
+
trustLevel,
|
|
54
|
+
permissions,
|
|
55
|
+
withheld: { hooks, commands, reasons: decision.reasons },
|
|
56
|
+
decision,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
33
59
|
// ============================================================================
|
|
34
60
|
// Plugin Manager
|
|
35
61
|
// ============================================================================
|
|
@@ -98,11 +124,14 @@ export class PluginManager {
|
|
|
98
124
|
/**
|
|
99
125
|
* Install a plugin from npm
|
|
100
126
|
*/
|
|
101
|
-
async installFromNpm(packageName, version) {
|
|
127
|
+
async installFromNpm(packageName, version, opts = {}) {
|
|
102
128
|
if (!this.manifest) {
|
|
103
129
|
await this.initialize();
|
|
104
130
|
}
|
|
105
131
|
const versionSpec = version ? `${packageName}@${version}` : packageName;
|
|
132
|
+
const verify = opts.verify !== false;
|
|
133
|
+
const warnings = [];
|
|
134
|
+
let tmpDir;
|
|
106
135
|
try {
|
|
107
136
|
// Check if already installed
|
|
108
137
|
if (this.manifest.plugins[packageName]) {
|
|
@@ -116,23 +145,56 @@ export class PluginManager {
|
|
|
116
145
|
await this.ensureDirectory(installDir);
|
|
117
146
|
// Validate package name to prevent injection (S-3)
|
|
118
147
|
validatePackageName(versionSpec);
|
|
148
|
+
// #3557: with --verify, a registry checksum must match the tarball we
|
|
149
|
+
// install. Pack first, hash that exact file, then install from it, so the
|
|
150
|
+
// bytes that were checked are the bytes that get installed.
|
|
151
|
+
let installTarget = versionSpec;
|
|
152
|
+
let verification = verify ? 'npm-integrity' : 'skipped';
|
|
153
|
+
const expected = verify ? parseSha256Checksum(opts.expectedChecksum) : null;
|
|
154
|
+
if (expected) {
|
|
155
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ruflo-plugin-verify-'));
|
|
156
|
+
const packed = await runNpm(['pack', versionSpec, '--pack-destination', tmpDir, '--json', '--ignore-scripts'], 120000);
|
|
157
|
+
const info = JSON.parse(packed.stdout);
|
|
158
|
+
const filename = info[0]?.filename;
|
|
159
|
+
if (!filename)
|
|
160
|
+
throw new Error(`npm pack returned no tarball for ${versionSpec}`);
|
|
161
|
+
const tarball = path.join(tmpDir, path.basename(filename));
|
|
162
|
+
const actual = sha256Hex(fs.readFileSync(tarball));
|
|
163
|
+
if (actual !== expected) {
|
|
164
|
+
return {
|
|
165
|
+
success: false,
|
|
166
|
+
error: `Checksum mismatch for ${versionSpec}: registry expects sha256:${expected}, ` +
|
|
167
|
+
`downloaded tarball is sha256:${actual}. Refusing to install (--verify).`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
installTarget = tarball;
|
|
171
|
+
verification = 'checksum';
|
|
172
|
+
}
|
|
173
|
+
else if (verify && opts.expectedChecksum) {
|
|
174
|
+
warnings.push(`Registry checksum "${opts.expectedChecksum}" is not a verifiable sha256 digest; ` +
|
|
175
|
+
`relying on npm's own registry integrity check.`);
|
|
176
|
+
}
|
|
119
177
|
// Use npm to install (array form prevents shell injection)
|
|
120
178
|
console.log(`[PluginManager] Installing ${versionSpec}...`);
|
|
121
|
-
|
|
179
|
+
// #3557 follow-up: lifecycle scripts run before the package's own trust
|
|
180
|
+
// declaration can be read, so untrusted installs skip them entirely.
|
|
181
|
+
const scriptsRun = shouldRunInstallScripts(opts);
|
|
182
|
+
const installArgs = ['install', '--prefix', this.config.pluginsDir, installTarget];
|
|
183
|
+
if (!scriptsRun) {
|
|
184
|
+
installArgs.push('--ignore-scripts');
|
|
185
|
+
warnings.push(`Install scripts were skipped for ${packageName} (--ignore-scripts): it is not registry-vouched and --trust was not given. ` +
|
|
186
|
+
`Reinstall with --trust to run them.`);
|
|
187
|
+
}
|
|
188
|
+
await runNpm(installArgs, 120000);
|
|
122
189
|
// Get installed version
|
|
123
190
|
const packageJsonPath = path.join(installDir, packageName, 'package.json');
|
|
124
191
|
let installedVersion = version || 'latest';
|
|
125
|
-
let
|
|
126
|
-
let hooks = [];
|
|
192
|
+
let pkg = {};
|
|
127
193
|
if (fs.existsSync(packageJsonPath)) {
|
|
128
|
-
|
|
129
|
-
installedVersion = pkg.version;
|
|
130
|
-
// Check for claude-flow plugin metadata
|
|
131
|
-
if (pkg['claude-flow']) {
|
|
132
|
-
commands = pkg['claude-flow'].commands || [];
|
|
133
|
-
hooks = pkg['claude-flow'].hooks || [];
|
|
134
|
-
}
|
|
194
|
+
pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
195
|
+
installedVersion = String(pkg.version ?? installedVersion);
|
|
135
196
|
}
|
|
197
|
+
const trusted = applyTrustPolicy(pkg, opts);
|
|
136
198
|
// Create plugin entry
|
|
137
199
|
const plugin = {
|
|
138
200
|
name: packageName,
|
|
@@ -141,25 +203,34 @@ export class PluginManager {
|
|
|
141
203
|
enabled: true,
|
|
142
204
|
source: 'npm',
|
|
143
205
|
path: path.join(installDir, packageName),
|
|
144
|
-
commands,
|
|
145
|
-
hooks,
|
|
206
|
+
commands: trusted.commands,
|
|
207
|
+
hooks: trusted.hooks,
|
|
208
|
+
trustLevel: trusted.trustLevel,
|
|
209
|
+
permissions: trusted.permissions,
|
|
210
|
+
verification,
|
|
211
|
+
scriptsRun,
|
|
212
|
+
...(trusted.withheld ? { withheld: trusted.withheld } : {}),
|
|
146
213
|
};
|
|
147
214
|
// Save to manifest
|
|
148
215
|
this.manifest.plugins[packageName] = plugin;
|
|
149
216
|
await this.saveManifest();
|
|
150
217
|
console.log(`[PluginManager] Installed ${packageName}@${installedVersion}`);
|
|
151
|
-
return { success: true, plugin };
|
|
218
|
+
return { success: true, plugin, decision: trusted.decision, warnings };
|
|
152
219
|
}
|
|
153
220
|
catch (error) {
|
|
154
221
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
155
222
|
console.error(`[PluginManager] Failed to install ${packageName}:`, errorMsg);
|
|
156
223
|
return { success: false, error: errorMsg };
|
|
157
224
|
}
|
|
225
|
+
finally {
|
|
226
|
+
if (tmpDir)
|
|
227
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
228
|
+
}
|
|
158
229
|
}
|
|
159
230
|
/**
|
|
160
231
|
* Install a plugin from a local path
|
|
161
232
|
*/
|
|
162
|
-
async installFromLocal(sourcePath) {
|
|
233
|
+
async installFromLocal(sourcePath, opts = {}) {
|
|
163
234
|
if (!this.manifest) {
|
|
164
235
|
await this.initialize();
|
|
165
236
|
}
|
|
@@ -182,6 +253,10 @@ export class PluginManager {
|
|
|
182
253
|
error: `Plugin ${packageName} is already installed`,
|
|
183
254
|
};
|
|
184
255
|
}
|
|
256
|
+
// #3557: record declared trust/permissions; withhold hooks and commands
|
|
257
|
+
// the policy doesn't allow without --trust. A local path has no registry
|
|
258
|
+
// entry to vouch for it, so the plugin's own declaration decides.
|
|
259
|
+
const trusted = applyTrustPolicy(pkg, { verify: opts.verify, trust: opts.trust });
|
|
185
260
|
// Create plugin entry (link to local path, don't copy)
|
|
186
261
|
const plugin = {
|
|
187
262
|
name: packageName,
|
|
@@ -190,14 +265,20 @@ export class PluginManager {
|
|
|
190
265
|
enabled: true,
|
|
191
266
|
source: 'local',
|
|
192
267
|
path: absolutePath,
|
|
193
|
-
commands:
|
|
194
|
-
hooks:
|
|
268
|
+
commands: trusted.commands,
|
|
269
|
+
hooks: trusted.hooks,
|
|
270
|
+
trustLevel: trusted.trustLevel,
|
|
271
|
+
permissions: trusted.permissions,
|
|
272
|
+
verification: opts.verify === false ? 'skipped' : 'policy',
|
|
273
|
+
// A local install links the path; it never runs npm or package scripts.
|
|
274
|
+
scriptsRun: false,
|
|
275
|
+
...(trusted.withheld ? { withheld: trusted.withheld } : {}),
|
|
195
276
|
};
|
|
196
277
|
// Save to manifest
|
|
197
278
|
this.manifest.plugins[packageName] = plugin;
|
|
198
279
|
await this.saveManifest();
|
|
199
280
|
console.log(`[PluginManager] Installed local plugin ${packageName}@${pkg.version}`);
|
|
200
|
-
return { success: true, plugin };
|
|
281
|
+
return { success: true, plugin, decision: trusted.decision };
|
|
201
282
|
}
|
|
202
283
|
catch (error) {
|
|
203
284
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
@@ -345,8 +426,13 @@ export class PluginManager {
|
|
|
345
426
|
const versionSpec = version ? `${packageName}@${version}` : `${packageName}@latest`;
|
|
346
427
|
// Validate package name to prevent injection (S-3)
|
|
347
428
|
validatePackageName(versionSpec);
|
|
348
|
-
// Reinstall with new version (array form prevents shell injection)
|
|
349
|
-
|
|
429
|
+
// Reinstall with new version (array form prevents shell injection).
|
|
430
|
+
// An install recorded with scriptsRun:false stays script-free on upgrade;
|
|
431
|
+
// legacy entries (no field) keep their pre-#3557 behaviour.
|
|
432
|
+
const upgradeArgs = ['install', '--prefix', this.config.pluginsDir, versionSpec];
|
|
433
|
+
if (existing.scriptsRun === false)
|
|
434
|
+
upgradeArgs.push('--ignore-scripts');
|
|
435
|
+
await runNpm(upgradeArgs, 120000);
|
|
350
436
|
// Update manifest
|
|
351
437
|
const installDir = path.join(this.config.pluginsDir, 'node_modules');
|
|
352
438
|
const packageJsonPath = path.join(installDir, packageName, 'package.json');
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin install trust policy (#3557).
|
|
3
|
+
*
|
|
4
|
+
* `plugins install --verify` used to be parsed and never read, and a plugin's
|
|
5
|
+
* declared `trustLevel`/`permissions` were neither recorded nor enforced.
|
|
6
|
+
* These pure helpers decide what an install may register.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Permissions a plugin may declare and still have its hooks and commands
|
|
10
|
+
* registered without `--trust`. Anything else (filesystem, network, shell,
|
|
11
|
+
* secrets, config, privileged, …) needs an explicit `--trust`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_PLUGIN_PERMISSIONS: readonly string[];
|
|
14
|
+
/** Trust levels that are withheld unless the user passes `--trust`. */
|
|
15
|
+
export declare const UNTRUSTED_TRUST_LEVELS: readonly string[];
|
|
16
|
+
/** Registry trust levels that the registry itself vouches for. */
|
|
17
|
+
export declare const REGISTRY_VOUCHED_TRUST_LEVELS: readonly string[];
|
|
18
|
+
export interface DeclaredTrust {
|
|
19
|
+
trustLevel?: string;
|
|
20
|
+
permissions: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface TrustDecision {
|
|
23
|
+
/** Hooks and commands may be registered. */
|
|
24
|
+
allowed: boolean;
|
|
25
|
+
/** Verification was skipped with `--no-verify`. */
|
|
26
|
+
verificationSkipped: boolean;
|
|
27
|
+
/** Why registration was withheld (empty when allowed). */
|
|
28
|
+
reasons: string[];
|
|
29
|
+
/** Declared permissions outside {@link DEFAULT_PLUGIN_PERMISSIONS}. */
|
|
30
|
+
excessPermissions: string[];
|
|
31
|
+
}
|
|
32
|
+
export interface TrustOptions {
|
|
33
|
+
/** `--verify` (default true). */
|
|
34
|
+
verify: boolean;
|
|
35
|
+
/** `--trust` (default false). */
|
|
36
|
+
trust: boolean;
|
|
37
|
+
/** Trust level from a registry entry, when the plugin was found there. */
|
|
38
|
+
registryTrustLevel?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read the trust declaration from a plugin's package.json. The `claude-flow`
|
|
42
|
+
* block wins; top-level fields are accepted as a fallback.
|
|
43
|
+
*/
|
|
44
|
+
export declare function readDeclaredTrust(pkg: Record<string, unknown>): DeclaredTrust;
|
|
45
|
+
/** Decide whether an install may register the plugin's hooks and commands. */
|
|
46
|
+
export declare function evaluatePluginTrust(declared: DeclaredTrust, opts: TrustOptions): TrustDecision;
|
|
47
|
+
/**
|
|
48
|
+
* Whether npm may run the package's lifecycle scripts (preinstall/install/
|
|
49
|
+
* postinstall, and those of its dependencies). Scripts execute before the
|
|
50
|
+
* package's own trust declaration can be read, so only an explicit `--trust`
|
|
51
|
+
* or a registry-vouched entry allows them; everything else installs with
|
|
52
|
+
* `--ignore-scripts`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function shouldRunInstallScripts(opts: {
|
|
55
|
+
trust?: boolean;
|
|
56
|
+
registryTrustLevel?: string;
|
|
57
|
+
}): boolean;
|
|
58
|
+
/** A registry checksum we can actually verify: `sha256:` + 64 hex chars. */
|
|
59
|
+
export declare function parseSha256Checksum(checksum: unknown): string | null;
|
|
60
|
+
export declare function sha256Hex(data: Buffer): string;
|
|
61
|
+
//# sourceMappingURL=trust-policy.d.ts.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin install trust policy (#3557).
|
|
3
|
+
*
|
|
4
|
+
* `plugins install --verify` used to be parsed and never read, and a plugin's
|
|
5
|
+
* declared `trustLevel`/`permissions` were neither recorded nor enforced.
|
|
6
|
+
* These pure helpers decide what an install may register.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash } from 'crypto';
|
|
9
|
+
/**
|
|
10
|
+
* Permissions a plugin may declare and still have its hooks and commands
|
|
11
|
+
* registered without `--trust`. Anything else (filesystem, network, shell,
|
|
12
|
+
* secrets, config, privileged, …) needs an explicit `--trust`.
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_PLUGIN_PERMISSIONS = Object.freeze(['memory:read']);
|
|
15
|
+
/** Trust levels that are withheld unless the user passes `--trust`. */
|
|
16
|
+
export const UNTRUSTED_TRUST_LEVELS = Object.freeze(['untrusted', 'unverified']);
|
|
17
|
+
/** Registry trust levels that the registry itself vouches for. */
|
|
18
|
+
export const REGISTRY_VOUCHED_TRUST_LEVELS = Object.freeze(['official', 'verified']);
|
|
19
|
+
function asStringList(value) {
|
|
20
|
+
if (!Array.isArray(value))
|
|
21
|
+
return [];
|
|
22
|
+
return value.filter((v) => typeof v === 'string' && v.trim() !== '').map((v) => v.trim());
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Read the trust declaration from a plugin's package.json. The `claude-flow`
|
|
26
|
+
* block wins; top-level fields are accepted as a fallback.
|
|
27
|
+
*/
|
|
28
|
+
export function readDeclaredTrust(pkg) {
|
|
29
|
+
const block = (pkg['claude-flow'] && typeof pkg['claude-flow'] === 'object'
|
|
30
|
+
? pkg['claude-flow']
|
|
31
|
+
: {});
|
|
32
|
+
const trustLevel = typeof block.trustLevel === 'string'
|
|
33
|
+
? block.trustLevel
|
|
34
|
+
: typeof pkg.trustLevel === 'string' ? pkg.trustLevel : undefined;
|
|
35
|
+
const permissions = asStringList(block.permissions).length > 0
|
|
36
|
+
? asStringList(block.permissions)
|
|
37
|
+
: asStringList(pkg.permissions);
|
|
38
|
+
return { trustLevel, permissions };
|
|
39
|
+
}
|
|
40
|
+
/** Decide whether an install may register the plugin's hooks and commands. */
|
|
41
|
+
export function evaluatePluginTrust(declared, opts) {
|
|
42
|
+
const excessPermissions = declared.permissions.filter((p) => !DEFAULT_PLUGIN_PERMISSIONS.includes(p));
|
|
43
|
+
if (!opts.verify) {
|
|
44
|
+
return { allowed: true, verificationSkipped: true, reasons: [], excessPermissions };
|
|
45
|
+
}
|
|
46
|
+
const reasons = [];
|
|
47
|
+
const registryVouched = opts.registryTrustLevel !== undefined
|
|
48
|
+
&& REGISTRY_VOUCHED_TRUST_LEVELS.includes(opts.registryTrustLevel);
|
|
49
|
+
if (!registryVouched) {
|
|
50
|
+
if (declared.trustLevel && UNTRUSTED_TRUST_LEVELS.includes(declared.trustLevel)) {
|
|
51
|
+
reasons.push(`declares trustLevel "${declared.trustLevel}"`);
|
|
52
|
+
}
|
|
53
|
+
if (excessPermissions.length > 0) {
|
|
54
|
+
reasons.push(`declares permissions beyond the default set (${DEFAULT_PLUGIN_PERMISSIONS.join(', ')}): ${excessPermissions.join(', ')}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (reasons.length > 0 && !opts.trust) {
|
|
58
|
+
return { allowed: false, verificationSkipped: false, reasons, excessPermissions };
|
|
59
|
+
}
|
|
60
|
+
return { allowed: true, verificationSkipped: false, reasons: [], excessPermissions };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Whether npm may run the package's lifecycle scripts (preinstall/install/
|
|
64
|
+
* postinstall, and those of its dependencies). Scripts execute before the
|
|
65
|
+
* package's own trust declaration can be read, so only an explicit `--trust`
|
|
66
|
+
* or a registry-vouched entry allows them; everything else installs with
|
|
67
|
+
* `--ignore-scripts`.
|
|
68
|
+
*/
|
|
69
|
+
export function shouldRunInstallScripts(opts) {
|
|
70
|
+
if (opts.trust === true)
|
|
71
|
+
return true;
|
|
72
|
+
return opts.registryTrustLevel !== undefined && REGISTRY_VOUCHED_TRUST_LEVELS.includes(opts.registryTrustLevel);
|
|
73
|
+
}
|
|
74
|
+
/** A registry checksum we can actually verify: `sha256:` + 64 hex chars. */
|
|
75
|
+
export function parseSha256Checksum(checksum) {
|
|
76
|
+
if (typeof checksum !== 'string')
|
|
77
|
+
return null;
|
|
78
|
+
const m = /^sha256:([0-9a-f]{64})$/i.exec(checksum.trim());
|
|
79
|
+
return m ? m[1].toLowerCase() : null;
|
|
80
|
+
}
|
|
81
|
+
export function sha256Hex(data) {
|
|
82
|
+
return createHash('sha256').update(data).digest('hex');
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=trust-policy.js.map
|
|
@@ -17,6 +17,12 @@ export declare function issuePolicyApproval(approval: Omit<PolicyApproval, 'uses
|
|
|
17
17
|
issuedAt?: number;
|
|
18
18
|
}, projectRoot?: string, approvalIssuerVerifier?: (issuer: string) => boolean): Promise<PolicyApproval>;
|
|
19
19
|
export declare function revokePolicyApproval(id: string, projectRoot?: string): Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Read-only verification (#3568). It must not run inside
|
|
22
|
+
* `withPolicyTransaction`, whose own post-operation check throws a generic
|
|
23
|
+
* `policy-ledger-verification-failed` and hides which check failed. The only
|
|
24
|
+
* write is persisting an anchor established for a pre-anchor ledger.
|
|
25
|
+
*/
|
|
20
26
|
export declare function verifyPolicyLedger(projectRoot?: string): Promise<ReturnType<AgenticPolicyEngine['verifyLedger']>>;
|
|
21
27
|
export declare function authorizeMcpTool(toolName: string, input: Record<string, unknown>, context?: Record<string, unknown>, attributes?: Readonly<{
|
|
22
28
|
actionType?: string;
|
|
@@ -4,6 +4,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
4
4
|
import { closeSync, constants as fsConstants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readlinkSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
5
5
|
import { dirname, join, resolve } from 'node:path';
|
|
6
6
|
import { hostname, userInfo } from 'node:os';
|
|
7
|
+
import { syncPolicyProjection } from '../mods/policy-projection.js';
|
|
7
8
|
const POLICY_DIR = join('.claude-flow', 'policy');
|
|
8
9
|
const POLICY_FILE = 'state.json';
|
|
9
10
|
const LOCK_FILE = 'state.lock';
|
|
@@ -227,6 +228,20 @@ function verifyStateAnchor(projectRoot, state) {
|
|
|
227
228
|
}
|
|
228
229
|
}
|
|
229
230
|
async function writePolicyState(projectRoot, statePath, state) {
|
|
231
|
+
await writePolicyStateFiles(projectRoot, statePath, state);
|
|
232
|
+
// ADR-404: the ruflo mod reads Claude Code tool rules from a small
|
|
233
|
+
// projection, never from state.json. Written only after the state and its
|
|
234
|
+
// anchor are safely down; a failure here never fails the state write. A
|
|
235
|
+
// projection left stale by such a failure can only tighten, never loosen,
|
|
236
|
+
// a Claude Code verdict (the mod merges with `stricter`).
|
|
237
|
+
try {
|
|
238
|
+
syncPolicyProjection(projectRoot, state);
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
process.stderr.write(`[policy] claude-code projection not written: ${error.message}\n`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async function writePolicyStateFiles(projectRoot, statePath, state) {
|
|
230
245
|
const anchorPath = trustPaths(projectRoot).anchor;
|
|
231
246
|
if (state.mode === 'enforce' || existsSync(anchorPath)) {
|
|
232
247
|
const key = trustKey(projectRoot, true);
|
|
@@ -338,9 +353,12 @@ export async function withPolicyTransaction(projectRoot, operation, options = {}
|
|
|
338
353
|
approvalIssuerVerifier: options.approvalIssuerVerifier,
|
|
339
354
|
});
|
|
340
355
|
const result = await operation(engine);
|
|
341
|
-
|
|
356
|
+
// Verify before exporting: verification establishes the ledger anchor
|
|
357
|
+
// (#3568) on state written before the anchor existed, and that anchor
|
|
358
|
+
// must be part of what is persisted.
|
|
342
359
|
if (!engine.verifyLedger().valid)
|
|
343
360
|
throw new Error('policy-ledger-verification-failed');
|
|
361
|
+
const nextState = engine.exportState();
|
|
344
362
|
await writePolicyState(projectRoot, target.state, nextState);
|
|
345
363
|
return result;
|
|
346
364
|
}
|
|
@@ -393,8 +411,30 @@ export async function issuePolicyApproval(approval, projectRoot = process.cwd(),
|
|
|
393
411
|
export async function revokePolicyApproval(id, projectRoot = process.cwd()) {
|
|
394
412
|
return withPolicyTransaction(projectRoot, (engine) => engine.revokeApproval(id));
|
|
395
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Read-only verification (#3568). It must not run inside
|
|
416
|
+
* `withPolicyTransaction`, whose own post-operation check throws a generic
|
|
417
|
+
* `policy-ledger-verification-failed` and hides which check failed. The only
|
|
418
|
+
* write is persisting an anchor established for a pre-anchor ledger.
|
|
419
|
+
*/
|
|
396
420
|
export async function verifyPolicyLedger(projectRoot = process.cwd()) {
|
|
397
|
-
|
|
421
|
+
const target = paths(projectRoot);
|
|
422
|
+
mkdirSync(target.dir, { recursive: true, mode: 0o700 });
|
|
423
|
+
const release = await acquireLock(target.lock);
|
|
424
|
+
try {
|
|
425
|
+
const engine = AgenticPolicyEngine.fromState(loadPolicyState(projectRoot), {
|
|
426
|
+
signingKey: process.env.CLAUDE_FLOW_POLICY_SIGNING_KEY,
|
|
427
|
+
keyId: process.env.CLAUDE_FLOW_POLICY_KEY_ID,
|
|
428
|
+
});
|
|
429
|
+
const result = engine.verifyLedger();
|
|
430
|
+
if (result.anchor === 'established-now') {
|
|
431
|
+
await writePolicyState(projectRoot, target.state, engine.exportState());
|
|
432
|
+
}
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
finally {
|
|
436
|
+
release();
|
|
437
|
+
}
|
|
398
438
|
}
|
|
399
439
|
/**
|
|
400
440
|
* Resolve the caller identity for `authorizeMcpTool`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.50.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"@claude-flow/mcp": "3.0.0",
|
|
104
104
|
"@claude-flow/neural": "3.0.0",
|
|
105
105
|
"@claude-flow/plugin-agent-federation": "1.0.0",
|
|
106
|
-
"@claude-flow/security": "3.0.
|
|
106
|
+
"@claude-flow/security": "3.0.1",
|
|
107
107
|
"@claude-flow/shared": "3.0.0",
|
|
108
108
|
"@iarna/toml": "^2.2.5",
|
|
109
109
|
"@noble/ed25519": "2.3.0",
|