@rewind-ai/cli 0.6.2
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/.env.example +8 -0
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/dist/src/action/capture.d.ts +15 -0
- package/dist/src/action/capture.js +215 -0
- package/dist/src/action/capture.js.map +1 -0
- package/dist/src/action/mediate.d.ts +28 -0
- package/dist/src/action/mediate.js +109 -0
- package/dist/src/action/mediate.js.map +1 -0
- package/dist/src/action/mutate.d.ts +11 -0
- package/dist/src/action/mutate.js +189 -0
- package/dist/src/action/mutate.js.map +1 -0
- package/dist/src/action/objects.d.ts +5 -0
- package/dist/src/action/objects.js +50 -0
- package/dist/src/action/objects.js.map +1 -0
- package/dist/src/action/store.d.ts +24 -0
- package/dist/src/action/store.js +149 -0
- package/dist/src/action/store.js.map +1 -0
- package/dist/src/action/types.d.ts +84 -0
- package/dist/src/action/types.js +3 -0
- package/dist/src/action/types.js.map +1 -0
- package/dist/src/action/undo.d.ts +11 -0
- package/dist/src/action/undo.js +42 -0
- package/dist/src/action/undo.js.map +1 -0
- package/dist/src/adapters/filesystem.d.ts +35 -0
- package/dist/src/adapters/filesystem.js +499 -0
- package/dist/src/adapters/filesystem.js.map +1 -0
- package/dist/src/adapters/index.d.ts +7 -0
- package/dist/src/adapters/index.js +19 -0
- package/dist/src/adapters/index.js.map +1 -0
- package/dist/src/adapters/types.d.ts +54 -0
- package/dist/src/adapters/types.js +7 -0
- package/dist/src/adapters/types.js.map +1 -0
- package/dist/src/api/server.d.ts +25 -0
- package/dist/src/api/server.js +786 -0
- package/dist/src/api/server.js.map +1 -0
- package/dist/src/approval/store.d.ts +35 -0
- package/dist/src/approval/store.js +113 -0
- package/dist/src/approval/store.js.map +1 -0
- package/dist/src/checkpoint.d.ts +9 -0
- package/dist/src/checkpoint.js +140 -0
- package/dist/src/checkpoint.js.map +1 -0
- package/dist/src/cli/v5.d.ts +15 -0
- package/dist/src/cli/v5.js +110 -0
- package/dist/src/cli/v5.js.map +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +823 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/clients/types.d.ts +90 -0
- package/dist/src/clients/types.js +239 -0
- package/dist/src/clients/types.js.map +1 -0
- package/dist/src/commercial/auth/provider.d.ts +61 -0
- package/dist/src/commercial/auth/provider.js +227 -0
- package/dist/src/commercial/auth/provider.js.map +1 -0
- package/dist/src/commercial/entitlement/provider.d.ts +27 -0
- package/dist/src/commercial/entitlement/provider.js +192 -0
- package/dist/src/commercial/entitlement/provider.js.map +1 -0
- package/dist/src/commercial/entitlement/signing.d.ts +61 -0
- package/dist/src/commercial/entitlement/signing.js +124 -0
- package/dist/src/commercial/entitlement/signing.js.map +1 -0
- package/dist/src/commercial/env.d.ts +10 -0
- package/dist/src/commercial/env.js +40 -0
- package/dist/src/commercial/env.js.map +1 -0
- package/dist/src/commercial/product.d.ts +26 -0
- package/dist/src/commercial/product.js +39 -0
- package/dist/src/commercial/product.js.map +1 -0
- package/dist/src/commercial/store.d.ts +46 -0
- package/dist/src/commercial/store.js +104 -0
- package/dist/src/commercial/store.js.map +1 -0
- package/dist/src/commercial/stripe/checkout.d.ts +104 -0
- package/dist/src/commercial/stripe/checkout.js +469 -0
- package/dist/src/commercial/stripe/checkout.js.map +1 -0
- package/dist/src/core/compatibility.d.ts +15 -0
- package/dist/src/core/compatibility.js +226 -0
- package/dist/src/core/compatibility.js.map +1 -0
- package/dist/src/core/engine.d.ts +33 -0
- package/dist/src/core/engine.js +432 -0
- package/dist/src/core/engine.js.map +1 -0
- package/dist/src/core/lifecycle.d.ts +17 -0
- package/dist/src/core/lifecycle.js +56 -0
- package/dist/src/core/lifecycle.js.map +1 -0
- package/dist/src/core/types.d.ts +140 -0
- package/dist/src/core/types.js +37 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/destroy.d.ts +21 -0
- package/dist/src/destroy.js +219 -0
- package/dist/src/destroy.js.map +1 -0
- package/dist/src/diff.d.ts +13 -0
- package/dist/src/diff.js +251 -0
- package/dist/src/diff.js.map +1 -0
- package/dist/src/enforcement/detect.d.ts +13 -0
- package/dist/src/enforcement/detect.js +75 -0
- package/dist/src/enforcement/detect.js.map +1 -0
- package/dist/src/enforcement/hook-policy.d.ts +38 -0
- package/dist/src/enforcement/hook-policy.js +111 -0
- package/dist/src/enforcement/hook-policy.js.map +1 -0
- package/dist/src/enforcement/index.d.ts +8 -0
- package/dist/src/enforcement/index.js +8 -0
- package/dist/src/enforcement/index.js.map +1 -0
- package/dist/src/enforcement/perf.d.ts +6 -0
- package/dist/src/enforcement/perf.js +18 -0
- package/dist/src/enforcement/perf.js.map +1 -0
- package/dist/src/enforcement/policy.d.ts +12 -0
- package/dist/src/enforcement/policy.js +84 -0
- package/dist/src/enforcement/policy.js.map +1 -0
- package/dist/src/enforcement/readonly.d.ts +17 -0
- package/dist/src/enforcement/readonly.js +124 -0
- package/dist/src/enforcement/readonly.js.map +1 -0
- package/dist/src/enforcement/sandbox-clone.d.ts +6 -0
- package/dist/src/enforcement/sandbox-clone.js +11 -0
- package/dist/src/enforcement/sandbox-clone.js.map +1 -0
- package/dist/src/enforcement/setup.d.ts +33 -0
- package/dist/src/enforcement/setup.js +168 -0
- package/dist/src/enforcement/setup.js.map +1 -0
- package/dist/src/enforcement/trust.d.ts +10 -0
- package/dist/src/enforcement/trust.js +71 -0
- package/dist/src/enforcement/trust.js.map +1 -0
- package/dist/src/enforcement/types.d.ts +40 -0
- package/dist/src/enforcement/types.js +3 -0
- package/dist/src/enforcement/types.js.map +1 -0
- package/dist/src/entitlement/provider.d.ts +48 -0
- package/dist/src/entitlement/provider.js +95 -0
- package/dist/src/entitlement/provider.js.map +1 -0
- package/dist/src/hash.d.ts +9 -0
- package/dist/src/hash.js +23 -0
- package/dist/src/hash.js.map +1 -0
- package/dist/src/ledger/query.d.ts +19 -0
- package/dist/src/ledger/query.js +158 -0
- package/dist/src/ledger/query.js.map +1 -0
- package/dist/src/manifest.d.ts +32 -0
- package/dist/src/manifest.js +242 -0
- package/dist/src/manifest.js.map +1 -0
- package/dist/src/mcp/server.d.ts +7 -0
- package/dist/src/mcp/server.js +364 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/paths.d.ts +94 -0
- package/dist/src/paths.js +354 -0
- package/dist/src/paths.js.map +1 -0
- package/dist/src/permission/engine.d.ts +15 -0
- package/dist/src/permission/engine.js +147 -0
- package/dist/src/permission/engine.js.map +1 -0
- package/dist/src/permission/types.d.ts +42 -0
- package/dist/src/permission/types.js +3 -0
- package/dist/src/permission/types.js.map +1 -0
- package/dist/src/product/setup.d.ts +35 -0
- package/dist/src/product/setup.js +300 -0
- package/dist/src/product/setup.js.map +1 -0
- package/dist/src/protect/audit.d.ts +5 -0
- package/dist/src/protect/audit.js +26 -0
- package/dist/src/protect/audit.js.map +1 -0
- package/dist/src/protect/capture.d.ts +7 -0
- package/dist/src/protect/capture.js +250 -0
- package/dist/src/protect/capture.js.map +1 -0
- package/dist/src/protect/fingerprint.d.ts +4 -0
- package/dist/src/protect/fingerprint.js +49 -0
- package/dist/src/protect/fingerprint.js.map +1 -0
- package/dist/src/protect/hook-cli.d.ts +2 -0
- package/dist/src/protect/hook-cli.js +107 -0
- package/dist/src/protect/hook-cli.js.map +1 -0
- package/dist/src/protect/index.d.ts +12 -0
- package/dist/src/protect/index.js +12 -0
- package/dist/src/protect/index.js.map +1 -0
- package/dist/src/protect/install.d.ts +34 -0
- package/dist/src/protect/install.js +293 -0
- package/dist/src/protect/install.js.map +1 -0
- package/dist/src/protect/intercept.d.ts +11 -0
- package/dist/src/protect/intercept.js +390 -0
- package/dist/src/protect/intercept.js.map +1 -0
- package/dist/src/protect/protect.d.ts +17 -0
- package/dist/src/protect/protect.js +194 -0
- package/dist/src/protect/protect.js.map +1 -0
- package/dist/src/protect/root-paths.d.ts +13 -0
- package/dist/src/protect/root-paths.js +140 -0
- package/dist/src/protect/root-paths.js.map +1 -0
- package/dist/src/protect/shell-policy.d.ts +17 -0
- package/dist/src/protect/shell-policy.js +269 -0
- package/dist/src/protect/shell-policy.js.map +1 -0
- package/dist/src/protect/status.d.ts +11 -0
- package/dist/src/protect/status.js +165 -0
- package/dist/src/protect/status.js.map +1 -0
- package/dist/src/protect/store.d.ts +20 -0
- package/dist/src/protect/store.js +170 -0
- package/dist/src/protect/store.js.map +1 -0
- package/dist/src/protect/types.d.ts +116 -0
- package/dist/src/protect/types.js +3 -0
- package/dist/src/protect/types.js.map +1 -0
- package/dist/src/protect/undo-session.d.ts +14 -0
- package/dist/src/protect/undo-session.js +182 -0
- package/dist/src/protect/undo-session.js.map +1 -0
- package/dist/src/restore.d.ts +39 -0
- package/dist/src/restore.js +141 -0
- package/dist/src/restore.js.map +1 -0
- package/dist/src/sandbox.d.ts +23 -0
- package/dist/src/sandbox.js +198 -0
- package/dist/src/sandbox.js.map +1 -0
- package/dist/src/session/ambient.d.ts +10 -0
- package/dist/src/session/ambient.js +53 -0
- package/dist/src/session/ambient.js.map +1 -0
- package/dist/src/session/attribution.d.ts +27 -0
- package/dist/src/session/attribution.js +201 -0
- package/dist/src/session/attribution.js.map +1 -0
- package/dist/src/session/index.d.ts +25 -0
- package/dist/src/session/index.js +472 -0
- package/dist/src/session/index.js.map +1 -0
- package/dist/src/session/process.d.ts +34 -0
- package/dist/src/session/process.js +234 -0
- package/dist/src/session/process.js.map +1 -0
- package/dist/src/session/store.d.ts +14 -0
- package/dist/src/session/store.js +125 -0
- package/dist/src/session/store.js.map +1 -0
- package/dist/src/session/types.d.ts +111 -0
- package/dist/src/session/types.js +3 -0
- package/dist/src/session/types.js.map +1 -0
- package/dist/src/session/watcher.d.ts +32 -0
- package/dist/src/session/watcher.js +186 -0
- package/dist/src/session/watcher.js.map +1 -0
- package/dist/src/timing.d.ts +7 -0
- package/dist/src/timing.js +19 -0
- package/dist/src/timing.js.map +1 -0
- package/dist/src/types.d.ts +101 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/verify.d.ts +8 -0
- package/dist/src/verify.js +126 -0
- package/dist/src/verify.js.map +1 -0
- package/docs/ADAPTERS.md +37 -0
- package/docs/APPROVALS.md +32 -0
- package/docs/ARCHITECTURE.md +57 -0
- package/docs/ATTRIBUTION.md +179 -0
- package/docs/COMMERCIAL.md +51 -0
- package/docs/CURSOR-ENFORCEMENT.md +73 -0
- package/docs/ENFORCEMENT.md +134 -0
- package/docs/FUTURE.md +91 -0
- package/docs/HUMAN-SETUP.md +98 -0
- package/docs/IDENTITY-AUTHORITY.md +35 -0
- package/docs/MCP.md +107 -0
- package/docs/PACKAGING.md +77 -0
- package/docs/PERMISSIONS.md +53 -0
- package/docs/PRODUCT-V5.md +63 -0
- package/docs/PRODUCT-V6.md +69 -0
- package/docs/PROTECTED-SESSIONS.md +155 -0
- package/docs/THREAT-MODEL.md +84 -0
- package/docs/TRANSACTIONS.md +57 -0
- package/package.json +85 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REWIND V5 — general transaction domain model.
|
|
3
|
+
*
|
|
4
|
+
* Filesystem ActionRecord (v2) remains the on-disk ledger shape for mediated
|
|
5
|
+
* FS actions. Transaction (v5) is the in-memory / query / adapter-facing model.
|
|
6
|
+
* See compatibility.ts for mapping. Do not treat planned adapters as shipped.
|
|
7
|
+
*/
|
|
8
|
+
export const TRANSACTION_MODEL_VERSION = 5;
|
|
9
|
+
export function unknownAuthority() {
|
|
10
|
+
return {
|
|
11
|
+
status: "UNKNOWN",
|
|
12
|
+
principal: null,
|
|
13
|
+
scope: null,
|
|
14
|
+
constraints: [],
|
|
15
|
+
expiresAt: null,
|
|
16
|
+
limits: null,
|
|
17
|
+
delegationChain: null,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function actorFromMcpClient(client) {
|
|
21
|
+
const unknown = client.name === "UNKNOWN_MCP_CLIENT" || client.version === "unknown";
|
|
22
|
+
return {
|
|
23
|
+
type: unknown ? "unknown" : "mcp_client",
|
|
24
|
+
client: client.name,
|
|
25
|
+
clientVersion: client.version,
|
|
26
|
+
identity: null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function emptyUndoInfo() {
|
|
30
|
+
return {
|
|
31
|
+
status: "NONE",
|
|
32
|
+
attemptedAt: null,
|
|
33
|
+
result: null,
|
|
34
|
+
conflict: null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAU,CAAC;AA6KpD,MAAM,UAAU,gBAAgB;IAC9B,OAAO;QACL,MAAM,EAAE,SAAS;QACjB,SAAS,EAAE,IAAI;QACf,KAAK,EAAE,IAAI;QACX,WAAW,EAAE,EAAE;QACf,SAAS,EAAE,IAAI;QACf,MAAM,EAAE,IAAI;QACZ,eAAe,EAAE,IAAI;KACtB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAGlC;IACC,MAAM,OAAO,GACX,MAAM,CAAC,IAAI,KAAK,oBAAoB,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC;IACvE,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY;QACxC,MAAM,EAAE,MAAM,CAAC,IAAI;QACnB,aAAa,EAAE,MAAM,CAAC,OAAO;QAC7B,QAAQ,EAAE,IAAI;KACf,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO;QACL,MAAM,EAAE,MAAM;QACd,WAAW,EAAE,IAAI;QACjB,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,IAAI;KACf,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface DestroyResult {
|
|
2
|
+
modified: number;
|
|
3
|
+
deleted: number;
|
|
4
|
+
created: number;
|
|
5
|
+
renamed: number;
|
|
6
|
+
moved: number;
|
|
7
|
+
permissionChanges: number;
|
|
8
|
+
totalActions: number;
|
|
9
|
+
timingsMs: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Safeguard 1: explicit approved path equality via printChaosSafetyBanner.
|
|
13
|
+
* Safeguard 2: basename must be sandbox-test-project.
|
|
14
|
+
* Safeguard 3: realpath already checked in assertApprovedSandbox.
|
|
15
|
+
* Safeguard 4: every mutation re-checks isPathInside.
|
|
16
|
+
* Safeguard 5: refuse if REWIND_ALLOW_CHAOS === "0".
|
|
17
|
+
*/
|
|
18
|
+
export declare function destroySandbox(projectRoot?: string): Promise<DestroyResult>;
|
|
19
|
+
/** Used by failure tests — attempt destroy on an arbitrary path (must refuse). */
|
|
20
|
+
export declare function destroyArbitraryPath(target: string): Promise<never>;
|
|
21
|
+
export declare function assertSandboxHasContent(projectRoot?: string): Promise<void>;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readFile, writeFile, rm, mkdir, rename, chmod, readdir, lstat, } from "node:fs/promises";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { getProjectRoot, getSandboxPath, printChaosSafetyBanner, isPathInside, PathSafetyError, assertSafeRelativePath, } from "./paths.js";
|
|
5
|
+
import { walkTree, toRelativePosix } from "./manifest.js";
|
|
6
|
+
import { Timer } from "./timing.js";
|
|
7
|
+
/**
|
|
8
|
+
* Deterministic chaos destroyer.
|
|
9
|
+
* Multiple independent safeguards refuse to run outside the approved sandbox.
|
|
10
|
+
*/
|
|
11
|
+
const CHAOS_SEED = "rewind-v0-chaos-v1";
|
|
12
|
+
function hashPick(seed, items, count) {
|
|
13
|
+
if (items.length === 0 || count <= 0)
|
|
14
|
+
return [];
|
|
15
|
+
const scored = items.map((item) => ({
|
|
16
|
+
item,
|
|
17
|
+
score: createHash("sha256").update(`${seed}:${item}`).digest("hex"),
|
|
18
|
+
}));
|
|
19
|
+
scored.sort((a, b) => a.score.localeCompare(b.score));
|
|
20
|
+
return scored.slice(0, Math.min(count, scored.length)).map((s) => s.item);
|
|
21
|
+
}
|
|
22
|
+
async function listRegularFiles(sandbox) {
|
|
23
|
+
const all = await walkTree(sandbox);
|
|
24
|
+
const files = [];
|
|
25
|
+
for (const abs of all) {
|
|
26
|
+
const st = await lstat(abs);
|
|
27
|
+
if (st.isFile())
|
|
28
|
+
files.push(abs);
|
|
29
|
+
}
|
|
30
|
+
return files.sort((a, b) => a.localeCompare(b));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Safeguard 1: explicit approved path equality via printChaosSafetyBanner.
|
|
34
|
+
* Safeguard 2: basename must be sandbox-test-project.
|
|
35
|
+
* Safeguard 3: realpath already checked in assertApprovedSandbox.
|
|
36
|
+
* Safeguard 4: every mutation re-checks isPathInside.
|
|
37
|
+
* Safeguard 5: refuse if REWIND_ALLOW_CHAOS === "0".
|
|
38
|
+
*/
|
|
39
|
+
export async function destroySandbox(projectRoot = getProjectRoot()) {
|
|
40
|
+
if (process.env.REWIND_ALLOW_CHAOS === "0") {
|
|
41
|
+
throw new PathSafetyError("Chaos disabled via REWIND_ALLOW_CHAOS=0");
|
|
42
|
+
}
|
|
43
|
+
const timer = new Timer();
|
|
44
|
+
const sandbox = getSandboxPath(projectRoot);
|
|
45
|
+
// Safeguard: basename
|
|
46
|
+
if (path.basename(sandbox) !== "sandbox-test-project") {
|
|
47
|
+
throw new PathSafetyError("Chaos refused: unexpected sandbox basename");
|
|
48
|
+
}
|
|
49
|
+
// Safeguard: must be direct child of project root
|
|
50
|
+
if (path.dirname(path.resolve(sandbox)) !== path.resolve(projectRoot)) {
|
|
51
|
+
throw new PathSafetyError("Chaos refused: sandbox not under project root");
|
|
52
|
+
}
|
|
53
|
+
await printChaosSafetyBanner(sandbox, projectRoot);
|
|
54
|
+
const files = await listRegularFiles(sandbox);
|
|
55
|
+
if (files.length < 50) {
|
|
56
|
+
throw new Error("Sandbox too small for chaos test — recreate it first");
|
|
57
|
+
}
|
|
58
|
+
let modified = 0;
|
|
59
|
+
let deleted = 0;
|
|
60
|
+
let created = 0;
|
|
61
|
+
let renamed = 0;
|
|
62
|
+
let moved = 0;
|
|
63
|
+
let permissionChanges = 0;
|
|
64
|
+
const mutate = async (abs, fn) => {
|
|
65
|
+
if (!isPathInside(sandbox, abs)) {
|
|
66
|
+
throw new PathSafetyError(`Chaos mutation escapes sandbox: ${abs}`);
|
|
67
|
+
}
|
|
68
|
+
await fn();
|
|
69
|
+
};
|
|
70
|
+
// Modify ~40 files
|
|
71
|
+
const toModify = hashPick(`${CHAOS_SEED}:modify`, files, 40);
|
|
72
|
+
for (const abs of toModify) {
|
|
73
|
+
await mutate(abs, async () => {
|
|
74
|
+
const prev = await readFile(abs);
|
|
75
|
+
const stamp = createHash("sha256")
|
|
76
|
+
.update(`${CHAOS_SEED}:mod:${toRelativePosix(sandbox, abs)}`)
|
|
77
|
+
.digest("hex")
|
|
78
|
+
.slice(0, 16);
|
|
79
|
+
if (abs.endsWith(".bin")) {
|
|
80
|
+
const next = Buffer.concat([prev, Buffer.from(stamp, "hex")]);
|
|
81
|
+
await writeFile(abs, next);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
await writeFile(abs, Buffer.concat([
|
|
85
|
+
prev,
|
|
86
|
+
Buffer.from(`\n/* CHAOS ${stamp} */\n`, "utf8"),
|
|
87
|
+
]));
|
|
88
|
+
}
|
|
89
|
+
modified++;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// Refresh file list after mods (same paths)
|
|
93
|
+
let remaining = (await listRegularFiles(sandbox)).filter((f) => !toModify.includes(f));
|
|
94
|
+
// Delete ~20 files
|
|
95
|
+
const toDelete = hashPick(`${CHAOS_SEED}:delete`, remaining, 20);
|
|
96
|
+
for (const abs of toDelete) {
|
|
97
|
+
await mutate(abs, async () => {
|
|
98
|
+
await rm(abs, { force: true });
|
|
99
|
+
deleted++;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
remaining = remaining.filter((f) => !toDelete.includes(f));
|
|
103
|
+
// Rename ~10 files (same directory, new basename)
|
|
104
|
+
const toRename = hashPick(`${CHAOS_SEED}:rename`, remaining, 10);
|
|
105
|
+
for (const abs of toRename) {
|
|
106
|
+
await mutate(abs, async () => {
|
|
107
|
+
const dir = path.dirname(abs);
|
|
108
|
+
const base = path.basename(abs);
|
|
109
|
+
const next = path.join(dir, `renamed-${base}`);
|
|
110
|
+
if (!isPathInside(sandbox, next)) {
|
|
111
|
+
throw new PathSafetyError("Rename target escapes");
|
|
112
|
+
}
|
|
113
|
+
await rename(abs, next);
|
|
114
|
+
renamed++;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
remaining = remaining.filter((f) => !toRename.includes(f));
|
|
118
|
+
// Move ~10 files into chaos-moved/
|
|
119
|
+
const moveDir = path.join(sandbox, "chaos-moved");
|
|
120
|
+
await mutate(moveDir, async () => {
|
|
121
|
+
await mkdir(moveDir, { recursive: true });
|
|
122
|
+
});
|
|
123
|
+
const toMove = hashPick(`${CHAOS_SEED}:move`, remaining, 10);
|
|
124
|
+
for (const abs of toMove) {
|
|
125
|
+
await mutate(abs, async () => {
|
|
126
|
+
const dest = path.join(moveDir, path.basename(abs));
|
|
127
|
+
if (!isPathInside(sandbox, dest)) {
|
|
128
|
+
throw new PathSafetyError("Move target escapes");
|
|
129
|
+
}
|
|
130
|
+
await rename(abs, dest);
|
|
131
|
+
moved++;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// Create ~25 new files + nested dirs
|
|
135
|
+
for (let i = 0; i < 25; i++) {
|
|
136
|
+
const rel = assertSafeRelativePath(`chaos-created/nested-${i % 5}/new-file-${String(i).padStart(2, "0")}.txt`);
|
|
137
|
+
const abs = path.join(sandbox, rel);
|
|
138
|
+
await mutate(abs, async () => {
|
|
139
|
+
await mkdir(path.dirname(abs), { recursive: true });
|
|
140
|
+
const body = createHash("sha256")
|
|
141
|
+
.update(`${CHAOS_SEED}:create:${i}`)
|
|
142
|
+
.digest("hex");
|
|
143
|
+
await writeFile(abs, `chaos created ${i}\n${body}\n`, "utf8");
|
|
144
|
+
created++;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// Replace one binary wholesale
|
|
148
|
+
const bins = (await listRegularFiles(sandbox)).filter((f) => f.endsWith(".bin"));
|
|
149
|
+
if (bins[0]) {
|
|
150
|
+
await mutate(bins[0], async () => {
|
|
151
|
+
await writeFile(bins[0], createHash("sha256").update(`${CHAOS_SEED}:binreplace`).digest());
|
|
152
|
+
// count as modified if not already
|
|
153
|
+
if (!toModify.includes(bins[0]))
|
|
154
|
+
modified++;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
// Remove an empty-ish directory tree leaf if present
|
|
158
|
+
const emptyDir = path.join(sandbox, "empty-dir");
|
|
159
|
+
if (isPathInside(sandbox, emptyDir)) {
|
|
160
|
+
try {
|
|
161
|
+
await rm(emptyDir, { recursive: true, force: true });
|
|
162
|
+
deleted++; // directory deletion
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
/* ignore */
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Permission changes on a few remaining files
|
|
169
|
+
const forChmod = hashPick(`${CHAOS_SEED}:chmod`, await listRegularFiles(sandbox), 5);
|
|
170
|
+
for (const abs of forChmod) {
|
|
171
|
+
await mutate(abs, async () => {
|
|
172
|
+
await chmod(abs, 0o600);
|
|
173
|
+
permissionChanges++;
|
|
174
|
+
// Mode change alone is a modification if content unchanged —
|
|
175
|
+
// diff will pick it up; don't double-count content mods.
|
|
176
|
+
if (!toModify.includes(abs) && abs !== bins[0]) {
|
|
177
|
+
modified++;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
// Create deeply nested chaos path
|
|
182
|
+
const deep = path.join(sandbox, "chaos-created/deep/a/b/c/d/e/wreckage.txt");
|
|
183
|
+
await mutate(deep, async () => {
|
|
184
|
+
await mkdir(path.dirname(deep), { recursive: true });
|
|
185
|
+
await writeFile(deep, "deep wreckage\n", "utf8");
|
|
186
|
+
created++;
|
|
187
|
+
});
|
|
188
|
+
const totalActions = modified + deleted + created + renamed + moved + permissionChanges;
|
|
189
|
+
return {
|
|
190
|
+
modified,
|
|
191
|
+
deleted,
|
|
192
|
+
created,
|
|
193
|
+
renamed,
|
|
194
|
+
moved,
|
|
195
|
+
permissionChanges,
|
|
196
|
+
totalActions,
|
|
197
|
+
timingsMs: timer.elapsedMs(),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/** Used by failure tests — attempt destroy on an arbitrary path (must refuse). */
|
|
201
|
+
export async function destroyArbitraryPath(target) {
|
|
202
|
+
// Re-use the same validation gate: only getSandboxPath is approved.
|
|
203
|
+
const projectRoot = getProjectRoot();
|
|
204
|
+
const approved = getSandboxPath(projectRoot);
|
|
205
|
+
if (path.resolve(target) !== path.resolve(approved)) {
|
|
206
|
+
throw new PathSafetyError(`REFUSED: chaos will not run on ${path.resolve(target)}`);
|
|
207
|
+
}
|
|
208
|
+
// If somehow equal, still go through full banner.
|
|
209
|
+
await destroySandbox(projectRoot);
|
|
210
|
+
throw new Error("unreachable");
|
|
211
|
+
}
|
|
212
|
+
export async function assertSandboxHasContent(projectRoot = getProjectRoot()) {
|
|
213
|
+
const sandbox = getSandboxPath(projectRoot);
|
|
214
|
+
const entries = await readdir(sandbox).catch(() => []);
|
|
215
|
+
if (entries.length === 0) {
|
|
216
|
+
throw new Error("Sandbox is empty — run sandbox:create first");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=destroy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"destroy.js","sourceRoot":"","sources":["../../src/destroy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Change, Manifest, Transaction } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Diff checkpoint manifest vs current manifest.
|
|
4
|
+
* Rename/move inference: unmatched DELETED + CREATED with identical content hash.
|
|
5
|
+
* - Same basename, different dirname → MOVED
|
|
6
|
+
* - Different basename → RENAMED
|
|
7
|
+
* Uncertain pairs remain DELETE + CREATE (never fake certainty beyond hash match).
|
|
8
|
+
*/
|
|
9
|
+
export declare function diffManifests(before: Manifest, after: Manifest): Change[];
|
|
10
|
+
export declare function createTransaction(checkpointId: string, targetPath: string, changes: Change[], diffMs: number): Transaction;
|
|
11
|
+
export declare function saveTransaction(tx: Transaction, projectRoot?: string): Promise<string>;
|
|
12
|
+
export declare function recordDiffTransaction(before: Manifest, after: Manifest, checkpointId: string, targetPath: string, projectRoot?: string): Promise<Transaction>;
|
|
13
|
+
export declare function printTransactionSummary(tx: Transaction): void;
|
package/dist/src/diff.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { Timer } from "./timing.js";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { getTransactionsDir } from "./paths.js";
|
|
6
|
+
function emptySummary() {
|
|
7
|
+
return {
|
|
8
|
+
total: 0,
|
|
9
|
+
modified: 0,
|
|
10
|
+
created: 0,
|
|
11
|
+
deleted: 0,
|
|
12
|
+
renamed: 0,
|
|
13
|
+
moved: 0,
|
|
14
|
+
reversible: 0,
|
|
15
|
+
compensable: 0,
|
|
16
|
+
irreversible: 0,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function summarize(changes) {
|
|
20
|
+
const s = emptySummary();
|
|
21
|
+
s.total = changes.length;
|
|
22
|
+
for (const c of changes) {
|
|
23
|
+
switch (c.type) {
|
|
24
|
+
case "MODIFIED":
|
|
25
|
+
s.modified++;
|
|
26
|
+
break;
|
|
27
|
+
case "CREATED":
|
|
28
|
+
s.created++;
|
|
29
|
+
break;
|
|
30
|
+
case "DELETED":
|
|
31
|
+
s.deleted++;
|
|
32
|
+
break;
|
|
33
|
+
case "RENAMED":
|
|
34
|
+
s.renamed++;
|
|
35
|
+
break;
|
|
36
|
+
case "MOVED":
|
|
37
|
+
s.moved++;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
switch (c.reversibility) {
|
|
41
|
+
case "REVERSIBLE":
|
|
42
|
+
s.reversible++;
|
|
43
|
+
break;
|
|
44
|
+
case "COMPENSABLE":
|
|
45
|
+
s.compensable++;
|
|
46
|
+
break;
|
|
47
|
+
case "IRREVERSIBLE":
|
|
48
|
+
s.irreversible++;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return s;
|
|
53
|
+
}
|
|
54
|
+
function sameContent(a, b) {
|
|
55
|
+
return (a.kind === b.kind &&
|
|
56
|
+
a.hash !== null &&
|
|
57
|
+
b.hash !== null &&
|
|
58
|
+
a.hash === b.hash &&
|
|
59
|
+
a.kind !== "directory");
|
|
60
|
+
}
|
|
61
|
+
function dirnamePosix(p) {
|
|
62
|
+
const i = p.lastIndexOf("/");
|
|
63
|
+
return i === -1 ? "" : p.slice(0, i);
|
|
64
|
+
}
|
|
65
|
+
function basenamePosix(p) {
|
|
66
|
+
const i = p.lastIndexOf("/");
|
|
67
|
+
return i === -1 ? p : p.slice(i + 1);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Diff checkpoint manifest vs current manifest.
|
|
71
|
+
* Rename/move inference: unmatched DELETED + CREATED with identical content hash.
|
|
72
|
+
* - Same basename, different dirname → MOVED
|
|
73
|
+
* - Different basename → RENAMED
|
|
74
|
+
* Uncertain pairs remain DELETE + CREATE (never fake certainty beyond hash match).
|
|
75
|
+
*/
|
|
76
|
+
export function diffManifests(before, after) {
|
|
77
|
+
const beforeMap = new Map(before.entries.map((e) => [e.path, e]));
|
|
78
|
+
const afterMap = new Map(after.entries.map((e) => [e.path, e]));
|
|
79
|
+
const changes = [];
|
|
80
|
+
const deleted = [];
|
|
81
|
+
const created = [];
|
|
82
|
+
for (const [p, b] of beforeMap) {
|
|
83
|
+
const a = afterMap.get(p);
|
|
84
|
+
if (!a) {
|
|
85
|
+
deleted.push(b);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// Directory presence alone isn't a content change; mode-only changes count as MODIFIED.
|
|
89
|
+
if (b.kind === "directory" && a.kind === "directory") {
|
|
90
|
+
if (b.mode !== a.mode) {
|
|
91
|
+
changes.push({
|
|
92
|
+
type: "MODIFIED",
|
|
93
|
+
pathBefore: p,
|
|
94
|
+
pathAfter: p,
|
|
95
|
+
hashBefore: null,
|
|
96
|
+
hashAfter: null,
|
|
97
|
+
sizeBefore: 0,
|
|
98
|
+
sizeAfter: 0,
|
|
99
|
+
reversibility: "REVERSIBLE",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (b.kind !== a.kind ||
|
|
105
|
+
b.hash !== a.hash ||
|
|
106
|
+
b.size !== a.size ||
|
|
107
|
+
b.mode !== a.mode ||
|
|
108
|
+
b.linkTarget !== a.linkTarget) {
|
|
109
|
+
changes.push({
|
|
110
|
+
type: "MODIFIED",
|
|
111
|
+
pathBefore: p,
|
|
112
|
+
pathAfter: p,
|
|
113
|
+
hashBefore: b.hash,
|
|
114
|
+
hashAfter: a.hash,
|
|
115
|
+
sizeBefore: b.size,
|
|
116
|
+
sizeAfter: a.size,
|
|
117
|
+
reversibility: "REVERSIBLE",
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const [p, a] of afterMap) {
|
|
122
|
+
if (!beforeMap.has(p)) {
|
|
123
|
+
created.push(a);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Infer renames/moves by content hash (files + symlinks only).
|
|
127
|
+
const unmatchedDeleted = [...deleted];
|
|
128
|
+
const unmatchedCreated = [...created];
|
|
129
|
+
const usedCreated = new Set();
|
|
130
|
+
const usedDeleted = new Set();
|
|
131
|
+
for (let di = 0; di < unmatchedDeleted.length; di++) {
|
|
132
|
+
const d = unmatchedDeleted[di];
|
|
133
|
+
if (d.kind === "directory" || !d.hash)
|
|
134
|
+
continue;
|
|
135
|
+
let bestCi = -1;
|
|
136
|
+
for (let ci = 0; ci < unmatchedCreated.length; ci++) {
|
|
137
|
+
if (usedCreated.has(ci))
|
|
138
|
+
continue;
|
|
139
|
+
const c = unmatchedCreated[ci];
|
|
140
|
+
if (c.kind === "directory" || !c.hash)
|
|
141
|
+
continue;
|
|
142
|
+
if (sameContent(d, c)) {
|
|
143
|
+
bestCi = ci;
|
|
144
|
+
break; // first hash match is enough; honest inference
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (bestCi < 0)
|
|
148
|
+
continue;
|
|
149
|
+
usedDeleted.add(di);
|
|
150
|
+
usedCreated.add(bestCi);
|
|
151
|
+
const c = unmatchedCreated[bestCi];
|
|
152
|
+
const sameName = basenamePosix(d.path) === basenamePosix(c.path);
|
|
153
|
+
const sameDir = dirnamePosix(d.path) === dirnamePosix(c.path);
|
|
154
|
+
const type = sameName && !sameDir ? "MOVED" : !sameName ? "RENAMED" : "MOVED";
|
|
155
|
+
changes.push({
|
|
156
|
+
type,
|
|
157
|
+
pathBefore: d.path,
|
|
158
|
+
pathAfter: c.path,
|
|
159
|
+
hashBefore: d.hash,
|
|
160
|
+
hashAfter: c.hash,
|
|
161
|
+
sizeBefore: d.size,
|
|
162
|
+
sizeAfter: c.size,
|
|
163
|
+
reversibility: "REVERSIBLE",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
for (let di = 0; di < unmatchedDeleted.length; di++) {
|
|
167
|
+
if (usedDeleted.has(di))
|
|
168
|
+
continue;
|
|
169
|
+
const d = unmatchedDeleted[di];
|
|
170
|
+
changes.push({
|
|
171
|
+
type: "DELETED",
|
|
172
|
+
pathBefore: d.path,
|
|
173
|
+
pathAfter: null,
|
|
174
|
+
hashBefore: d.hash,
|
|
175
|
+
hashAfter: null,
|
|
176
|
+
sizeBefore: d.size,
|
|
177
|
+
sizeAfter: null,
|
|
178
|
+
reversibility: "REVERSIBLE",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
for (let ci = 0; ci < unmatchedCreated.length; ci++) {
|
|
182
|
+
if (usedCreated.has(ci))
|
|
183
|
+
continue;
|
|
184
|
+
const c = unmatchedCreated[ci];
|
|
185
|
+
changes.push({
|
|
186
|
+
type: "CREATED",
|
|
187
|
+
pathBefore: null,
|
|
188
|
+
pathAfter: c.path,
|
|
189
|
+
hashBefore: null,
|
|
190
|
+
hashAfter: c.hash,
|
|
191
|
+
sizeBefore: null,
|
|
192
|
+
sizeAfter: c.size,
|
|
193
|
+
reversibility: "REVERSIBLE",
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
// Stable order for readability
|
|
197
|
+
changes.sort((a, b) => {
|
|
198
|
+
const pa = a.pathAfter ?? a.pathBefore ?? "";
|
|
199
|
+
const pb = b.pathAfter ?? b.pathBefore ?? "";
|
|
200
|
+
return pa.localeCompare(pb) || a.type.localeCompare(b.type);
|
|
201
|
+
});
|
|
202
|
+
return changes;
|
|
203
|
+
}
|
|
204
|
+
export function createTransaction(checkpointId, targetPath, changes, diffMs) {
|
|
205
|
+
return {
|
|
206
|
+
id: `tx-${new Date().toISOString().replace(/[:.]/g, "-")}-${randomBytes(3).toString("hex")}`,
|
|
207
|
+
startedAt: new Date().toISOString(),
|
|
208
|
+
completedAt: new Date().toISOString(),
|
|
209
|
+
checkpointId,
|
|
210
|
+
targetPath,
|
|
211
|
+
changes,
|
|
212
|
+
status: "recorded",
|
|
213
|
+
summary: summarize(changes),
|
|
214
|
+
timingsMs: { diff: diffMs },
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
export async function saveTransaction(tx, projectRoot) {
|
|
218
|
+
const dir = getTransactionsDir(projectRoot);
|
|
219
|
+
await mkdir(dir, { recursive: true });
|
|
220
|
+
const filePath = path.join(dir, `${tx.id}.json`);
|
|
221
|
+
const tmp = `${filePath}.tmp`;
|
|
222
|
+
await writeFile(tmp, JSON.stringify(tx, null, 2) + "\n", "utf8");
|
|
223
|
+
const { rename } = await import("node:fs/promises");
|
|
224
|
+
await rename(tmp, filePath);
|
|
225
|
+
await writeFile(path.join(dir, "latest.json"), JSON.stringify({ id: tx.id }, null, 2) + "\n", "utf8");
|
|
226
|
+
return filePath;
|
|
227
|
+
}
|
|
228
|
+
export async function recordDiffTransaction(before, after, checkpointId, targetPath, projectRoot) {
|
|
229
|
+
const timer = new Timer();
|
|
230
|
+
const changes = diffManifests(before, after);
|
|
231
|
+
const tx = createTransaction(checkpointId, targetPath, changes, timer.elapsedMs());
|
|
232
|
+
await saveTransaction(tx, projectRoot);
|
|
233
|
+
return tx;
|
|
234
|
+
}
|
|
235
|
+
export function printTransactionSummary(tx) {
|
|
236
|
+
const s = tx.summary;
|
|
237
|
+
console.log("REWIND");
|
|
238
|
+
console.log("");
|
|
239
|
+
console.log("Transaction detected");
|
|
240
|
+
console.log("");
|
|
241
|
+
console.log(`${s.total} filesystem changes`);
|
|
242
|
+
console.log("");
|
|
243
|
+
console.log(`Modified ${s.modified}`);
|
|
244
|
+
console.log(`Created ${s.created}`);
|
|
245
|
+
console.log(`Deleted ${s.deleted}`);
|
|
246
|
+
console.log(`Renamed/moved ${s.renamed + s.moved}`);
|
|
247
|
+
console.log("");
|
|
248
|
+
console.log(`Reversible ${s.reversible}`);
|
|
249
|
+
console.log(`Irreversible ${s.irreversible}`);
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect unexplained mutations in the enforced sandbox.
|
|
3
|
+
* Does NOT attribute actor identity — only absence of mediated action.
|
|
4
|
+
*/
|
|
5
|
+
export declare function classifyExternalMutation(relPath: string, projectRoot?: string): Promise<{
|
|
6
|
+
trusted: boolean;
|
|
7
|
+
policyEventId: string | null;
|
|
8
|
+
message: string;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function recordBlockedWrite(relPath: string, mechanism: string, evidence: string, projectRoot?: string): Promise<import("./types.js").PolicyEvent>;
|
|
11
|
+
export declare function ensureUntrustedIfNeeded(relPath: string, projectRoot?: string): Promise<string | null>;
|
|
12
|
+
/** Stable content fingerprint helper for tests. */
|
|
13
|
+
export declare function contentFingerprint(data: string | Buffer): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, lstat } from "node:fs/promises";
|
|
3
|
+
import { ENFORCED_SANDBOX_DIR_NAME, getEnforcedSandboxPath, getProjectRoot, } from "../paths.js";
|
|
4
|
+
import { listActions } from "../action/store.js";
|
|
5
|
+
import { recordPolicyEvent, formatPolicyViolation } from "./policy.js";
|
|
6
|
+
import { markUntrusted, getPathTrust } from "./trust.js";
|
|
7
|
+
import { sha256 } from "../hash.js";
|
|
8
|
+
/**
|
|
9
|
+
* Detect unexplained mutations in the enforced sandbox.
|
|
10
|
+
* Does NOT attribute actor identity — only absence of mediated action.
|
|
11
|
+
*/
|
|
12
|
+
export async function classifyExternalMutation(relPath, projectRoot = getProjectRoot()) {
|
|
13
|
+
const actions = await listActions(projectRoot);
|
|
14
|
+
const mediated = actions.filter((a) => (a.sandbox ?? "sandbox-test-project") === ENFORCED_SANDBOX_DIR_NAME &&
|
|
15
|
+
a.status === "COMPLETED" &&
|
|
16
|
+
(a.target === relPath || a.targetSecondary === relPath));
|
|
17
|
+
// If any completed mediated action's after-hash still matches live content, authoritative
|
|
18
|
+
const abs = `${getEnforcedSandboxPath(projectRoot)}/${relPath}`;
|
|
19
|
+
let liveHash = null;
|
|
20
|
+
try {
|
|
21
|
+
const st = await lstat(abs);
|
|
22
|
+
if (st.isFile()) {
|
|
23
|
+
liveHash = sha256(await readFile(abs));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
liveHash = null;
|
|
28
|
+
}
|
|
29
|
+
for (const a of mediated.slice().reverse()) {
|
|
30
|
+
const after = a.target === relPath
|
|
31
|
+
? a.after.hash
|
|
32
|
+
: a.afterSecondary?.hash ?? null;
|
|
33
|
+
if (liveHash && after && liveHash === after) {
|
|
34
|
+
return {
|
|
35
|
+
trusted: true,
|
|
36
|
+
policyEventId: null,
|
|
37
|
+
message: `Matches mediated action ${a.id}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const event = await recordPolicyEvent({
|
|
42
|
+
type: "UNKNOWN_MUTATION",
|
|
43
|
+
path: relPath,
|
|
44
|
+
mechanism: "bypass-detection",
|
|
45
|
+
evidence: `No COMPLETED mediated action explains current state of ${relPath} (liveHash=${liveHash ?? "ABSENT"})`,
|
|
46
|
+
status: "UNTRUSTED",
|
|
47
|
+
}, projectRoot);
|
|
48
|
+
await markUntrusted(relPath, event.id, "External unexplained mutation", projectRoot);
|
|
49
|
+
return {
|
|
50
|
+
trusted: false,
|
|
51
|
+
policyEventId: event.id,
|
|
52
|
+
message: formatPolicyViolation(event),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export async function recordBlockedWrite(relPath, mechanism, evidence, projectRoot = getProjectRoot()) {
|
|
56
|
+
return recordPolicyEvent({
|
|
57
|
+
type: "BLOCKED_WRITE",
|
|
58
|
+
path: relPath,
|
|
59
|
+
mechanism,
|
|
60
|
+
evidence,
|
|
61
|
+
status: "BLOCKED",
|
|
62
|
+
}, projectRoot);
|
|
63
|
+
}
|
|
64
|
+
export async function ensureUntrustedIfNeeded(relPath, projectRoot = getProjectRoot()) {
|
|
65
|
+
const existing = await getPathTrust(relPath, projectRoot);
|
|
66
|
+
if (existing?.state === "UNTRUSTED")
|
|
67
|
+
return existing.lastPolicyEventId;
|
|
68
|
+
const result = await classifyExternalMutation(relPath, projectRoot);
|
|
69
|
+
return result.policyEventId;
|
|
70
|
+
}
|
|
71
|
+
/** Stable content fingerprint helper for tests. */
|
|
72
|
+
export function contentFingerprint(data) {
|
|
73
|
+
return createHash("sha256").update(data).digest("hex");
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=detect.js.map
|