deepdebug-local-agent 1.4.0 → 1.6.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/docs/la-protocol-v2.md +69 -5
- package/env.example +4 -0
- package/package.json +1 -1
- package/scripts/sync-la-protocol-v2-contract.mjs +3 -3
- package/src/la-protocol-v2/content-handlers/audit.js +36 -0
- package/src/la-protocol-v2/content-handlers/config.js +54 -0
- package/src/la-protocol-v2/content-handlers/content-index.js +161 -0
- package/src/la-protocol-v2/content-handlers/egress.js +65 -0
- package/src/la-protocol-v2/content-handlers/git-diff.js +142 -0
- package/src/la-protocol-v2/content-handlers/handler.js +182 -0
- package/src/la-protocol-v2/content-handlers/index.js +6 -0
- package/src/la-protocol-v2/content-handlers/install.js +65 -0
- package/src/la-protocol-v2/content-handlers/path-guard.js +183 -0
- package/src/la-protocol-v2/content-handlers/ref-registry.js +105 -0
- package/src/la-protocol-v2/content.js +7 -1
- package/src/la-protocol-v2/contract/CONTRACT_SOURCE.json +9 -6
- package/src/la-protocol-v2/contract/README.md +49 -2
- package/src/la-protocol-v2/contract/content.index.schema.json +92 -0
- package/src/la-protocol-v2/contract/envelope.schema.json +1 -0
- package/src/la-protocol-v2/contract/index.json +7 -0
- package/src/la-protocol-v2/runtime.js +23 -4
- package/src/server.js +4 -1
- package/test/contract/la-protocol-v2/vectors/content.index.vectors.json +3777 -0
- package/test/contract/la-protocol-v2/vectors/traces/invalid-content-index-before-ready.json +68 -0
- package/test/contract/la-protocol-v2/vectors/traces/valid-content-index.json +161 -0
- package/test/la-protocol-v2/content-alignment.test.js +608 -0
- package/test/la-protocol-v2/content-handlers.test.js +627 -0
- package/test/la-protocol-v2/content-path-guard.test.js +219 -0
- package/test/la-protocol-v2/content-wiring.test.js +250 -0
- package/test/la-protocol-v2/contract.test.js +5 -5
- package/test/la-protocol-v2/helpers/content-rig.js +94 -0
- package/test/la-protocol-v2/helpers/reference-cp.js +8 -0
- package/test/la-protocol-v2/helpers/spawn-agent.js +2 -1
- package/test/la-protocol-v2/traces.test.js +29 -4
- package/tools/interop/LaV2NodeContentInteropTest.java +262 -0
- package/tools/interop/LaV2NodeContentRelayE2ETest.java +374 -0
- package/tools/interop/README.md +17 -0
- package/tools/interop/la-interop-content-client.mjs +102 -0
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
// F3-07: content.request handlers (kinds file and diff) behind LOCAL_AGENT_CONTENT_HANDLERS. Real V2Session, real handler,
|
|
2
|
+
// real files and git repos on disk; the control plane is the in-memory ReferenceCp. What is NOT here: real gateway (tools/interop).
|
|
3
|
+
import { test } from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import crypto from "node:crypto";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { waitFor, uuid, TENANT } from "./helpers/reference-cp.js";
|
|
9
|
+
import { setup, write, git, initRepo, tmpDir } from "./helpers/content-rig.js";
|
|
10
|
+
|
|
11
|
+
const GH = "ghp_" + "A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8";
|
|
12
|
+
const AWS = "AKIAIOSFODNN7EXAMPLE";
|
|
13
|
+
const PEM = "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n-----END RSA PRIVATE KEY-----";
|
|
14
|
+
const SECRETS = [GH, AWS, "MIIEowIBAAKCAQEAxxxxxxxxxxxxxxxxxxxxxxxxxxxx"];
|
|
15
|
+
|
|
16
|
+
/** Every byte of content that went out, per chunk and joined (a secret must be absent from both). */
|
|
17
|
+
function wireContent(rig) {
|
|
18
|
+
const chunks = rig.cp.received.filter((f) => f.type === "content.response").map((f) => Buffer.from(f.payload.data, "base64").toString("utf8"));
|
|
19
|
+
return { chunks, joined: chunks.join("") };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function withRig(opts, body) {
|
|
23
|
+
const rig = await setup(opts);
|
|
24
|
+
try { await body(rig); } finally { await rig.stop(); }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ------------------------------------------------------------------------------------------ file: the happy path
|
|
28
|
+
|
|
29
|
+
test("file: an id minted by the agent is served; the request is acked `accepted` before the answer; content type from the extension", async () => {
|
|
30
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
31
|
+
write(rig.root(), "docs/readme.md", "# hello\n");
|
|
32
|
+
write(rig.root(), "src/data.json", '{"a":1}');
|
|
33
|
+
write(rig.root(), "src/a.txt", "plain");
|
|
34
|
+
const r = await rig.request({ ref: rig.mint("file", "docs/readme.md") });
|
|
35
|
+
assert.deepEqual([r.status, r.contentType, r.text], ["ok", "text/markdown", "# hello\n"]);
|
|
36
|
+
assert.equal((await rig.request({ ref: rig.mint("file", "src/data.json") })).contentType, "application/json");
|
|
37
|
+
assert.equal((await rig.request({ ref: rig.mint("file", "src/a.txt") })).contentType, "text/plain");
|
|
38
|
+
assert.ok(rig.cp.acksFromLa.some((a) => a.status === "accepted"), "the LA acked the content.request itself");
|
|
39
|
+
const frame = r.frames[0];
|
|
40
|
+
assert.equal(frame.workspaceId, "ws-1");
|
|
41
|
+
assert.equal(frame.payload.totalBytes, 8);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("file: 100 KiB goes out in 4 chunks and the CP rebuilds it byte for byte; exactly the size limit is accepted", async () => {
|
|
46
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
47
|
+
const data = Buffer.from("line of text number x\n".repeat(5000)).subarray(0, 100 * 1024);
|
|
48
|
+
write(rig.root(), "big.txt", data);
|
|
49
|
+
const r = await rig.request({ ref: rig.mint("file", "big.txt") });
|
|
50
|
+
assert.equal(r.status, "ok");
|
|
51
|
+
assert.equal(r.chunks, 4);
|
|
52
|
+
assert.ok(r.body.equals(data));
|
|
53
|
+
write(rig.root(), "max.txt", Buffer.alloc(rig.config.maxBytes, 0x61));
|
|
54
|
+
const m = await rig.request({ ref: rig.mint("file", "max.txt") });
|
|
55
|
+
assert.equal(m.status, "ok");
|
|
56
|
+
assert.equal(m.chunks, rig.config.maxBytes / 32768);
|
|
57
|
+
assert.equal(m.body.length, rig.config.maxBytes);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("file: unicode names, spaces and a root reached through a symlink work", async () => {
|
|
62
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
63
|
+
write(rig.root(), "Dir Com Espaço/ficheiro ü ñ 日本.txt", "olá");
|
|
64
|
+
assert.equal((await rig.request({ ref: rig.mint("file", "Dir Com Espaço/ficheiro ü ñ 日本.txt") })).text, "olá");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("file: range is applied to the bytes as served, and past the end is an empty ok", async () => {
|
|
69
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
70
|
+
write(rig.root(), "r.txt", "0123456789");
|
|
71
|
+
const ref = rig.mint("file", "r.txt");
|
|
72
|
+
assert.equal((await rig.request({ ref, range: { offset: 2, length: 3 } })).text, "234");
|
|
73
|
+
assert.equal((await rig.request({ ref, range: { offset: 8, length: 100 } })).text, "89");
|
|
74
|
+
const past = await rig.request({ ref, range: { offset: 500, length: 10 } });
|
|
75
|
+
assert.deepEqual([past.status, past.body.length], ["ok", 0]);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ------------------------------------------------------------------------------------------ refs: opaque ids, nothing is a path
|
|
80
|
+
|
|
81
|
+
test("ref: path-like refs are never interpreted as paths: traversal, absolute, NUL, %2e%2e, unicode tricks -> not_found, nothing echoed", async () => {
|
|
82
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
83
|
+
const outside = tmpDir(); rig.dirs.push(outside);
|
|
84
|
+
write(outside, "passwd", "OUTSIDE-CONTENT");
|
|
85
|
+
write(rig.root(), "src/a.txt", "inside");
|
|
86
|
+
const hostile = ["../../etc/passwd", "../" + path.basename(outside) + "/passwd", path.join(outside, "passwd"), "/etc/passwd", "src/a.txt", "src/../src/a.txt",
|
|
87
|
+
"..%2f..%2fetc%2fpasswd", "%2e%2e/%2e%2e/etc/passwd", "src/a.txt\u0000.png", "..\\..\\windows\\win.ini", "src/a.txt", "src/a.txt ", "./src/a.txt", "src/a.txt", "cr1_" + "A".repeat(22), "ZZMARKERZZ/../x"];
|
|
88
|
+
for (const ref of hostile) {
|
|
89
|
+
// the schema refuses control characters in `ref` (the CP cannot even send the NUL one); the others reach the handler
|
|
90
|
+
if (/[\u0000-\u001f]/.test(ref)) continue;
|
|
91
|
+
const r = await rig.request({ ref });
|
|
92
|
+
assert.equal(r.status, "not_found", ref);
|
|
93
|
+
assert.equal(r.body.length, 0);
|
|
94
|
+
assert.equal(r.errorCode, undefined, "no code needed for not_found");
|
|
95
|
+
}
|
|
96
|
+
const wire = rig.everything();
|
|
97
|
+
assert.equal(wire.includes("OUTSIDE-CONTENT"), false);
|
|
98
|
+
assert.equal(wire.includes("ZZMARKERZZ"), false, "the ref is never echoed");
|
|
99
|
+
assert.equal(wire.includes(path.basename(outside)), false);
|
|
100
|
+
assert.equal(wire.includes(path.basename(rig.root())), false);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("ref: unknown and expired ids are not_found; an expired id does not come back", async () => {
|
|
105
|
+
let t = 5_000_000;
|
|
106
|
+
await withRig({ dlp: "enforce", now: () => t, env: { LOCAL_AGENT_CONTENT_REF_TTL_MS: "60000" } }, async (rig) => {
|
|
107
|
+
write(rig.root(), "a.txt", "x");
|
|
108
|
+
const id = rig.mint("file", "a.txt");
|
|
109
|
+
assert.equal((await rig.request({ ref: id })).status, "ok");
|
|
110
|
+
t += 59_000;
|
|
111
|
+
assert.equal((await rig.request({ ref: id })).status, "ok");
|
|
112
|
+
t += 2_000;
|
|
113
|
+
assert.equal((await rig.request({ ref: id })).status, "not_found");
|
|
114
|
+
t = 5_000_000;
|
|
115
|
+
assert.equal((await rig.request({ ref: id })).status, "not_found", "gone for good, not just late");
|
|
116
|
+
assert.equal((await rig.request({ ref: "cr1_" + crypto.randomBytes(16).toString("base64url") })).status, "not_found");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("ref: an id of workspace A does not open anything through workspace B (both registered), nor through an unregistered one", async () => {
|
|
121
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
122
|
+
write(rig.root("ws-1"), "a.txt", "A-CONTENT");
|
|
123
|
+
write(rig.root("ws-2"), "a.txt", "B-CONTENT");
|
|
124
|
+
const idA = rig.mint("file", "a.txt", "ws-1");
|
|
125
|
+
assert.equal((await rig.request({ ref: idA, workspaceId: "ws-1" })).text, "A-CONTENT");
|
|
126
|
+
const cross = await rig.request({ ref: idA, workspaceId: "ws-2" });
|
|
127
|
+
assert.equal(cross.status, "not_found");
|
|
128
|
+
assert.equal(cross.body.length, 0);
|
|
129
|
+
assert.equal((await rig.request({ ref: idA, workspaceId: "ws-unknown" })).status, "not_found");
|
|
130
|
+
assert.equal((await rig.request({ ref: idA, workspaceId: "default" })).status, "not_found");
|
|
131
|
+
assert.equal(rig.everything().includes("B-CONTENT"), false);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("ref: a workspace that belongs to another tenant is not served, even with a valid id", async () => {
|
|
136
|
+
const rootA = tmpDir(); const rootB = tmpDir();
|
|
137
|
+
await withRig({ dlp: "enforce", workspaces: { "ws-1": { root: rootA }, "ws-foreign": { root: rootB, tenantId: "9d1f6a2c-0000-4000-8000-000000000000" } } }, async (rig) => {
|
|
138
|
+
write(rootA, "a.txt", "mine"); write(rootB, "a.txt", "FOREIGN");
|
|
139
|
+
const id = rig.registry.mint({ workspaceId: "ws-foreign", root: rootB, kind: "file", rel: "a.txt" }).id;
|
|
140
|
+
assert.equal((await rig.request({ ref: id, workspaceId: "ws-foreign" })).status, "not_found");
|
|
141
|
+
assert.equal(rig.everything().includes("FOREIGN"), false);
|
|
142
|
+
});
|
|
143
|
+
fs.rmSync(rootA, { recursive: true, force: true }); fs.rmSync(rootB, { recursive: true, force: true });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("ref: a frame for another tenant never reaches the handler (the session refuses it as tenant_mismatch)", async () => {
|
|
147
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
148
|
+
write(rig.root(), "a.txt", "x");
|
|
149
|
+
const f = rig.cp.frameWith("content.request", { kind: "file", ref: rig.mint("file", "a.txt"), actor: { userId: "u-1", role: "member" } }, { requestId: uuid() });
|
|
150
|
+
f.tenantId = "9d1f6a2c-0000-4000-8000-000000000000";
|
|
151
|
+
rig.cp.sendRaw(JSON.stringify(f));
|
|
152
|
+
await waitFor(() => rig.cp.peerErrors.length > 0);
|
|
153
|
+
assert.equal(rig.cp.peerErrors[0], "tenant_mismatch");
|
|
154
|
+
assert.equal(rig.handler.stats.received, 0);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("ref: a workspace re-opened on another directory invalidates the ids minted for the old root", async () => {
|
|
159
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
160
|
+
write(rig.root(), "a.txt", "old root");
|
|
161
|
+
const id = rig.mint("file", "a.txt");
|
|
162
|
+
const other = tmpDir(); rig.dirs.push(other);
|
|
163
|
+
write(other, "a.txt", "NEW ROOT");
|
|
164
|
+
rig.wsMap.set("ws-1", { root: other });
|
|
165
|
+
const r = await rig.request({ ref: id });
|
|
166
|
+
assert.equal(r.status, "not_found");
|
|
167
|
+
assert.equal(r.text, "");
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// ------------------------------------------------------------------------------------------ path checks at read time
|
|
172
|
+
|
|
173
|
+
test("file: sensitive files are forbidden by name (an id for them cannot be minted) and by link (a harmless name pointing to .env)", async () => {
|
|
174
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
175
|
+
write(rig.root(), ".env", "DB_PASSWORD=hunter2hunter2");
|
|
176
|
+
write(rig.root(), "certs/server.pem", "PEMDATA");
|
|
177
|
+
write(rig.root(), ".git/config", "[remote]\n\turl = https://user:tok@host/r.git");
|
|
178
|
+
for (const rel of [".env", "certs/server.pem", ".git/config"]) assert.equal(rig.mint("file", rel), null, rel);
|
|
179
|
+
// an id minted for an innocent file that is later replaced by a link to .env
|
|
180
|
+
write(rig.root(), "src/innocent.txt", "fine");
|
|
181
|
+
const id = rig.mint("file", "src/innocent.txt");
|
|
182
|
+
fs.rmSync(path.join(rig.root(), "src/innocent.txt"));
|
|
183
|
+
fs.symlinkSync("../.env", path.join(rig.root(), "src/innocent.txt"));
|
|
184
|
+
const r = await rig.request({ ref: id });
|
|
185
|
+
assert.equal(r.status, "forbidden");
|
|
186
|
+
assert.equal(r.body.length, 0);
|
|
187
|
+
assert.equal(rig.everything().includes("hunter2"), false);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("file: the deny list is configurable (LOCAL_AGENT_CONTENT_DENY_GLOBS) and applied at read time too", async () => {
|
|
192
|
+
await withRig({ dlp: "enforce", env: { LOCAL_AGENT_CONTENT_DENY_GLOBS: "private-notes/, *.hush" } }, async (rig) => {
|
|
193
|
+
write(rig.root(), "private-notes/n.txt", "n"); write(rig.root(), "a.hush", "h");
|
|
194
|
+
assert.equal(rig.mint("file", "private-notes/n.txt"), null);
|
|
195
|
+
assert.equal(rig.mint("file", "a.hush"), null);
|
|
196
|
+
write(rig.root(), "ok.txt", "ok");
|
|
197
|
+
const id = rig.mint("file", "ok.txt");
|
|
198
|
+
fs.rmSync(path.join(rig.root(), "ok.txt"));
|
|
199
|
+
fs.symlinkSync("a.hush", path.join(rig.root(), "ok.txt"));
|
|
200
|
+
assert.equal((await rig.request({ ref: id })).status, "forbidden");
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("file: a symlink that leaves the workspace (swapped in after the id was minted) is forbidden and its target is never read", async () => {
|
|
205
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
206
|
+
const outside = tmpDir(); rig.dirs.push(outside);
|
|
207
|
+
write(outside, "secret.txt", "OUTSIDE-SECRET-CONTENT");
|
|
208
|
+
write(outside, "d/x.txt", "OUTSIDE-DIR-CONTENT");
|
|
209
|
+
write(rig.root(), "a.txt", "fine"); write(rig.root(), "sub/b.txt", "fine");
|
|
210
|
+
const a = rig.mint("file", "a.txt");
|
|
211
|
+
const b = rig.mint("file", "sub/b.txt");
|
|
212
|
+
fs.rmSync(path.join(rig.root(), "a.txt"));
|
|
213
|
+
fs.symlinkSync(path.join(outside, "secret.txt"), path.join(rig.root(), "a.txt"));
|
|
214
|
+
fs.rmSync(path.join(rig.root(), "sub"), { recursive: true });
|
|
215
|
+
fs.symlinkSync(path.join(outside, "d"), path.join(rig.root(), "sub"));
|
|
216
|
+
write(outside, "d/b.txt", "OUTSIDE-DIR-CONTENT");
|
|
217
|
+
assert.equal((await rig.request({ ref: a })).status, "forbidden");
|
|
218
|
+
assert.equal((await rig.request({ ref: b })).status, "forbidden");
|
|
219
|
+
assert.equal(rig.everything().includes("OUTSIDE"), false);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("file: missing file, a directory and an id minted for a path that never existed are not_found", async () => {
|
|
224
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
225
|
+
write(rig.root(), "d/x.txt", "x");
|
|
226
|
+
assert.equal((await rig.request({ ref: rig.mint("file", "nope.txt") })).status, "not_found");
|
|
227
|
+
assert.equal((await rig.request({ ref: rig.mint("file", "d") })).status, "not_found");
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("file: a file above the limit is payload_too_large WITHOUT being read (3 GiB sparse file answered at once)", async () => {
|
|
232
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
233
|
+
write(rig.root(), "huge.bin", "");
|
|
234
|
+
fs.truncateSync(path.join(rig.root(), "huge.bin"), 3 * 1024 ** 3);
|
|
235
|
+
const t0 = Date.now();
|
|
236
|
+
const r = await rig.request({ ref: rig.mint("file", "huge.bin") });
|
|
237
|
+
assert.deepEqual([r.status, r.errorCode, r.body.length], ["error", "payload_too_large", 0]);
|
|
238
|
+
assert.ok(Date.now() - t0 < 1500);
|
|
239
|
+
write(rig.root(), "over.txt", Buffer.alloc(rig.config.maxBytes + 1, 0x61));
|
|
240
|
+
assert.equal((await rig.request({ ref: rig.mint("file", "over.txt") })).errorCode, "payload_too_large");
|
|
241
|
+
const small = await setup({ dlp: "enforce", env: { LOCAL_AGENT_CONTENT_MAX_BYTES: "4096" } });
|
|
242
|
+
try {
|
|
243
|
+
write(small.root(), "f.txt", Buffer.alloc(4097, 0x61));
|
|
244
|
+
assert.equal((await small.request({ ref: small.mint("file", "f.txt") })).errorCode, "payload_too_large");
|
|
245
|
+
} finally { await small.stop(); }
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// ------------------------------------------------------------------------------------------ binary
|
|
250
|
+
|
|
251
|
+
test("binary: in enforce it cannot be inspected and is withheld (forbidden); invalid UTF-8 counts as binary", async () => {
|
|
252
|
+
const bin = Buffer.concat([Buffer.from([0x89, 0x50, 0x4e, 0x47, 0, 0, 0, 1]), crypto.randomBytes(5000)]);
|
|
253
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
254
|
+
write(rig.root(), "logo.png", bin);
|
|
255
|
+
write(rig.root(), "latin1.txt", Buffer.from([0x63, 0x61, 0x66, 0xe9, 0xff, 0xfe]));
|
|
256
|
+
const r = await rig.request({ ref: rig.mint("file", "logo.png") });
|
|
257
|
+
assert.equal(r.status, "forbidden");
|
|
258
|
+
assert.equal(r.body.length, 0);
|
|
259
|
+
assert.equal((await rig.request({ ref: rig.mint("file", "latin1.txt") })).status, "forbidden");
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// ------------------------------------------------------------------------------------------ DLP
|
|
264
|
+
|
|
265
|
+
test("DLP enforce: a secret in the middle of a file never goes out in clear; the rest of the file does", async () => {
|
|
266
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
267
|
+
const text = `${"x = 1\n".repeat(4000)}token = "${GH}"\naws_key=${AWS}\n${PEM}\n${"y = 2\n".repeat(4000)}`;
|
|
268
|
+
write(rig.root(), "config.txt", text);
|
|
269
|
+
const r = await rig.request({ ref: rig.mint("file", "config.txt") });
|
|
270
|
+
assert.equal(r.status, "ok");
|
|
271
|
+
for (const s of SECRETS) assert.equal(r.text.includes(s), false, s.slice(0, 8));
|
|
272
|
+
assert.match(r.text, /\[REDACTED:github_token\]/);
|
|
273
|
+
assert.match(r.text, /\[REDACTED:aws_access_key_id\]/);
|
|
274
|
+
assert.match(r.text, /\[REDACTED:pem_private_key\]/);
|
|
275
|
+
assert.ok(r.text.startsWith("x = 1\n") && r.text.trimEnd().endsWith("y = 2"));
|
|
276
|
+
const w = wireContent(rig);
|
|
277
|
+
for (const s of SECRETS) { assert.equal(w.joined.includes(s), false); for (const c of w.chunks) assert.equal(c.includes(s), false); }
|
|
278
|
+
const rec = rig.handler.audit.tail(1)[0];
|
|
279
|
+
assert.equal(rec.dlpMode, "enforce");
|
|
280
|
+
assert.ok(rec.dlpHits >= 3);
|
|
281
|
+
assert.equal(JSON.stringify(rec).includes(GH), false);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("DLP enforce: a secret that would straddle two chunks is found (the whole text is masked BEFORE it is split)", async () => {
|
|
286
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
287
|
+
for (const cut of [1, 17, 20, 39]) { // the token starts `cut` bytes before the 32 KiB boundary
|
|
288
|
+
const head = "a".repeat(32766 - cut - 1) + "\n"; // + "k=" => the token starts `cut` bytes before the boundary
|
|
289
|
+
const text = head + `k=${GH}\n` + "z\n".repeat(100);
|
|
290
|
+
const start = text.indexOf(GH);
|
|
291
|
+
assert.ok(start < 32768 && start + GH.length > 32768, "the token crosses the 32 KiB chunk boundary of the ORIGINAL file");
|
|
292
|
+
write(rig.root(), `straddle-${cut}.txt`, text);
|
|
293
|
+
const r = await rig.request({ ref: rig.mint("file", `straddle-${cut}.txt`) });
|
|
294
|
+
assert.equal(r.status, "ok");
|
|
295
|
+
assert.ok(r.chunks >= 2);
|
|
296
|
+
const w = wireContent(rig);
|
|
297
|
+
assert.equal(w.joined.includes(GH), false);
|
|
298
|
+
for (const c of w.chunks) assert.equal(c.includes(GH.slice(0, 12)), false, "not even a fragment (cut " + cut + ")");
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("DLP warn and off (F3-07b): no content is served at all, the answer is protocol_unsupported and nothing was read", async () => {
|
|
304
|
+
for (const dlp of ["warn", "off"]) {
|
|
305
|
+
await withRig({ dlp }, async (rig) => {
|
|
306
|
+
write(rig.root(), "c.txt", `k=${GH}\n`);
|
|
307
|
+
const r = await rig.request({ ref: rig.mint("file", "c.txt") });
|
|
308
|
+
assert.deepEqual([r.status, r.errorCode, r.body.length], ["error", "protocol_unsupported", 0], dlp);
|
|
309
|
+
assert.equal(r.text.includes(GH), false);
|
|
310
|
+
const rec = rig.handler.audit.tail(1)[0];
|
|
311
|
+
assert.deepEqual([rec.status, rec.errorCode, rec.bytes], ["error", "protocol_unsupported", 0]);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("DLP enforce: a hit in LOCAL_AGENT_DLP_BLOCK_CLASSES withholds the whole file (forbidden, no body)", async () => {
|
|
317
|
+
await withRig({ dlp: "enforce", env: { LOCAL_AGENT_DLP_BLOCK_CLASSES: "private-key" } }, async (rig) => {
|
|
318
|
+
write(rig.root(), "k.txt", `before\n${PEM}\nafter\n`);
|
|
319
|
+
write(rig.root(), "ok.txt", `just ${AWS}\n`);
|
|
320
|
+
const r = await rig.request({ ref: rig.mint("file", "k.txt") });
|
|
321
|
+
assert.deepEqual([r.status, r.body.length], ["forbidden", 0]);
|
|
322
|
+
assert.equal(rig.everything().includes("before"), false);
|
|
323
|
+
assert.match((await rig.request({ ref: rig.mint("file", "ok.txt") })).text, /REDACTED:aws_access_key_id/);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("DLP enforce: above LOCAL_AGENT_DLP_MAX_BYTES it fails closed with payload_too_large (never the oversize placeholder as if it were content)", async () => {
|
|
328
|
+
await withRig({ dlp: "enforce", env: { LOCAL_AGENT_DLP_MAX_BYTES: "1000" } }, async (rig) => {
|
|
329
|
+
write(rig.root(), "a.txt", `${"a".repeat(2000)}${GH}`);
|
|
330
|
+
const r = await rig.request({ ref: rig.mint("file", "a.txt") });
|
|
331
|
+
assert.deepEqual([r.status, r.errorCode, r.body.length], ["error", "payload_too_large", 0]);
|
|
332
|
+
assert.equal(rig.everything().includes("dlp_oversize"), false);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("DLP enforce: masking that makes the text grow past the limit is payload_too_large, not a truncated answer", async () => {
|
|
337
|
+
await withRig({ dlp: "enforce", env: { LOCAL_AGENT_CONTENT_MAX_BYTES: "25000" } }, async (rig) => {
|
|
338
|
+
write(rig.root(), "many.txt", `${AWS} `.repeat(1000)); // 21 KB -> 29 KB once masked
|
|
339
|
+
const r = await rig.request({ ref: rig.mint("file", "many.txt") });
|
|
340
|
+
assert.deepEqual([r.status, r.errorCode], ["error", "payload_too_large"]);
|
|
341
|
+
assert.equal(rig.everything().includes(AWS), false);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("DLP: the mode is read from LOCAL_AGENT_DLP_MODE at each request; leaving enforce stops serving at once (no opt-out), going back resumes", async () => {
|
|
346
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
347
|
+
write(rig.root(), "c.txt", `k=${GH}\n`);
|
|
348
|
+
const id = rig.mint("file", "c.txt");
|
|
349
|
+
assert.equal((await rig.request({ ref: id })).text.includes(GH), false);
|
|
350
|
+
rig.env.LOCAL_AGENT_DLP_MODE = "off";
|
|
351
|
+
const off = await rig.request({ ref: id });
|
|
352
|
+
assert.deepEqual([off.status, off.errorCode, off.text], ["error", "protocol_unsupported", ""]);
|
|
353
|
+
rig.env.LOCAL_AGENT_DLP_MODE = "enforce";
|
|
354
|
+
assert.equal((await rig.request({ ref: id })).status, "ok");
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("range after masking: the offsets refer to what is served, and a range cannot cut a secret out of the masking", async () => {
|
|
359
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
360
|
+
write(rig.root(), "r.txt", `AAAA\n${GH}\nBBBB\n`);
|
|
361
|
+
const ref = rig.mint("file", "r.txt");
|
|
362
|
+
const whole = (await rig.request({ ref })).text;
|
|
363
|
+
assert.equal(whole, "AAAA\n[REDACTED:github_token]\nBBBB\n");
|
|
364
|
+
// asking for the bytes where the token WAS in the original returns bytes of the masked text, never of the token
|
|
365
|
+
const slice = await rig.request({ ref, range: { offset: 5, length: 20 } });
|
|
366
|
+
assert.equal(slice.text, whole.slice(5, 25));
|
|
367
|
+
assert.equal(slice.text.includes("ghp_"), false);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// ------------------------------------------------------------------------------------------ other kinds
|
|
372
|
+
|
|
373
|
+
test("every other kind of the allowlist answers `error` + protocol_unsupported (the code already used for \"no handler\"), reading nothing", async () => {
|
|
374
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
375
|
+
write(rig.root(), "a.txt", "x");
|
|
376
|
+
const ref = rig.mint("file", "a.txt");
|
|
377
|
+
for (const kind of ["incident.detail", "incident.stack", "log.window", "log.groups", "chat.session", "wiki.page", "job.stream", "job.timeline", "approval.detail", "report"]) {
|
|
378
|
+
const r = await rig.request({ kind, ref });
|
|
379
|
+
assert.deepEqual([r.status, r.errorCode, r.body.length], ["error", "protocol_unsupported", 0], kind);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test("an id minted for a file is not valid as a diff, and the other way round", async () => {
|
|
385
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
386
|
+
write(rig.root(), "a.txt", "x");
|
|
387
|
+
assert.equal((await rig.request({ kind: "diff", ref: rig.mint("file", "a.txt") })).status, "not_found");
|
|
388
|
+
assert.equal((await rig.request({ kind: "file", ref: rig.mint("diff", null) })).status, "not_found");
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// ------------------------------------------------------------------------------------------ diff
|
|
393
|
+
|
|
394
|
+
function repoWithChanges(root) {
|
|
395
|
+
write(root, "src/app.js", "const a = 1;\n");
|
|
396
|
+
write(root, "src/other.js", "const o = 1;\n");
|
|
397
|
+
write(root, ".env", "API=old\n");
|
|
398
|
+
write(root, "keys/server.pem", "OLDPEM\n");
|
|
399
|
+
write(root, "logo.bin", Buffer.from([0, 1, 2, 3]));
|
|
400
|
+
git(root, "init", "-q", "-b", "main");
|
|
401
|
+
git(root, "add", "-f", "-A");
|
|
402
|
+
git(root, "commit", "-q", "-m", "init");
|
|
403
|
+
write(root, "src/app.js", `const a = 2;\nconst token = "${GH}";\n`);
|
|
404
|
+
write(root, ".env", "API=NEWSECRETVALUE-1234567890\n");
|
|
405
|
+
write(root, "keys/server.pem", "NEWPEM-CONTENT\n");
|
|
406
|
+
write(root, "logo.bin", Buffer.from([0, 9, 9, 9]));
|
|
407
|
+
write(root, "untracked.txt", "UNTRACKED-NEVER-IN-A-DIFF\n");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
test("diff: `worktree` is the work tree against HEAD; sensitive files are left out, secrets are masked, untracked files are not there", async () => {
|
|
411
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
412
|
+
repoWithChanges(rig.root());
|
|
413
|
+
const r = await rig.request({ kind: "diff", ref: "worktree" });
|
|
414
|
+
assert.equal(r.status, "ok");
|
|
415
|
+
assert.equal(r.contentType, "text/x-diff");
|
|
416
|
+
assert.match(r.text, /^diff --git a\/src\/app\.js b\/src\/app\.js/m, "relative to the workspace root");
|
|
417
|
+
assert.match(r.text, /-const a = 1;/);
|
|
418
|
+
assert.match(r.text, /\[REDACTED:github_token\]/);
|
|
419
|
+
assert.equal(r.text.includes(GH), false);
|
|
420
|
+
assert.equal(r.text.includes(".env"), false);
|
|
421
|
+
assert.equal(r.text.includes("NEWSECRETVALUE"), false);
|
|
422
|
+
assert.equal(r.text.includes("server.pem") || r.text.includes("NEWPEM"), false);
|
|
423
|
+
assert.equal(r.text.includes("UNTRACKED"), false);
|
|
424
|
+
assert.match(r.text, /Binary files a\/logo\.bin and b\/logo\.bin differ/);
|
|
425
|
+
assert.equal(wireContent(rig).joined.includes(GH), false);
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test("diff: a minted id can ask for one file; a sensitive path cannot be minted; an unchanged file is an empty ok", async () => {
|
|
430
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
431
|
+
repoWithChanges(rig.root());
|
|
432
|
+
const one = await rig.request({ kind: "diff", ref: rig.mint("diff", "src/app.js") });
|
|
433
|
+
assert.equal(one.status, "ok");
|
|
434
|
+
assert.match(one.text, /src\/app\.js/);
|
|
435
|
+
assert.equal(one.text.includes("other.js"), false);
|
|
436
|
+
assert.equal(rig.mint("diff", ".env"), null);
|
|
437
|
+
const unchanged = await rig.request({ kind: "diff", ref: rig.mint("diff", "src/other.js") });
|
|
438
|
+
assert.deepEqual([unchanged.status, unchanged.body.length], ["ok", 0]);
|
|
439
|
+
const whole = await rig.request({ kind: "diff", ref: rig.mint("diff", null) });
|
|
440
|
+
assert.match(whole.text, /src\/app\.js/);
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test("diff: not a repository, or a repository without commits -> not_found; another workspace's id does not diff this one", async () => {
|
|
445
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
446
|
+
write(rig.root("ws-1"), "a.txt", "x");
|
|
447
|
+
assert.equal((await rig.request({ kind: "diff", ref: "worktree" })).status, "not_found");
|
|
448
|
+
git(rig.root("ws-1"), "init", "-q", "-b", "main");
|
|
449
|
+
assert.equal((await rig.request({ kind: "diff", ref: "worktree" })).status, "not_found", "no HEAD yet");
|
|
450
|
+
repoWithChanges(rig.root("ws-2"));
|
|
451
|
+
const id = rig.mint("diff", null, "ws-2");
|
|
452
|
+
assert.equal((await rig.request({ kind: "diff", ref: id, workspaceId: "ws-1" })).status, "not_found");
|
|
453
|
+
assert.equal((await rig.request({ kind: "diff", ref: "worktree", workspaceId: "ws-unknown" })).status, "not_found");
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test("diff: a workspace inside a bigger repository only shows its own subtree", async () => {
|
|
458
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
459
|
+
const top = rig.root();
|
|
460
|
+
write(top, "pkg/in.txt", "1\n"); write(top, "outside.txt", "1\n");
|
|
461
|
+
git(top, "init", "-q", "-b", "main"); git(top, "add", "-A"); git(top, "commit", "-q", "-m", "i");
|
|
462
|
+
write(top, "pkg/in.txt", "2\n"); write(top, "outside.txt", "OUTSIDE-CHANGE\n");
|
|
463
|
+
rig.wsMap.set("ws-sub", { root: path.join(top, "pkg") });
|
|
464
|
+
const r = await rig.request({ kind: "diff", ref: "worktree", workspaceId: "ws-sub" });
|
|
465
|
+
assert.equal(r.status, "ok");
|
|
466
|
+
assert.match(r.text, /in\.txt/);
|
|
467
|
+
assert.equal(r.text.includes("OUTSIDE-CHANGE") || r.text.includes("outside.txt"), false);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("diff: repository config cannot run anything (fsmonitor, external diff, textconv) and GIT_DIR in the agent's env is ignored", async () => {
|
|
472
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
473
|
+
const root = rig.root();
|
|
474
|
+
repoWithChanges(root);
|
|
475
|
+
const marker = path.join(tmpDir(), "PWNED"); rig.dirs.push(path.dirname(marker));
|
|
476
|
+
const script = path.join(path.dirname(marker), "evil.sh");
|
|
477
|
+
fs.writeFileSync(script, `#!/bin/sh\ntouch "${marker}"\nexit 0\n`, { mode: 0o755 });
|
|
478
|
+
git(root, "config", "core.fsmonitor", script);
|
|
479
|
+
git(root, "config", "diff.external", script);
|
|
480
|
+
git(root, "config", "diff.evil.textconv", script);
|
|
481
|
+
write(root, ".gitattributes", "*.js diff=evil\n");
|
|
482
|
+
const decoy = tmpDir(); rig.dirs.push(decoy);
|
|
483
|
+
write(decoy, "x", "x"); git(decoy, "init", "-q", "-b", "main"); git(decoy, "add", "-A"); git(decoy, "commit", "-q", "-m", "d");
|
|
484
|
+
process.env.GIT_DIR = path.join(decoy, ".git");
|
|
485
|
+
try {
|
|
486
|
+
const r = await rig.request({ kind: "diff", ref: "worktree" });
|
|
487
|
+
assert.equal(r.status, "ok");
|
|
488
|
+
assert.match(r.text, /src\/app\.js/, "the diff is of the workspace, not of GIT_DIR");
|
|
489
|
+
} finally { delete process.env.GIT_DIR; }
|
|
490
|
+
assert.equal(fs.existsSync(marker), false, "no command named by the repository config ran");
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
test("diff: above the size limit is payload_too_large (git is stopped while it streams)", async () => {
|
|
495
|
+
await withRig({ dlp: "enforce", env: { LOCAL_AGENT_CONTENT_MAX_BYTES: "20000" } }, async (rig) => {
|
|
496
|
+
write(rig.root(), "big.txt", "a\n");
|
|
497
|
+
git(rig.root(), "init", "-q", "-b", "main"); git(rig.root(), "add", "-A"); git(rig.root(), "commit", "-q", "-m", "i");
|
|
498
|
+
write(rig.root(), "big.txt", "change number x\n".repeat(5000));
|
|
499
|
+
const r = await rig.request({ kind: "diff", ref: "worktree" });
|
|
500
|
+
assert.deepEqual([r.status, r.errorCode], ["error", "payload_too_large"]);
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// ------------------------------------------------------------------------------------------ cancellation, limits, backpressure
|
|
505
|
+
|
|
506
|
+
test("cancel in the middle of a stream: no further chunk is read or sent, the slot and the memory come back, the audit says cancelled", async () => {
|
|
507
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
508
|
+
write(rig.root(), "big.txt", Buffer.alloc(rig.config.maxBytes, 0x61));
|
|
509
|
+
const requestId = uuid();
|
|
510
|
+
rig.cp.silent = true; // the CP never acks: 8 chunks fit the window, the rest waits (backpressure)
|
|
511
|
+
rig.cp.sendData("content.request", { kind: "file", ref: rig.mint("file", "big.txt"), actor: { userId: "u-1", role: "member" } }, { workspaceId: "ws-1", requestId });
|
|
512
|
+
await waitFor(() => rig.cp.received.filter((f) => f.type === "content.response").length === 8);
|
|
513
|
+
assert.equal(rig.handler.inflight, 1);
|
|
514
|
+
assert.ok(rig.handler.reservedBytes > 0);
|
|
515
|
+
assert.equal(rig.session.snapshot.waiting, 1, "the 9th chunk waits for a slot and the loop is blocked on it: the other 23 are not even built");
|
|
516
|
+
assert.equal(rig.handler.cancel(requestId), true);
|
|
517
|
+
await waitFor(() => rig.handler.inflight === 0);
|
|
518
|
+
assert.equal(rig.handler.reservedBytes, 0);
|
|
519
|
+
assert.equal(rig.session.snapshot.waiting, 0);
|
|
520
|
+
const before = rig.cp.received.filter((f) => f.type === "content.response").length;
|
|
521
|
+
rig.cp.silent = false;
|
|
522
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
523
|
+
assert.equal(rig.cp.received.filter((f) => f.type === "content.response").length, before, "nothing more was sent");
|
|
524
|
+
assert.equal(rig.handler.audit.tail(1)[0].status, "cancelled");
|
|
525
|
+
assert.equal(rig.handler.stats.cancelled, 1);
|
|
526
|
+
assert.equal(rig.handler.cancel(requestId), false, "already gone");
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test("the request deadline (LOCAL_AGENT_CONTENT_TOTAL_MS) stops a stream the CP does not consume", async () => {
|
|
531
|
+
await withRig({ dlp: "enforce", env: { LOCAL_AGENT_CONTENT_TOTAL_MS: "1000" } }, async (rig) => {
|
|
532
|
+
write(rig.root(), "big.txt", Buffer.alloc(rig.config.maxBytes, 0x61));
|
|
533
|
+
rig.cp.silent = true;
|
|
534
|
+
const requestId = uuid();
|
|
535
|
+
rig.cp.sendData("content.request", { kind: "file", ref: rig.mint("file", "big.txt"), actor: { userId: "u-1", role: "member" } }, { workspaceId: "ws-1", requestId });
|
|
536
|
+
await waitFor(() => rig.handler.audit.tail(1).length === 1, 4000);
|
|
537
|
+
assert.ok(["cancelled", "timeout"].includes(rig.handler.audit.tail(1)[0].status));
|
|
538
|
+
assert.equal(rig.handler.inflight, 0);
|
|
539
|
+
assert.equal(rig.handler.reservedBytes, 0);
|
|
540
|
+
assert.equal(rig.session.snapshot.waiting, 0);
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test("backpressure: with a slow consumer the window of the session holds (at most 8 chunks unacked) and the file still arrives intact", async () => {
|
|
545
|
+
await withRig({ dlp: "enforce", cp: {} }, async (rig) => {
|
|
546
|
+
const data = crypto.randomBytes(Math.floor(rig.config.maxBytes / 2)).toString("base64").slice(0, Math.floor(rig.config.maxBytes / 2));
|
|
547
|
+
write(rig.root(), "big.txt", data);
|
|
548
|
+
rig.cp.ackDelayMs = 20;
|
|
549
|
+
let maxUnacked = 0;
|
|
550
|
+
const iv = setInterval(() => { maxUnacked = Math.max(maxUnacked, rig.session.snapshot.unacked); }, 2);
|
|
551
|
+
const r = await rig.request({ ref: rig.mint("file", "big.txt"), waitMs: 8000 });
|
|
552
|
+
clearInterval(iv);
|
|
553
|
+
assert.equal(r.status, "ok");
|
|
554
|
+
assert.equal(r.text, data);
|
|
555
|
+
assert.ok(maxUnacked <= 8, `max unacked ${maxUnacked}`);
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test("limits: above LOCAL_AGENT_CONTENT_MAX_CONCURRENT the extra request gets rate_limited (a code, no content); the first one is not disturbed", async () => {
|
|
560
|
+
await withRig({ dlp: "enforce", env: { LOCAL_AGENT_CONTENT_MAX_CONCURRENT: "1", LOCAL_AGENT_CONTENT_MAX_INFLIGHT_BYTES: String(64 * 1024 * 1024) } }, async (rig) => {
|
|
561
|
+
write(rig.root(), "big.txt", Buffer.alloc(rig.config.maxBytes, 0x61));
|
|
562
|
+
write(rig.root(), "small.txt", "s");
|
|
563
|
+
rig.cp.ackDelayMs = 40;
|
|
564
|
+
const idBig = rig.mint("file", "big.txt"); const idSmall = rig.mint("file", "small.txt");
|
|
565
|
+
const first = rig.request({ ref: idBig, waitMs: 8000 });
|
|
566
|
+
await waitFor(() => rig.handler.inflight === 1);
|
|
567
|
+
const second = await rig.request({ ref: idSmall, waitMs: 8000 });
|
|
568
|
+
assert.deepEqual([second.status, second.errorCode, second.body.length], ["error", "rate_limited", 0]);
|
|
569
|
+
const f = await first;
|
|
570
|
+
assert.equal(f.status, "ok");
|
|
571
|
+
assert.equal(f.body.length, rig.config.maxBytes);
|
|
572
|
+
await waitFor(() => rig.handler.inflight === 0);
|
|
573
|
+
assert.equal((await rig.request({ ref: idSmall })).text, "s", "the slot came back");
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
test("limits: the memory reservation (LOCAL_AGENT_CONTENT_MAX_INFLIGHT_BYTES) is enforced too", async () => {
|
|
578
|
+
await withRig({ dlp: "enforce", env: { LOCAL_AGENT_CONTENT_MAX_BYTES: "4096", LOCAL_AGENT_CONTENT_MAX_INFLIGHT_BYTES: "16384", LOCAL_AGENT_CONTENT_MAX_CONCURRENT: "8" } }, async (rig) => {
|
|
579
|
+
assert.equal(rig.config.maxInFlightBytes, 16384); // each request reserves 2 x 4096: two fit
|
|
580
|
+
write(rig.root(), "a.txt", "a".repeat(4096));
|
|
581
|
+
rig.cp.ackDelayMs = 100;
|
|
582
|
+
const ids = Array.from({ length: 3 }, () => rig.mint("file", "a.txt"));
|
|
583
|
+
const rs = await Promise.all(ids.map((ref) => rig.request({ ref, waitMs: 6000 })));
|
|
584
|
+
const codes = rs.map((r) => r.errorCode || r.status).sort();
|
|
585
|
+
assert.deepEqual(codes, ["ok", "ok", "rate_limited"].sort());
|
|
586
|
+
await waitFor(() => rig.handler.reservedBytes === 0);
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
// ------------------------------------------------------------------------------------------ nothing sensitive in wire, audit, logs
|
|
591
|
+
|
|
592
|
+
test("nothing but codes in errors, audit and logs: no path, no ref, no content, over a mixed run", async () => {
|
|
593
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
594
|
+
write(rig.root(), "secret-dir-name/CONFIDENTIAL-FILE-NAME.txt", `CONFIDENTIAL-BODY ${GH}`);
|
|
595
|
+
write(rig.root(), ".env", "X=1");
|
|
596
|
+
const id = rig.mint("file", "secret-dir-name/CONFIDENTIAL-FILE-NAME.txt");
|
|
597
|
+
await rig.request({ ref: id });
|
|
598
|
+
await rig.request({ ref: "secret-dir-name/CONFIDENTIAL-FILE-NAME.txt" });
|
|
599
|
+
await rig.request({ ref: "../../CONFIDENTIAL-REF-MARKER" });
|
|
600
|
+
await rig.request({ kind: "report", ref: id });
|
|
601
|
+
fs.rmSync(path.join(rig.root(), "secret-dir-name"), { recursive: true });
|
|
602
|
+
await rig.request({ ref: id });
|
|
603
|
+
const dump = rig.everything() + JSON.stringify(rig.handler.stats);
|
|
604
|
+
for (const needle of ["CONFIDENTIAL-FILE-NAME", "secret-dir-name", "CONFIDENTIAL-REF-MARKER", path.basename(rig.root()), id, GH]) {
|
|
605
|
+
assert.equal(dump.includes(needle), false, needle);
|
|
606
|
+
}
|
|
607
|
+
// frames with a failure carry a status and a wire code only
|
|
608
|
+
for (const f of rig.cp.received.filter((x) => x.type === "content.response" && x.payload.status !== "ok")) {
|
|
609
|
+
assert.deepEqual(Object.keys(f.payload).sort().filter((k) => !["status", "errorCode", "encoding", "data", "byteLength"].includes(k)), []);
|
|
610
|
+
assert.equal(f.payload.data, "");
|
|
611
|
+
}
|
|
612
|
+
const rec = rig.handler.audit.tail(10);
|
|
613
|
+
assert.equal(rec.length, 5);
|
|
614
|
+
for (const r of rec) {
|
|
615
|
+
assert.deepEqual(Object.keys(r).sort(), ["actorRole", "actorSha", "bytes", "chunks", "dlpHits", "dlpMode", "dlpRules", "errorCode", "kind", "ms", "refSha", "requestId", "status", "ts", "workspaceId"]);
|
|
616
|
+
assert.match(r.refSha, /^[0-9a-f]{16}$/);
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
test("a request without requestId is refused with a rejected ack, not left hanging", async () => {
|
|
622
|
+
await withRig({ dlp: "enforce" }, async (rig) => {
|
|
623
|
+
const before = rig.handler.stats.received;
|
|
624
|
+
assert.deepEqual(rig.handler.handle({ params: { kind: "file", ref: "x" }, workspaceId: "ws-1" }), { reject: "schema_violation" });
|
|
625
|
+
assert.equal(rig.handler.stats.received, before + 1);
|
|
626
|
+
});
|
|
627
|
+
});
|