@adtl/gateway 0.3.2 → 0.5.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/README.md +86 -2
- package/dist/audit.d.ts +23 -0
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +137 -2
- package/dist/audit.js.map +1 -1
- package/dist/auditCli.d.ts +7 -0
- package/dist/auditCli.d.ts.map +1 -0
- package/dist/auditCli.js +54 -0
- package/dist/auditCli.js.map +1 -0
- package/dist/authorizer.d.ts +50 -0
- package/dist/authorizer.d.ts.map +1 -0
- package/dist/authorizer.js +15 -0
- package/dist/authorizer.js.map +1 -0
- package/dist/cli.js +19 -1
- package/dist/cli.js.map +1 -1
- package/dist/gateway.d.ts +12 -3
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +89 -15
- package/dist/gateway.js.map +1 -1
- package/dist/hooks/claudeCode.d.ts +8 -1
- package/dist/hooks/claudeCode.d.ts.map +1 -1
- package/dist/hooks/claudeCode.js +51 -4
- package/dist/hooks/claudeCode.js.map +1 -1
- package/dist/hooks/run.d.ts.map +1 -1
- package/dist/hooks/run.js +9 -3
- package/dist/hooks/run.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/invoker.d.ts +9 -6
- package/dist/invoker.d.ts.map +1 -1
- package/dist/invoker.js +11 -9
- package/dist/invoker.js.map +1 -1
- package/dist/ledgerCli.d.ts +7 -0
- package/dist/ledgerCli.d.ts.map +1 -0
- package/dist/ledgerCli.js +106 -0
- package/dist/ledgerCli.js.map +1 -0
- package/dist/startup.d.ts.map +1 -1
- package/dist/startup.js +4 -2
- package/dist/startup.js.map +1 -1
- package/package.json +5 -4
- package/src/audit.ts +144 -2
- package/src/auditCli.ts +53 -0
- package/src/authorizer.ts +60 -0
- package/src/cli.ts +19 -1
- package/src/gateway.ts +96 -17
- package/src/hooks/claudeCode.ts +55 -5
- package/src/hooks/run.ts +9 -3
- package/src/index.ts +3 -0
- package/src/invoker.ts +24 -17
- package/src/ledgerCli.ts +99 -0
- package/src/startup.ts +4 -2
package/src/ledgerCli.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `adtl-gateway ledger`: look after the label ledger by hand.
|
|
3
|
+
*
|
|
4
|
+
* adtl-gateway ledger stats [-c adtl.yaml]
|
|
5
|
+
* adtl-gateway ledger verify [-c adtl.yaml]
|
|
6
|
+
* adtl-gateway ledger declassify <file>... [-c adtl.yaml] --reason <text>
|
|
7
|
+
* adtl-gateway ledger declassified [-c adtl.yaml] [--limit <n>]
|
|
8
|
+
*
|
|
9
|
+
* `declassify` clears the label of each file's current content, after you've
|
|
10
|
+
* checked it. It is meant to be run by a person; the Claude Code hook asks
|
|
11
|
+
* before letting the agent run it. The reason is required: declassifying is
|
|
12
|
+
* where taint tracking erodes, and the reason is what a later review reads.
|
|
13
|
+
* `declassified` lists recent declassifications for that review.
|
|
14
|
+
*/
|
|
15
|
+
import { readFile } from "node:fs/promises";
|
|
16
|
+
import { userInfo } from "node:os";
|
|
17
|
+
import { dirname, resolve } from "node:path";
|
|
18
|
+
import { parseConfigText } from "@adtl/interceptors";
|
|
19
|
+
import { Ledger, chunkHashes, defaultLedgerPath } from "@adtl/ledger";
|
|
20
|
+
|
|
21
|
+
export const LEDGER_USAGE = `Usage: adtl-gateway ledger stats [-c <adtl.yaml>]
|
|
22
|
+
adtl-gateway ledger verify [-c <adtl.yaml>]
|
|
23
|
+
adtl-gateway ledger declassify <file>... [-c <adtl.yaml>] --reason <text>
|
|
24
|
+
adtl-gateway ledger declassified [-c <adtl.yaml>] [--limit <n>]
|
|
25
|
+
|
|
26
|
+
The ledger remembers content written by sessions that had read untrusted or
|
|
27
|
+
private data, and re-labels it when it is read back. It lives in
|
|
28
|
+
ledger.path from the config, else ADTL_LEDGER, else ~/.adtl/ledger.db.
|
|
29
|
+
|
|
30
|
+
stats Count entries.
|
|
31
|
+
verify Check that no entry was changed or removed (exit 1 if one was).
|
|
32
|
+
declassify Clear the label of each file's current content. Check the
|
|
33
|
+
files first: after this, ADTL treats that content as clean.
|
|
34
|
+
--reason is required and kept in the ledger.
|
|
35
|
+
declassified List recent declassifications: when, who, what and why.`;
|
|
36
|
+
|
|
37
|
+
export interface LedgerCommandResult {
|
|
38
|
+
stdout: string;
|
|
39
|
+
exitCode: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function runLedgerCommand(argv: string[], env: NodeJS.ProcessEnv = process.env): Promise<LedgerCommandResult> {
|
|
43
|
+
const [cmd, ...rest] = argv;
|
|
44
|
+
let config: string | undefined;
|
|
45
|
+
let reason: string | undefined;
|
|
46
|
+
let limit: number | undefined;
|
|
47
|
+
const files: string[] = [];
|
|
48
|
+
for (let i = 0; i < rest.length; i++) {
|
|
49
|
+
const a = rest[i]!;
|
|
50
|
+
if (a === "-c" || a === "--config") config = rest[++i];
|
|
51
|
+
else if (a === "--reason") reason = rest[++i];
|
|
52
|
+
else if (a === "--limit") limit = Number(rest[++i]);
|
|
53
|
+
else if (a.startsWith("-")) throw new Error(`Unknown argument: ${a}\n\n${LEDGER_USAGE}`);
|
|
54
|
+
else files.push(a);
|
|
55
|
+
}
|
|
56
|
+
if (cmd === undefined || cmd === "-h" || cmd === "--help") return { stdout: LEDGER_USAGE, exitCode: 0 };
|
|
57
|
+
if (!["stats", "verify", "declassify", "declassified"].includes(cmd)) throw new Error(`Unknown ledger command: ${cmd}\n\n${LEDGER_USAGE}`);
|
|
58
|
+
if (cmd === "declassify" && files.length === 0) throw new Error(`declassify needs at least one file\n\n${LEDGER_USAGE}`);
|
|
59
|
+
if (cmd === "declassify" && !reason?.trim()) throw new Error(`declassify needs --reason: say why this content is safe\n\n${LEDGER_USAGE}`);
|
|
60
|
+
if (limit !== undefined && !(Number.isInteger(limit) && limit > 0)) throw new Error(`--limit must be a positive whole number\n\n${LEDGER_USAGE}`);
|
|
61
|
+
if (cmd !== "declassify" && files.length > 0) throw new Error(`Unexpected argument: ${files[0]}\n\n${LEDGER_USAGE}`);
|
|
62
|
+
|
|
63
|
+
let path = defaultLedgerPath(env);
|
|
64
|
+
let retentionDays: number | undefined;
|
|
65
|
+
if (config) {
|
|
66
|
+
const abs = resolve(config);
|
|
67
|
+
const parsed = parseConfigText(await readFile(abs, "utf8"), abs);
|
|
68
|
+
if (parsed.ledger.path) path = resolve(dirname(abs), parsed.ledger.path);
|
|
69
|
+
retentionDays = parsed.ledger.retentionDays;
|
|
70
|
+
}
|
|
71
|
+
const ledger = new Ledger(path, retentionDays ? { retentionDays } : {});
|
|
72
|
+
try {
|
|
73
|
+
if (cmd === "stats") {
|
|
74
|
+
const s = ledger.stats();
|
|
75
|
+
return { stdout: `${path}\n${s.records} records from ${s.writers} sessions, ${s.clears} declassified chunks (${s.entries} entries)`, exitCode: 0 };
|
|
76
|
+
}
|
|
77
|
+
if (cmd === "verify") {
|
|
78
|
+
const v = ledger.verify();
|
|
79
|
+
return v.ok
|
|
80
|
+
? { stdout: `${path}: ${v.entries} entries, chain intact`, exitCode: 0 }
|
|
81
|
+
: { stdout: `${path}: TAMPERED: ${v.problem}`, exitCode: 1 };
|
|
82
|
+
}
|
|
83
|
+
if (cmd === "declassified") {
|
|
84
|
+
const list = ledger.declassifications(limit);
|
|
85
|
+
if (list.length === 0) return { stdout: `${path}: nothing declassified`, exitCode: 0 };
|
|
86
|
+
const rows = list.map((d) => `${d.ts} ${d.by} ${d.subject ?? "-"} ${d.chunks} chunk${d.chunks === 1 ? "" : "s"} ${d.reason ?? "(no reason given)"}`);
|
|
87
|
+
return { stdout: rows.join("\n"), exitCode: 0 };
|
|
88
|
+
}
|
|
89
|
+
const by = `user:${userInfo().username}`;
|
|
90
|
+
const lines: string[] = [];
|
|
91
|
+
for (const f of files) {
|
|
92
|
+
const n = ledger.declassify(chunkHashes(await readFile(resolve(f), "utf8")), by, reason!.trim(), f);
|
|
93
|
+
lines.push(n ? `${f}: cleared ${n} labeled chunk${n === 1 ? "" : "s"}` : `${f}: nothing labeled`);
|
|
94
|
+
}
|
|
95
|
+
return { stdout: lines.join("\n"), exitCode: 0 };
|
|
96
|
+
} finally {
|
|
97
|
+
ledger.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
package/src/startup.ts
CHANGED
|
@@ -152,12 +152,14 @@ export async function startGateway(
|
|
|
152
152
|
// 1. Interceptor server (out of process, per the Interceptors WG).
|
|
153
153
|
const shared = sharedSession(env);
|
|
154
154
|
const custom = loaded.gateway.interceptors && expandUpstream("interceptors", loaded.gateway.interceptors, env);
|
|
155
|
+
// The interceptor process starts with a minimal environment; pass on where the ledger lives.
|
|
156
|
+
const ledgerEnv: Record<string, string> = env.ADTL_LEDGER ? { ADTL_LEDGER: env.ADTL_LEDGER } : {};
|
|
155
157
|
const icfg: StdioUpstream = custom
|
|
156
|
-
? { ...custom, env: { ...custom.env, ...(shared ? { ADTL_SESSIONS_DIR: shared.dir } : {}) } }
|
|
158
|
+
? { ...custom, env: { ...ledgerEnv, ...custom.env, ...(shared ? { ADTL_SESSIONS_DIR: shared.dir } : {}) } }
|
|
157
159
|
: {
|
|
158
160
|
command: process.execPath,
|
|
159
161
|
args: [bundledInterceptorsCli(), "--config", loaded.path, ...(shared ? ["--sessions-dir", shared.dir] : [])],
|
|
160
|
-
env:
|
|
162
|
+
env: ledgerEnv,
|
|
161
163
|
};
|
|
162
164
|
const interceptors = await connectUpstream("interceptors", icfg, baseDir);
|
|
163
165
|
clients.push(interceptors);
|