fullcourtdefense-cli 1.34.21 → 1.34.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/actionIdentity.d.ts +1 -0
- package/dist/actionIdentity.js +2 -0
- package/dist/actionPolicyEngine.d.ts +5 -0
- package/dist/actionPolicyEngine.js +273 -30
- package/dist/agentTerminal.d.ts +1 -3
- package/dist/agentTerminal.js +14 -29
- package/dist/approvalPrompt.d.ts +7 -0
- package/dist/approvalPrompt.js +46 -0
- package/dist/askDialog.d.ts +87 -0
- package/dist/askDialog.js +209 -0
- package/dist/blockExplanation.js +18 -6
- package/dist/boundaryVerification.d.ts +20 -0
- package/dist/boundaryVerification.js +147 -0
- package/dist/commands/daemon.js +198 -70
- package/dist/commands/deterministicGuard.d.ts +2 -0
- package/dist/commands/deterministicGuard.js +161 -9
- package/dist/commands/hook.js +81 -29
- package/dist/commands/installClaudeHook.js +11 -5
- package/dist/commands/installCursorHook.js +5 -4
- package/dist/commands/mcpGateway.d.ts +2 -0
- package/dist/commands/mcpGateway.js +163 -56
- package/dist/commands/onboard.js +6 -5
- package/dist/commands/onboardingSummary.d.ts +5 -0
- package/dist/commands/onboardingSummary.js +11 -0
- package/dist/commands/protectedRun.d.ts +14 -0
- package/dist/commands/protectedRun.js +207 -0
- package/dist/containment-runtime/index.mjs +30118 -0
- package/dist/containment-runtime/licenses/_anthropic-ai_sandbox-runtime.txt +201 -0
- package/dist/containment-runtime/licenses/_pondwader_socks5-server.txt +20 -0
- package/dist/containment-runtime/licenses/commander.txt +22 -0
- package/dist/containment-runtime/licenses/node-forge.txt +331 -0
- package/dist/containment-runtime/licenses/zod.txt +21 -0
- package/dist/containment-runtime/runtime-version.json +1 -0
- package/dist/containment-runtime/vendor/java-proxy-agent/build.ts +95 -0
- package/dist/containment-runtime/vendor/java-proxy-agent/srt-proxy-agent.jar +0 -0
- package/dist/containment-runtime/vendor/seccomp/arm64/apply-seccomp +0 -0
- package/dist/containment-runtime/vendor/seccomp/build.ts +69 -0
- package/dist/containment-runtime/vendor/seccomp/x64/apply-seccomp +0 -0
- package/dist/containment-runtime/vendor/srt-win/arm64/srt-win.exe +0 -0
- package/dist/containment-runtime/vendor/srt-win/build.ts +21 -0
- package/dist/containment-runtime/vendor/srt-win/x64/srt-win.exe +0 -0
- package/dist/containmentLease.d.ts +5 -0
- package/dist/containmentLease.js +72 -0
- package/dist/detectionUpdates.d.ts +23 -3
- package/dist/detectionUpdates.js +45 -14
- package/dist/detectorArtifact.d.ts +17 -0
- package/dist/detectorArtifact.js +35 -0
- package/dist/detectorBaseline.d.ts +2 -0
- package/dist/detectorBaseline.js +5 -0
- package/dist/detectorModule.d.ts +9 -0
- package/dist/detectorModule.js +84 -0
- package/dist/detectorRuntime.d.ts +21 -0
- package/dist/detectorRuntime.js +49 -0
- package/dist/detectorUpdates.d.ts +21 -0
- package/dist/detectorUpdates.js +86 -0
- package/dist/devConfirm.d.ts +97 -15
- package/dist/devConfirm.js +447 -117
- package/dist/distress.d.ts +2 -0
- package/dist/distress.js +2 -0
- package/dist/frictionRollout.js +103 -0
- package/dist/gatewayRuntime.d.ts +19 -0
- package/dist/gatewayRuntime.js +93 -0
- package/dist/hookSlim.js +17 -18
- package/dist/index.js +35 -0
- package/dist/localDetectionUpdates.d.ts +3 -1
- package/dist/localDetectionUpdates.js +8 -1
- package/dist/localSafetySnapshot.d.ts +9 -5
- package/dist/localSafetySnapshot.js +42 -15
- package/dist/mcpToolReview.d.ts +17 -0
- package/dist/mcpToolReview.js +127 -0
- package/dist/mcpToolTrust.d.ts +22 -0
- package/dist/mcpToolTrust.js +66 -0
- package/dist/nativeHook.d.ts +4 -0
- package/dist/nativeHook.js +31 -17
- package/dist/policyRefresh.d.ts +3 -0
- package/dist/policyRefresh.js +27 -0
- package/dist/protectionMaintenance.d.ts +10 -0
- package/dist/protectionMaintenance.js +64 -0
- package/dist/protectionProfile.d.ts +26 -0
- package/dist/protectionProfile.js +63 -0
- package/dist/runtimeConfig.d.ts +2 -0
- package/dist/runtimeConfig.js +65 -26
- package/dist/selfTest.d.ts +2 -0
- package/dist/selfTest.js +9 -1
- package/dist/sessionLimits.d.ts +2 -0
- package/dist/sessionLimits.js +24 -0
- package/dist/telemetry.d.ts +5 -0
- package/dist/telemetry.js +8 -0
- package/dist/version.json +1 -1
- package/package.json +10 -6
package/dist/distress.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export declare const DISTRESS: {
|
|
|
32
32
|
readonly AUTH_BROKEN: "auth_broken";
|
|
33
33
|
/** Control plane unreachable (network / DNS / proxy / timeout). */
|
|
34
34
|
readonly NETWORK_DOWN: "network_down";
|
|
35
|
+
/** A policy response arrived, but the replacement cache could not be saved. */
|
|
36
|
+
readonly POLICY_CACHE_WRITE_FAILED: "policy_cache_write_failed";
|
|
35
37
|
/**
|
|
36
38
|
* Control plane REACHED and credentials accepted, but it refused the policy
|
|
37
39
|
* request (HTTP 400/404/409/422/5xx). A server-side or data fault — the fix
|
package/dist/distress.js
CHANGED
|
@@ -77,6 +77,8 @@ exports.DISTRESS = {
|
|
|
77
77
|
AUTH_BROKEN: 'auth_broken',
|
|
78
78
|
/** Control plane unreachable (network / DNS / proxy / timeout). */
|
|
79
79
|
NETWORK_DOWN: 'network_down',
|
|
80
|
+
/** A policy response arrived, but the replacement cache could not be saved. */
|
|
81
|
+
POLICY_CACHE_WRITE_FAILED: 'policy_cache_write_failed',
|
|
80
82
|
/**
|
|
81
83
|
* Control plane REACHED and credentials accepted, but it refused the policy
|
|
82
84
|
* request (HTTP 400/404/409/422/5xx). A server-side or data fault — the fix
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// ../../backend/src/modules/discovery/friction-rollout.ts
|
|
21
|
+
var friction_rollout_exports = {};
|
|
22
|
+
__export(friction_rollout_exports, {
|
|
23
|
+
assessFrictionRollout: () => assessFrictionRollout
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(friction_rollout_exports);
|
|
26
|
+
function assessFrictionRollout(input) {
|
|
27
|
+
const value = input;
|
|
28
|
+
if (!value || typeof value.baselineVersion !== "string" || typeof value.candidateVersion !== "string" || !/^\d+\.\d+\.\d+$/.test(value.baselineVersion) || !/^\d+\.\d+\.\d+$/.test(value.candidateVersion) || !["replay", "real_hook", "real_ide", "production"].includes(String(value.evidenceLevel)) || !Array.isArray(value.observations) || value.observations.length > 2e3) throw new Error("Invalid paired evidence");
|
|
29
|
+
const artifact = (raw, bundled = false) => {
|
|
30
|
+
const item = raw;
|
|
31
|
+
if (!item || !Number.isSafeInteger(item.revision) || Number(item.revision) < (bundled ? 0 : 1) || typeof item.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(item.sha256)) throw new Error("Invalid detector artifact identity");
|
|
32
|
+
return { revision: Number(item.revision), sha256: item.sha256 };
|
|
33
|
+
};
|
|
34
|
+
const hasDetection = value.baselineDetection !== void 0 || value.candidateDetection !== void 0;
|
|
35
|
+
const baselineDetection = hasDetection ? artifact(value.baselineDetection, true) : void 0;
|
|
36
|
+
const candidateDetection = hasDetection ? artifact(value.candidateDetection) : void 0;
|
|
37
|
+
const sameCli = value.baselineVersion === value.candidateVersion;
|
|
38
|
+
if (baselineDetection && candidateDetection) {
|
|
39
|
+
if (candidateDetection.revision < baselineDetection.revision || candidateDetection.revision === baselineDetection.revision && candidateDetection.sha256 !== baselineDetection.sha256) {
|
|
40
|
+
throw new Error("Detector revision downgrade or reuse");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (sameCli && (!baselineDetection || !candidateDetection || candidateDetection.revision <= baselineDetection.revision || candidateDetection.sha256 === baselineDetection.sha256)) throw new Error("Same CLI requires distinct, increasing detector artifacts");
|
|
44
|
+
const verdicts = ["allow", "block", "require_approval"];
|
|
45
|
+
const seen = /* @__PURE__ */ new Set();
|
|
46
|
+
let fixed = 0, regressed = 0, unchanged = 0, missingEffects = 0, remainingErrors = 0;
|
|
47
|
+
let benign = 0, guarded = 0, baselineInterruptions = 0, candidateInterruptions = 0;
|
|
48
|
+
const families = /* @__PURE__ */ new Set();
|
|
49
|
+
const machines = /* @__PURE__ */ new Set();
|
|
50
|
+
for (const row of value.observations) {
|
|
51
|
+
if (!row || !["caseId", "family", "machineId", "policyHash"].every((key2) => typeof row[key2] === "string" && /^[a-zA-Z0-9_.:-]{1,100}$/.test(row[key2])) || ![row.expected, row.baseline, row.candidate].every((v) => verdicts.includes(v)) || typeof row.sideEffectVerified !== "boolean") throw new Error("Invalid observation");
|
|
52
|
+
const key = JSON.stringify([row.machineId, row.caseId, row.policyHash]);
|
|
53
|
+
if (seen.has(key)) throw new Error("Duplicate paired observation");
|
|
54
|
+
seen.add(key);
|
|
55
|
+
families.add(row.family);
|
|
56
|
+
machines.add(row.machineId);
|
|
57
|
+
const before = row.baseline === row.expected, after = row.candidate === row.expected;
|
|
58
|
+
if (!before && after) fixed++;
|
|
59
|
+
else if (before && !after) regressed++;
|
|
60
|
+
else unchanged++;
|
|
61
|
+
if (!after) remainingErrors++;
|
|
62
|
+
if (!row.sideEffectVerified) missingEffects++;
|
|
63
|
+
if (row.expected === "allow") {
|
|
64
|
+
benign++;
|
|
65
|
+
if (row.baseline !== "allow") baselineInterruptions++;
|
|
66
|
+
if (row.candidate !== "allow") candidateInterruptions++;
|
|
67
|
+
} else guarded++;
|
|
68
|
+
}
|
|
69
|
+
const reasons = [];
|
|
70
|
+
if (!benign || !guarded) reasons.push("Both benign actions and prohibited/approval counterparts are required");
|
|
71
|
+
if (!fixed) reasons.push("No measured correction");
|
|
72
|
+
if (regressed || remainingErrors) reasons.push("Candidate has regressions or unresolved mismatches");
|
|
73
|
+
if (missingEffects) reasons.push("Actual side-effect verification is missing");
|
|
74
|
+
if (value.evidenceLevel !== "production") reasons.push("This evidence cannot establish production improvement");
|
|
75
|
+
return {
|
|
76
|
+
schema: "fcd.paired-feedback.v1",
|
|
77
|
+
baselineVersion: value.baselineVersion,
|
|
78
|
+
candidateVersion: value.candidateVersion,
|
|
79
|
+
...hasDetection ? { baselineDetection, candidateDetection } : {},
|
|
80
|
+
changeKind: sameCli ? "detector_only" : hasDetection && baselineDetection.sha256 !== candidateDetection.sha256 ? "cli_and_detector" : "cli",
|
|
81
|
+
evidenceLevel: value.evidenceLevel,
|
|
82
|
+
cases: seen.size,
|
|
83
|
+
families: families.size,
|
|
84
|
+
machines: machines.size,
|
|
85
|
+
fixed,
|
|
86
|
+
regressed,
|
|
87
|
+
unchanged,
|
|
88
|
+
remainingErrors,
|
|
89
|
+
missingEffects,
|
|
90
|
+
benign,
|
|
91
|
+
guarded,
|
|
92
|
+
baselineInterruptions,
|
|
93
|
+
candidateInterruptions,
|
|
94
|
+
eligibleForOwnerReview: reasons.length === 0,
|
|
95
|
+
reasons,
|
|
96
|
+
automaticPublication: false,
|
|
97
|
+
note: "Labels and side effects are reviewer-supplied evidence, not independently verified by this gate. Publication requires reviewed signed release evidence."
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
101
|
+
0 && (module.exports = {
|
|
102
|
+
assessFrictionRollout
|
|
103
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface GatewayRuntime {
|
|
2
|
+
executable: string;
|
|
3
|
+
entry: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Keep wrappers already owned by the MSI on its maintained runtime when a
|
|
7
|
+
* developer invokes another CLI copy. Unknown/missing runtimes still converge
|
|
8
|
+
* to the invoking CLI; arbitrary existing executables are never trusted merely
|
|
9
|
+
* because they exist. Only the canonical packaged Windows installation qualifies.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveGatewayRuntime(current: GatewayRuntime, existing?: {
|
|
12
|
+
command?: unknown;
|
|
13
|
+
args?: unknown;
|
|
14
|
+
}, options?: {
|
|
15
|
+
platform?: NodeJS.Platform;
|
|
16
|
+
env?: NodeJS.ProcessEnv;
|
|
17
|
+
}): GatewayRuntime;
|
|
18
|
+
/** Hooks share the gateway's installed owner, including the older full entry. */
|
|
19
|
+
export declare function resolveHookRuntime(current: GatewayRuntime, existingCommand?: string): GatewayRuntime;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolveGatewayRuntime = resolveGatewayRuntime;
|
|
37
|
+
exports.resolveHookRuntime = resolveHookRuntime;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
/**
|
|
41
|
+
* Keep wrappers already owned by the MSI on its maintained runtime when a
|
|
42
|
+
* developer invokes another CLI copy. Unknown/missing runtimes still converge
|
|
43
|
+
* to the invoking CLI; arbitrary existing executables are never trusted merely
|
|
44
|
+
* because they exist. Only the canonical packaged Windows installation qualifies.
|
|
45
|
+
*/
|
|
46
|
+
function resolveGatewayRuntime(current, existing, options = {}) {
|
|
47
|
+
if ((options.platform || process.platform) !== 'win32')
|
|
48
|
+
return current;
|
|
49
|
+
const env = options.env || process.env;
|
|
50
|
+
const normalize = (value) => path.resolve(value).toLowerCase();
|
|
51
|
+
const existingArgs = Array.isArray(existing?.args) ? existing.args : [];
|
|
52
|
+
for (const base of [env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', env.ProgramFiles || 'C:\\Program Files']) {
|
|
53
|
+
const root = path.join(base, 'FullCourtDefense');
|
|
54
|
+
const installed = { executable: path.join(root, 'runtime', 'node.exe'), entry: path.join(root, 'dist', 'index.js') };
|
|
55
|
+
// A fresh development-only config remains local to that runtime. Once the
|
|
56
|
+
// installed daemon owns it, other copies preserve that owner on every pass.
|
|
57
|
+
const belongsToInstall = [current.entry, existingArgs[0]].some(value => typeof value === 'string' && normalize(value) === normalize(installed.entry));
|
|
58
|
+
if (!belongsToInstall)
|
|
59
|
+
continue;
|
|
60
|
+
try {
|
|
61
|
+
const files = [root, path.dirname(installed.executable), path.dirname(installed.entry), installed.executable, installed.entry, path.join(root, 'package.json')];
|
|
62
|
+
if (files.some(file => fs.lstatSync(file).isSymbolicLink()))
|
|
63
|
+
continue;
|
|
64
|
+
if (!fs.statSync(installed.executable).isFile() || !fs.statSync(installed.entry).isFile())
|
|
65
|
+
continue;
|
|
66
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
67
|
+
if (pkg.name !== 'fullcourtdefense-cli')
|
|
68
|
+
continue;
|
|
69
|
+
return installed;
|
|
70
|
+
}
|
|
71
|
+
catch { /* Missing installation: repair using the running CLI. */ }
|
|
72
|
+
}
|
|
73
|
+
return current;
|
|
74
|
+
}
|
|
75
|
+
/** Hooks share the gateway's installed owner, including the older full entry. */
|
|
76
|
+
function resolveHookRuntime(current, existingCommand) {
|
|
77
|
+
const invocation = existingCommand?.match(/^(?:"[^"]*"|\S+)\s+(?:"([^"]*)"|(\S+))\s+hook(?:\s|$)/);
|
|
78
|
+
const gatewayEntry = (entry) => path.basename(entry).toLowerCase() === 'hookslim.js'
|
|
79
|
+
? path.join(path.dirname(entry), 'index.js') : entry;
|
|
80
|
+
const previousEntry = invocation?.[1] || invocation?.[2];
|
|
81
|
+
const mapped = { ...current, entry: gatewayEntry(current.entry) };
|
|
82
|
+
const owner = resolveGatewayRuntime(mapped, previousEntry ? { args: [gatewayEntry(previousEntry)] } : undefined);
|
|
83
|
+
if (owner === mapped)
|
|
84
|
+
return current;
|
|
85
|
+
const slim = path.join(path.dirname(owner.entry), 'hookSlim.js');
|
|
86
|
+
try {
|
|
87
|
+
const stat = fs.lstatSync(slim);
|
|
88
|
+
if (stat.isFile() && !stat.isSymbolicLink())
|
|
89
|
+
return { ...owner, entry: slim };
|
|
90
|
+
}
|
|
91
|
+
catch { /* Older installed releases use the full entry. */ }
|
|
92
|
+
return owner;
|
|
93
|
+
}
|
package/dist/hookSlim.js
CHANGED
|
@@ -91,19 +91,28 @@ async function main() {
|
|
|
91
91
|
const ipc = await (0, verdictIpcClient_1.requestDaemonVerdict)(args, raw, undefined, startedAt);
|
|
92
92
|
if (ipc.outcome === 'verdict') {
|
|
93
93
|
(0, hookIo_1.dbg)({ phase: 'ipc_verdict', event: args.event, exitCode: ipc.exitCode, entry: 'slim' });
|
|
94
|
-
|
|
94
|
+
const verdict = { stdout: ipc.stdout || '', stderr: ipc.stderr || '', exitCode: ipc.exitCode ?? 0 };
|
|
95
|
+
// The daemon never opens a window; an `ask` carries a dialog envelope for
|
|
96
|
+
// THIS process (the developer's session). Loaded only when one is present,
|
|
97
|
+
// so the warm allow/deny path stays three files.
|
|
98
|
+
if (verdict.stdout.includes('"fcdAsk"')) {
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
100
|
+
const { emitVerdictForIde } = require('./askDialog');
|
|
101
|
+
await emitVerdictForIde(args, verdict, 'slim-ipc');
|
|
102
|
+
}
|
|
103
|
+
if (verdict.stderr) {
|
|
95
104
|
try {
|
|
96
|
-
process.stderr.write(
|
|
105
|
+
process.stderr.write(verdict.stderr);
|
|
97
106
|
}
|
|
98
107
|
catch { /* stderr gone */ }
|
|
99
108
|
}
|
|
100
|
-
if (
|
|
109
|
+
if (verdict.stdout) {
|
|
101
110
|
try {
|
|
102
|
-
process.stdout.write(
|
|
111
|
+
process.stdout.write(verdict.stdout);
|
|
103
112
|
}
|
|
104
113
|
catch { /* stdout gone */ }
|
|
105
114
|
}
|
|
106
|
-
process.exit(
|
|
115
|
+
process.exit(verdict.exitCode);
|
|
107
116
|
}
|
|
108
117
|
(0, hookIo_1.dbg)({ phase: 'ipc_unavailable', event: args.event, detail: ipc.detail, entry: 'slim' });
|
|
109
118
|
}
|
|
@@ -118,19 +127,9 @@ async function main() {
|
|
|
118
127
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
119
128
|
const { markVerdictTiming } = require('./telemetry');
|
|
120
129
|
const result = await markVerdictTiming({ startedAt, path: 'local' }, () => evaluateHookRequest(args, raw, loadConfig()));
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
catch { /* stderr gone */ }
|
|
126
|
-
}
|
|
127
|
-
if (result.stdout) {
|
|
128
|
-
try {
|
|
129
|
-
process.stdout.write(result.stdout);
|
|
130
|
-
}
|
|
131
|
-
catch { /* stdout gone */ }
|
|
132
|
-
}
|
|
133
|
-
process.exit(result.exitCode);
|
|
130
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
131
|
+
const { emitVerdictForIde } = require('./askDialog');
|
|
132
|
+
await emitVerdictForIde(args, result, 'slim-local');
|
|
134
133
|
}
|
|
135
134
|
catch (err) {
|
|
136
135
|
emitFailOpen(args.event, err instanceof Error ? err.message : String(err));
|
package/dist/index.js
CHANGED
|
@@ -137,6 +137,13 @@ function printHelp() {
|
|
|
137
137
|
Exits 1 when remnants are found. Works via npx after the CLI
|
|
138
138
|
itself is uninstalled.
|
|
139
139
|
doctor First step. Checks outbound HTTPS access to FullCourtDefense.
|
|
140
|
+
protection-check
|
|
141
|
+
Test supported action boundaries and report verified coverage.
|
|
142
|
+
protected-run
|
|
143
|
+
Launch with this computer's signed folder/site limits.
|
|
144
|
+
--inspect shows settings; --command "your-agent" starts it.
|
|
145
|
+
Missing OS prerequisites or expired settings stop the launch.
|
|
146
|
+
hotfix-status Show loaded detector revision, update state and fallback status.
|
|
140
147
|
--perf measures this machine's real per-event overhead
|
|
141
148
|
(hook latency, spawn floor, cmd-guard cost, disk footprint).
|
|
142
149
|
--health prints an mdatp-health-style per-subsystem status
|
|
@@ -459,6 +466,7 @@ async function main() {
|
|
|
459
466
|
apiKey: flags['api-key'],
|
|
460
467
|
});
|
|
461
468
|
const buildGatewayArgs = () => ({
|
|
469
|
+
mcpServerName: flags['mcp-server-name'],
|
|
462
470
|
mcpCommand: flags['mcp-command'],
|
|
463
471
|
mcpArgs: flags['mcp-args'],
|
|
464
472
|
mcpUrl: flags['mcp-url'],
|
|
@@ -542,6 +550,26 @@ async function main() {
|
|
|
542
550
|
printHelp();
|
|
543
551
|
break;
|
|
544
552
|
}
|
|
553
|
+
case 'protected-run': {
|
|
554
|
+
const { protectedRunCommand } = await Promise.resolve().then(() => __importStar(require('./commands/protectedRun')));
|
|
555
|
+
await protectedRunCommand(config, flags.command, flags.inspect === 'true');
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
case 'protection-check': {
|
|
559
|
+
const { runBoundaryVerification } = await Promise.resolve().then(() => __importStar(require('./boundaryVerification')));
|
|
560
|
+
const report = await runBoundaryVerification();
|
|
561
|
+
if (flags.json === 'true')
|
|
562
|
+
console.log(JSON.stringify(report, null, 2));
|
|
563
|
+
else {
|
|
564
|
+
console.log('Action Protection — local checker verification');
|
|
565
|
+
console.log('Private sample policy; no submitted command or tool is executed.');
|
|
566
|
+
for (const check of report.checks)
|
|
567
|
+
console.log(`${check.boundary}: ${check.status} (${check.allowed} allowed sample, ${check.denied} blocked sample)`);
|
|
568
|
+
console.log('This checks the CLI, not IDE integration or OS containment. Use --json for the full report.');
|
|
569
|
+
}
|
|
570
|
+
process.exitCode = report.checks.some(check => check.status === 'failed') ? 1 : 0;
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
545
573
|
case 'doctor': {
|
|
546
574
|
const args = {
|
|
547
575
|
apiUrl: flags['api-url'],
|
|
@@ -553,6 +581,13 @@ async function main() {
|
|
|
553
581
|
await doctorCommand(args, config);
|
|
554
582
|
break;
|
|
555
583
|
}
|
|
584
|
+
case 'hotfix-status': {
|
|
585
|
+
const { localDetectorUpdates } = await Promise.resolve().then(() => __importStar(require('./localDetectionUpdates')));
|
|
586
|
+
const { cliVersion } = await Promise.resolve().then(() => __importStar(require('./cliVersion')));
|
|
587
|
+
console.log(JSON.stringify({ cliVersion: cliVersion(), detector: localDetectorUpdates.getDetectorStatus(),
|
|
588
|
+
note: 'Acceptance is not proof of correction. Compare the reported action and prohibited counterparts under the same policy.' }, null, 2));
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
556
591
|
case 'demo-actions': {
|
|
557
592
|
const { demoActionsCommand } = await Promise.resolve().then(() => __importStar(require('./commands/demoActions')));
|
|
558
593
|
await demoActionsCommand({ live: flags.live, json: flags.json, demoPolicies: flags['demo-policies'], timeout: flags.timeout }, config);
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import { DetectionUpdates } from './detectionUpdates';
|
|
2
|
-
|
|
2
|
+
import { DetectorUpdates } from './detectorUpdates';
|
|
3
|
+
export declare const localDetectionUpdates: DetectionUpdates<import("./detectionData").DetectionData>;
|
|
4
|
+
export declare const localDetectorUpdates: DetectorUpdates;
|
|
@@ -33,9 +33,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.localDetectionUpdates = void 0;
|
|
36
|
+
exports.localDetectorUpdates = exports.localDetectionUpdates = void 0;
|
|
37
37
|
const os = __importStar(require("os"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const detectionUpdates_1 = require("./detectionUpdates");
|
|
40
|
+
const detectorUpdates_1 = require("./detectorUpdates");
|
|
41
|
+
const machineIdentity_1 = require("./machineIdentity");
|
|
40
42
|
exports.localDetectionUpdates = new detectionUpdates_1.DetectionUpdates(path.join(os.homedir(), '.fullcourtdefense', 'detection-update.json'));
|
|
41
43
|
exports.localDetectionUpdates.loadLocal();
|
|
44
|
+
exports.localDetectorUpdates = new detectorUpdates_1.DetectorUpdates(path.join(os.homedir(), '.fullcourtdefense', 'detector-update-v1.json'), undefined, 'stable', (0, machineIdentity_1.getMachineIdentity)().machineId);
|
|
45
|
+
exports.localDetectorUpdates.loadLocal();
|
|
46
|
+
// Long-running gateways observe the daemon's accepted cache without a restart.
|
|
47
|
+
// This timer is background-only and never keeps a short-lived hook alive.
|
|
48
|
+
setInterval(() => { exports.localDetectionUpdates.loadLocal(); exports.localDetectorUpdates.loadLocal(); }, 30_000).unref();
|
|
@@ -17,6 +17,8 @@ export interface LocalSafetySnapshotInput {
|
|
|
17
17
|
machineName: string;
|
|
18
18
|
expectedPolicyHash?: string;
|
|
19
19
|
timeoutMs?: number;
|
|
20
|
+
/** Explicit background refresh must fetch and persist a verified replacement. */
|
|
21
|
+
requireServerValidation?: boolean;
|
|
20
22
|
/**
|
|
21
23
|
* Hot-path caller on a monitor/shadow machine: any cached snapshot wins
|
|
22
24
|
* immediately (even stale / hash-mismatched) — the resident daemon owns
|
|
@@ -37,13 +39,15 @@ export declare function hookSnapshotIdentity(): {
|
|
|
37
39
|
};
|
|
38
40
|
export declare function clearLocalSafetySnapshotCache(input: Pick<LocalSafetySnapshotInput, 'apiUrl' | 'shieldId' | 'developerName' | 'machineName'>): void;
|
|
39
41
|
/**
|
|
40
|
-
* Remove
|
|
41
|
-
*
|
|
42
|
-
*
|
|
42
|
+
* Remove Local Safety snapshot cache files, regardless of their identity key.
|
|
43
|
+
* Enrollment clears all; a verified refresh preserves the newly saved hash.
|
|
44
|
+
* Returns false when an old cache could not be removed, so remote refresh
|
|
45
|
+
* does not claim convergence while another surface can still use stale rules.
|
|
46
|
+
* Files are keyed by sha1(apiUrl|shieldId|developer|machine), so after a
|
|
43
47
|
* shield/identity change a targeted unlink can miss files written under an
|
|
44
48
|
* old key — leaving a stale (possibly enforcing) snapshot for whichever
|
|
45
49
|
* surface still resolves that old identity.
|
|
46
50
|
*/
|
|
47
|
-
export declare function clearAllLocalSafetySnapshotCaches():
|
|
51
|
+
export declare function clearAllLocalSafetySnapshotCaches(preservePolicyHash?: string): boolean;
|
|
48
52
|
export declare function loadLocalSafetySnapshot(input: LocalSafetySnapshotInput): Promise<LocalSafetySnapshot | undefined>;
|
|
49
|
-
export declare function snapshotToScanOptions(snapshot: LocalSafetySnapshot | undefined, extra?: Pick<LocalSafetyScanOptions, 'cwd' | 'inspectScripts'>): LocalSafetyScanOptions;
|
|
53
|
+
export declare function snapshotToScanOptions(snapshot: LocalSafetySnapshot | undefined, extra?: Pick<LocalSafetyScanOptions, 'cwd' | 'inspectScripts' | 'mcpServerName'>): LocalSafetyScanOptions;
|
|
@@ -44,6 +44,7 @@ const os = __importStar(require("os"));
|
|
|
44
44
|
const path = __importStar(require("path"));
|
|
45
45
|
const deterministicGuard_1 = require("./commands/deterministicGuard");
|
|
46
46
|
const honeypot_1 = require("./honeypot");
|
|
47
|
+
const distress_1 = require("./distress");
|
|
47
48
|
const CACHE_TTL_MS = 60_000;
|
|
48
49
|
const CACHE_DIR = path.join(os.homedir(), '.fullcourtdefense');
|
|
49
50
|
function cachePath(input) {
|
|
@@ -55,9 +56,12 @@ function cachePath(input) {
|
|
|
55
56
|
function readCache(file) {
|
|
56
57
|
try {
|
|
57
58
|
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
58
|
-
if (!parsed?.snapshot || typeof parsed.loadedAt !== 'number')
|
|
59
|
+
if (!parsed?.snapshot || typeof parsed.loadedAt !== 'number' || !Number.isFinite(parsed.loadedAt) || parsed.loadedAt < 0)
|
|
59
60
|
return undefined;
|
|
60
|
-
|
|
61
|
+
// Parseable JSON can still be a partial write or an invalid old cache.
|
|
62
|
+
// Apply the same policy validation used for a server replacement.
|
|
63
|
+
const snapshot = toSnapshot(parsed.snapshot);
|
|
64
|
+
return snapshot ? { loadedAt: parsed.loadedAt, snapshot } : undefined;
|
|
61
65
|
}
|
|
62
66
|
catch {
|
|
63
67
|
return undefined;
|
|
@@ -72,7 +76,10 @@ function writeCache(file, snapshot) {
|
|
|
72
76
|
fs.writeFileSync(tmp, JSON.stringify({ loadedAt: Date.now(), snapshot }, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
73
77
|
fs.renameSync(tmp, file);
|
|
74
78
|
}
|
|
75
|
-
catch {
|
|
79
|
+
catch (error) {
|
|
80
|
+
(0, distress_1.reportDistress)('shell_policy', distress_1.DISTRESS.POLICY_CACHE_WRITE_FAILED, 'Could not save the shell policy cache; refresh is incomplete');
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
76
83
|
}
|
|
77
84
|
/**
|
|
78
85
|
* Fallback when the derived cache key has no file: the freshest snapshot on
|
|
@@ -109,8 +116,14 @@ function toRuleAction(value) {
|
|
|
109
116
|
function toSnapshot(data) {
|
|
110
117
|
if (!data || typeof data !== 'object')
|
|
111
118
|
return undefined;
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
if (typeof data.policyHash !== 'string' || !data.policyHash.trim()
|
|
120
|
+
|| !Array.isArray(data.disabledBuiltInItemIds) || data.disabledBuiltInItemIds.some((id) => typeof id !== 'string')
|
|
121
|
+
|| !Array.isArray(data.customBlocks) || data.customBlocks.some((item) => !item || typeof item.pattern !== 'string' || !item.pattern
|
|
122
|
+
|| (item.action !== undefined && !toRuleAction(item.action)))
|
|
123
|
+
|| (data.itemActions !== undefined && (!data.itemActions || typeof data.itemActions !== 'object' || Array.isArray(data.itemActions)
|
|
124
|
+
|| Object.values(data.itemActions).some(value => !toRuleAction(value)))))
|
|
125
|
+
return undefined;
|
|
126
|
+
// Invalid actions were rejected above; keep explicit 'block' overrides too.
|
|
114
127
|
let itemActions;
|
|
115
128
|
if (data.itemActions && typeof data.itemActions === 'object' && !Array.isArray(data.itemActions)) {
|
|
116
129
|
for (const [itemId, raw] of Object.entries(data.itemActions)) {
|
|
@@ -179,33 +192,45 @@ function clearLocalSafetySnapshotCache(input) {
|
|
|
179
192
|
catch { /* cache may not exist */ }
|
|
180
193
|
}
|
|
181
194
|
/**
|
|
182
|
-
* Remove
|
|
183
|
-
*
|
|
184
|
-
*
|
|
195
|
+
* Remove Local Safety snapshot cache files, regardless of their identity key.
|
|
196
|
+
* Enrollment clears all; a verified refresh preserves the newly saved hash.
|
|
197
|
+
* Returns false when an old cache could not be removed, so remote refresh
|
|
198
|
+
* does not claim convergence while another surface can still use stale rules.
|
|
199
|
+
* Files are keyed by sha1(apiUrl|shieldId|developer|machine), so after a
|
|
185
200
|
* shield/identity change a targeted unlink can miss files written under an
|
|
186
201
|
* old key — leaving a stale (possibly enforcing) snapshot for whichever
|
|
187
202
|
* surface still resolves that old identity.
|
|
188
203
|
*/
|
|
189
|
-
function clearAllLocalSafetySnapshotCaches() {
|
|
204
|
+
function clearAllLocalSafetySnapshotCaches(preservePolicyHash) {
|
|
205
|
+
let cleared = true;
|
|
190
206
|
try {
|
|
191
207
|
for (const name of fs.readdirSync(CACHE_DIR)) {
|
|
192
208
|
if (/^local-safety-[0-9a-f]{40}\.json$/i.test(name)) {
|
|
209
|
+
if (preservePolicyHash && readCache(path.join(CACHE_DIR, name))?.snapshot.policyHash === preservePolicyHash)
|
|
210
|
+
continue;
|
|
193
211
|
try {
|
|
194
212
|
fs.unlinkSync(path.join(CACHE_DIR, name));
|
|
195
213
|
}
|
|
196
|
-
catch {
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (error.code !== 'ENOENT')
|
|
216
|
+
cleared = false;
|
|
217
|
+
}
|
|
197
218
|
}
|
|
198
219
|
}
|
|
199
220
|
}
|
|
200
|
-
catch {
|
|
221
|
+
catch (error) {
|
|
222
|
+
if (error.code !== 'ENOENT')
|
|
223
|
+
cleared = false;
|
|
224
|
+
}
|
|
225
|
+
return cleared;
|
|
201
226
|
}
|
|
202
227
|
async function loadLocalSafetySnapshot(input) {
|
|
203
228
|
const file = cachePath(input);
|
|
204
229
|
const cached = readCache(file) || readFreshestAnyCache();
|
|
205
230
|
const hashMatches = !input.expectedPolicyHash || cached?.snapshot.policyHash === input.expectedPolicyHash;
|
|
206
|
-
if (cached && hashMatches && Date.now() - cached.loadedAt < CACHE_TTL_MS)
|
|
231
|
+
if (!input.requireServerValidation && cached && hashMatches && Date.now() - cached.loadedAt < CACHE_TTL_MS)
|
|
207
232
|
return cached.snapshot;
|
|
208
|
-
if (cached && input.preferCached)
|
|
233
|
+
if (!input.requireServerValidation && cached && input.preferCached)
|
|
209
234
|
return cached.snapshot;
|
|
210
235
|
const headers = { 'Content-Type': 'application/json' };
|
|
211
236
|
if (input.shieldKey)
|
|
@@ -223,9 +248,9 @@ async function loadLocalSafetySnapshot(input) {
|
|
|
223
248
|
});
|
|
224
249
|
if (resp.ok) {
|
|
225
250
|
const body = await resp.json().catch(() => ({}));
|
|
226
|
-
if (body.success) {
|
|
251
|
+
if (body.success === true) {
|
|
227
252
|
const snapshot = toSnapshot(body.data);
|
|
228
|
-
if (snapshot) {
|
|
253
|
+
if (snapshot && (!input.requireServerValidation || !input.expectedPolicyHash || snapshot.policyHash === input.expectedPolicyHash)) {
|
|
229
254
|
writeCache(file, snapshot);
|
|
230
255
|
return snapshot;
|
|
231
256
|
}
|
|
@@ -233,6 +258,8 @@ async function loadLocalSafetySnapshot(input) {
|
|
|
233
258
|
}
|
|
234
259
|
}
|
|
235
260
|
catch { /* offline-safe fallback below */ }
|
|
261
|
+
if (input.requireServerValidation)
|
|
262
|
+
throw new Error('Shell policy refresh did not receive and save the expected snapshot; cached rules remain available');
|
|
236
263
|
return cached?.snapshot;
|
|
237
264
|
}
|
|
238
265
|
function snapshotToScanOptions(snapshot, extra = {}) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { VerifiableMachineAction } from './machineActionVerify';
|
|
2
|
+
import { ToolTrustSettings } from './mcpToolTrust';
|
|
3
|
+
/** Retain the signed requirement even when the general bundle ages out offline. */
|
|
4
|
+
export declare class ToolReviewLease {
|
|
5
|
+
private machineId;
|
|
6
|
+
private shieldId;
|
|
7
|
+
private file;
|
|
8
|
+
private remembered?;
|
|
9
|
+
private loaded;
|
|
10
|
+
constructor(machineId: string, shieldId: string, apiUrl: string, directory?: string);
|
|
11
|
+
settings(candidate?: VerifiableMachineAction | null): ToolTrustSettings | undefined;
|
|
12
|
+
}
|
|
13
|
+
export declare function verifiedToolTrust(envelope: VerifiableMachineAction, machineId: string, shieldId: string): ToolTrustSettings;
|
|
14
|
+
/** Preserve every declared field and exhaust pagination before fingerprinting. */
|
|
15
|
+
export declare function readCompleteToolCatalog(request: (params: Record<string, unknown>) => Promise<unknown>): Promise<Array<Record<string, unknown> & {
|
|
16
|
+
name: string;
|
|
17
|
+
}>>;
|