@lifeaitools/clauth 1.30.2 → 1.30.4
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/.clauth-skill/SKILL.md +111 -111
- package/cli/api.classify.test.js +75 -75
- package/cli/assets/codevelop/launcher-active.cmd.template +20 -20
- package/cli/assets/codevelop/launcher-static.cmd.template +7 -7
- package/cli/assets/codevelop/windows-terminal.profiles.json +48 -48
- package/cli/assets/watchdog.ps1 +42 -42
- package/cli/commands/agent-cron.js +396 -396
- package/cli/commands/agent-pool.js +22 -6
- package/cli/commands/codevelop.js +1190 -1190
- package/cli/commands/doctor.js +302 -302
- package/cli/commands/install.js +10 -10
- package/cli/commands/invite.js +175 -175
- package/cli/commands/join.js +179 -179
- package/cli/commands/npm.js +182 -182
- package/cli/commands/scrub.js +327 -327
- package/cli/commands/scrub.test.js +115 -115
- package/cli/commands/serve.js +1070 -144
- package/cli/commands/watchdog.js +209 -209
- package/cli/conf-path.js +21 -21
- package/cli/enrollment-script.js +62 -0
- package/cli/index.js +1050 -1089
- package/cli/lib/fs-git.js +282 -282
- package/cli/recovery.js +101 -101
- package/cli/studio-debug.js +1095 -1087
- package/cli/watchdog-registry.js +209 -209
- package/cli/watchdog-registry.test.js +89 -89
- package/install.ps1 +21 -21
- package/package.json +68 -68
- package/scripts/bin/bootstrap-linux +0 -0
- package/scripts/bin/bootstrap-macos +0 -0
- package/scripts/bin/bootstrap-win.exe +0 -0
- package/supabase/migrations/001_clauth_schema.sql +12 -12
- package/supabase/migrations/003_clauth_config.sql +13 -13
- package/supabase/migrations/003_machine_enrollments.sql +39 -39
- package/cli/commands/serve/tools/fs.js +0 -1055
|
@@ -1,115 +1,115 @@
|
|
|
1
|
-
// node --test cli/commands/scrub.test.js
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import test from "node:test";
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
PATTERNS,
|
|
10
|
-
findTranscripts,
|
|
11
|
-
scrubFile,
|
|
12
|
-
isSecretLike,
|
|
13
|
-
loadExtraPatterns,
|
|
14
|
-
sessionTargets,
|
|
15
|
-
} from "./scrub.js";
|
|
16
|
-
|
|
17
|
-
const GH_TOKEN = "ghp_0123456789abcdefABCDEF0123456789abcd"; // ghp_ + 36 chars
|
|
18
|
-
const VAULT_VALUE = "SuperSecretVaultValue_abc123XYZ"; // secret-like literal
|
|
19
|
-
const CUSTOM_SECRET = "MYCORP-TOKEN-998877"; // only an editable pattern catches it
|
|
20
|
-
|
|
21
|
-
function seedProjects() {
|
|
22
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-scrub-"));
|
|
23
|
-
const sess = path.join(root, "proj", "session-uuid");
|
|
24
|
-
const sidecarDir = path.join(sess, "tool-results");
|
|
25
|
-
fs.mkdirSync(sidecarDir, { recursive: true });
|
|
26
|
-
|
|
27
|
-
const jsonl = path.join(root, "proj", "session-uuid.jsonl");
|
|
28
|
-
const txt = path.join(sidecarDir, "toolu_abc.txt");
|
|
29
|
-
const body = `token=${GH_TOKEN} value=${VAULT_VALUE} custom=${CUSTOM_SECRET}\n`;
|
|
30
|
-
fs.writeFileSync(jsonl, `{"x":"${body.trim()}"}\n`, "utf-8");
|
|
31
|
-
fs.writeFileSync(txt, body, "utf-8"); // the sidecar that the old scrubber skipped
|
|
32
|
-
return { root, jsonl, txt };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
test("findTranscripts discovers .jsonl AND tool-results/*.txt sidecars", () => {
|
|
36
|
-
const { root, jsonl, txt } = seedProjects();
|
|
37
|
-
const found = findTranscripts(root);
|
|
38
|
-
assert.ok(found.includes(jsonl), "should find the .jsonl transcript");
|
|
39
|
-
assert.ok(found.includes(txt), "should find the .txt sidecar (the previously-skipped leak path)");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("scrubFile redacts the github token in BOTH the jsonl and the sidecar", () => {
|
|
43
|
-
const { jsonl, txt } = seedProjects();
|
|
44
|
-
for (const f of [jsonl, txt]) {
|
|
45
|
-
const n = scrubFile(f, { force: true, patterns: PATTERNS, literals: [] });
|
|
46
|
-
assert.ok(n >= 1, `expected >=1 redaction in ${path.basename(f)}`);
|
|
47
|
-
const after = fs.readFileSync(f, "utf-8");
|
|
48
|
-
assert.ok(!after.includes(GH_TOKEN), "github token must be gone");
|
|
49
|
-
assert.ok(after.includes("[GITHUB_TOKEN_REDACTED]"), "redaction marker present");
|
|
50
|
-
assert.ok(after.includes("[CLAUTH-SCRUBBED]"), "file stamped as scrubbed");
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("vault-value (literal) redaction removes an arbitrary secret regardless of format", () => {
|
|
55
|
-
const { txt } = seedProjects();
|
|
56
|
-
const n = scrubFile(txt, { force: true, patterns: [], literals: [{ name: "test-svc", value: VAULT_VALUE }] });
|
|
57
|
-
assert.equal(n, 1, "exactly one literal occurrence redacted");
|
|
58
|
-
const after = fs.readFileSync(txt, "utf-8");
|
|
59
|
-
assert.ok(!after.includes(VAULT_VALUE), "vault value must be gone");
|
|
60
|
-
assert.ok(after.includes("[CLAUTH:test-svc_REDACTED]"), "labelled vault redaction present");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("editable patterns file catches a custom secret with no clauth release", () => {
|
|
64
|
-
const cfg = path.join(os.tmpdir(), `clauth-scrub-patterns-${Date.now()}.json`);
|
|
65
|
-
fs.writeFileSync(cfg, JSON.stringify([{ pattern: "MYCORP-TOKEN-\\d+", replacement: "[MYCORP_REDACTED]" }]), "utf-8");
|
|
66
|
-
const extra = loadExtraPatterns(cfg);
|
|
67
|
-
assert.equal(extra.length, 1, "one extra pattern loaded");
|
|
68
|
-
|
|
69
|
-
const { txt } = seedProjects();
|
|
70
|
-
const n = scrubFile(txt, { force: true, patterns: extra, literals: [] });
|
|
71
|
-
assert.ok(n >= 1, "custom pattern matched");
|
|
72
|
-
const after = fs.readFileSync(txt, "utf-8");
|
|
73
|
-
assert.ok(!after.includes(CUSTOM_SECRET), "custom secret gone");
|
|
74
|
-
assert.ok(after.includes("[MYCORP_REDACTED]"), "custom replacement applied");
|
|
75
|
-
fs.rmSync(cfg, { force: true });
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test("isSecretLike: accepts real tokens, rejects short/url/whitespace values", () => {
|
|
79
|
-
assert.ok(isSecretLike(GH_TOKEN), "long token is secret-like");
|
|
80
|
-
assert.ok(isSecretLike(VAULT_VALUE), "31-char no-space value is secret-like");
|
|
81
|
-
assert.ok(!isSecretLike("short"), "short value rejected");
|
|
82
|
-
assert.ok(!isSecretLike("https://research.regendevcorp.com/mcp"), "plain URL rejected");
|
|
83
|
-
assert.ok(!isSecretLike("has spaces in it value"), "whitespace value rejected");
|
|
84
|
-
assert.ok(!isSecretLike(""), "empty rejected");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("sessionTargets returns ONLY the ending session's transcript + its sidecars", () => {
|
|
88
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-sess-"));
|
|
89
|
-
const proj = path.join(root, "C--proj");
|
|
90
|
-
const sid = "11112222-3333-4444-5555-666677778888";
|
|
91
|
-
fs.mkdirSync(path.join(proj, sid, "tool-results"), { recursive: true });
|
|
92
|
-
const transcript = path.join(proj, `${sid}.jsonl`);
|
|
93
|
-
const sidecar = path.join(proj, sid, "tool-results", "toolu_x.txt");
|
|
94
|
-
const otherSession = path.join(proj, "99990000-aaaa-bbbb-cccc-ddddeeeeffff.jsonl");
|
|
95
|
-
fs.writeFileSync(transcript, "{}\n");
|
|
96
|
-
fs.writeFileSync(sidecar, "tool output\n");
|
|
97
|
-
fs.writeFileSync(otherSession, "{}\n"); // a DIFFERENT session — must NOT be included
|
|
98
|
-
|
|
99
|
-
const targets = sessionTargets({ transcript_path: transcript, session_id: sid });
|
|
100
|
-
assert.ok(targets.includes(transcript), "includes the session transcript");
|
|
101
|
-
assert.ok(targets.includes(sidecar), "includes the session's sidecar");
|
|
102
|
-
assert.ok(!targets.includes(otherSession), "does NOT include other sessions (session-only)");
|
|
103
|
-
assert.equal(targets.length, 2, "exactly the 2 session files");
|
|
104
|
-
|
|
105
|
-
assert.deepEqual(sessionTargets(null), [], "no hook input → no targets (caller falls back)");
|
|
106
|
-
assert.deepEqual(sessionTargets({ transcript_path: path.join(root, "nope.jsonl") }), [], "missing file → none");
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("loadExtraPatterns tolerates a missing/malformed file", () => {
|
|
110
|
-
assert.deepEqual(loadExtraPatterns(path.join(os.tmpdir(), "does-not-exist-xyz.json")), []);
|
|
111
|
-
const bad = path.join(os.tmpdir(), `clauth-bad-${Date.now()}.json`);
|
|
112
|
-
fs.writeFileSync(bad, "{ not json", "utf-8");
|
|
113
|
-
assert.deepEqual(loadExtraPatterns(bad), [], "malformed json → empty, never throws");
|
|
114
|
-
fs.rmSync(bad, { force: true });
|
|
115
|
-
});
|
|
1
|
+
// node --test cli/commands/scrub.test.js
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
PATTERNS,
|
|
10
|
+
findTranscripts,
|
|
11
|
+
scrubFile,
|
|
12
|
+
isSecretLike,
|
|
13
|
+
loadExtraPatterns,
|
|
14
|
+
sessionTargets,
|
|
15
|
+
} from "./scrub.js";
|
|
16
|
+
|
|
17
|
+
const GH_TOKEN = "ghp_0123456789abcdefABCDEF0123456789abcd"; // ghp_ + 36 chars
|
|
18
|
+
const VAULT_VALUE = "SuperSecretVaultValue_abc123XYZ"; // secret-like literal
|
|
19
|
+
const CUSTOM_SECRET = "MYCORP-TOKEN-998877"; // only an editable pattern catches it
|
|
20
|
+
|
|
21
|
+
function seedProjects() {
|
|
22
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-scrub-"));
|
|
23
|
+
const sess = path.join(root, "proj", "session-uuid");
|
|
24
|
+
const sidecarDir = path.join(sess, "tool-results");
|
|
25
|
+
fs.mkdirSync(sidecarDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
const jsonl = path.join(root, "proj", "session-uuid.jsonl");
|
|
28
|
+
const txt = path.join(sidecarDir, "toolu_abc.txt");
|
|
29
|
+
const body = `token=${GH_TOKEN} value=${VAULT_VALUE} custom=${CUSTOM_SECRET}\n`;
|
|
30
|
+
fs.writeFileSync(jsonl, `{"x":"${body.trim()}"}\n`, "utf-8");
|
|
31
|
+
fs.writeFileSync(txt, body, "utf-8"); // the sidecar that the old scrubber skipped
|
|
32
|
+
return { root, jsonl, txt };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test("findTranscripts discovers .jsonl AND tool-results/*.txt sidecars", () => {
|
|
36
|
+
const { root, jsonl, txt } = seedProjects();
|
|
37
|
+
const found = findTranscripts(root);
|
|
38
|
+
assert.ok(found.includes(jsonl), "should find the .jsonl transcript");
|
|
39
|
+
assert.ok(found.includes(txt), "should find the .txt sidecar (the previously-skipped leak path)");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("scrubFile redacts the github token in BOTH the jsonl and the sidecar", () => {
|
|
43
|
+
const { jsonl, txt } = seedProjects();
|
|
44
|
+
for (const f of [jsonl, txt]) {
|
|
45
|
+
const n = scrubFile(f, { force: true, patterns: PATTERNS, literals: [] });
|
|
46
|
+
assert.ok(n >= 1, `expected >=1 redaction in ${path.basename(f)}`);
|
|
47
|
+
const after = fs.readFileSync(f, "utf-8");
|
|
48
|
+
assert.ok(!after.includes(GH_TOKEN), "github token must be gone");
|
|
49
|
+
assert.ok(after.includes("[GITHUB_TOKEN_REDACTED]"), "redaction marker present");
|
|
50
|
+
assert.ok(after.includes("[CLAUTH-SCRUBBED]"), "file stamped as scrubbed");
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("vault-value (literal) redaction removes an arbitrary secret regardless of format", () => {
|
|
55
|
+
const { txt } = seedProjects();
|
|
56
|
+
const n = scrubFile(txt, { force: true, patterns: [], literals: [{ name: "test-svc", value: VAULT_VALUE }] });
|
|
57
|
+
assert.equal(n, 1, "exactly one literal occurrence redacted");
|
|
58
|
+
const after = fs.readFileSync(txt, "utf-8");
|
|
59
|
+
assert.ok(!after.includes(VAULT_VALUE), "vault value must be gone");
|
|
60
|
+
assert.ok(after.includes("[CLAUTH:test-svc_REDACTED]"), "labelled vault redaction present");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("editable patterns file catches a custom secret with no clauth release", () => {
|
|
64
|
+
const cfg = path.join(os.tmpdir(), `clauth-scrub-patterns-${Date.now()}.json`);
|
|
65
|
+
fs.writeFileSync(cfg, JSON.stringify([{ pattern: "MYCORP-TOKEN-\\d+", replacement: "[MYCORP_REDACTED]" }]), "utf-8");
|
|
66
|
+
const extra = loadExtraPatterns(cfg);
|
|
67
|
+
assert.equal(extra.length, 1, "one extra pattern loaded");
|
|
68
|
+
|
|
69
|
+
const { txt } = seedProjects();
|
|
70
|
+
const n = scrubFile(txt, { force: true, patterns: extra, literals: [] });
|
|
71
|
+
assert.ok(n >= 1, "custom pattern matched");
|
|
72
|
+
const after = fs.readFileSync(txt, "utf-8");
|
|
73
|
+
assert.ok(!after.includes(CUSTOM_SECRET), "custom secret gone");
|
|
74
|
+
assert.ok(after.includes("[MYCORP_REDACTED]"), "custom replacement applied");
|
|
75
|
+
fs.rmSync(cfg, { force: true });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("isSecretLike: accepts real tokens, rejects short/url/whitespace values", () => {
|
|
79
|
+
assert.ok(isSecretLike(GH_TOKEN), "long token is secret-like");
|
|
80
|
+
assert.ok(isSecretLike(VAULT_VALUE), "31-char no-space value is secret-like");
|
|
81
|
+
assert.ok(!isSecretLike("short"), "short value rejected");
|
|
82
|
+
assert.ok(!isSecretLike("https://research.regendevcorp.com/mcp"), "plain URL rejected");
|
|
83
|
+
assert.ok(!isSecretLike("has spaces in it value"), "whitespace value rejected");
|
|
84
|
+
assert.ok(!isSecretLike(""), "empty rejected");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("sessionTargets returns ONLY the ending session's transcript + its sidecars", () => {
|
|
88
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-sess-"));
|
|
89
|
+
const proj = path.join(root, "C--proj");
|
|
90
|
+
const sid = "11112222-3333-4444-5555-666677778888";
|
|
91
|
+
fs.mkdirSync(path.join(proj, sid, "tool-results"), { recursive: true });
|
|
92
|
+
const transcript = path.join(proj, `${sid}.jsonl`);
|
|
93
|
+
const sidecar = path.join(proj, sid, "tool-results", "toolu_x.txt");
|
|
94
|
+
const otherSession = path.join(proj, "99990000-aaaa-bbbb-cccc-ddddeeeeffff.jsonl");
|
|
95
|
+
fs.writeFileSync(transcript, "{}\n");
|
|
96
|
+
fs.writeFileSync(sidecar, "tool output\n");
|
|
97
|
+
fs.writeFileSync(otherSession, "{}\n"); // a DIFFERENT session — must NOT be included
|
|
98
|
+
|
|
99
|
+
const targets = sessionTargets({ transcript_path: transcript, session_id: sid });
|
|
100
|
+
assert.ok(targets.includes(transcript), "includes the session transcript");
|
|
101
|
+
assert.ok(targets.includes(sidecar), "includes the session's sidecar");
|
|
102
|
+
assert.ok(!targets.includes(otherSession), "does NOT include other sessions (session-only)");
|
|
103
|
+
assert.equal(targets.length, 2, "exactly the 2 session files");
|
|
104
|
+
|
|
105
|
+
assert.deepEqual(sessionTargets(null), [], "no hook input → no targets (caller falls back)");
|
|
106
|
+
assert.deepEqual(sessionTargets({ transcript_path: path.join(root, "nope.jsonl") }), [], "missing file → none");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("loadExtraPatterns tolerates a missing/malformed file", () => {
|
|
110
|
+
assert.deepEqual(loadExtraPatterns(path.join(os.tmpdir(), "does-not-exist-xyz.json")), []);
|
|
111
|
+
const bad = path.join(os.tmpdir(), `clauth-bad-${Date.now()}.json`);
|
|
112
|
+
fs.writeFileSync(bad, "{ not json", "utf-8");
|
|
113
|
+
assert.deepEqual(loadExtraPatterns(bad), [], "malformed json → empty, never throws");
|
|
114
|
+
fs.rmSync(bad, { force: true });
|
|
115
|
+
});
|