@koda-sl/baker-bridge 0.122.0-dev.3613db443 → 0.122.0-dev.71ff4e44a
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 +27 -4
- package/dist/broker/baker-api.d.ts +23 -0
- package/dist/broker/baker-api.d.ts.map +1 -0
- package/dist/broker/baker-api.js +55 -0
- package/dist/broker/baker-api.js.map +1 -0
- package/dist/broker/broker-client.d.ts +10 -0
- package/dist/broker/broker-client.d.ts.map +1 -0
- package/dist/broker/broker-client.js +60 -0
- package/dist/broker/broker-client.js.map +1 -0
- package/dist/broker/ca.d.ts +38 -0
- package/dist/broker/ca.d.ts.map +1 -0
- package/dist/broker/ca.js +80 -0
- package/dist/broker/ca.js.map +1 -0
- package/dist/broker/ca.test.d.ts +2 -0
- package/dist/broker/ca.test.d.ts.map +1 -0
- package/dist/broker/ca.test.js +58 -0
- package/dist/broker/ca.test.js.map +1 -0
- package/dist/broker/credential-cache.d.ts +24 -0
- package/dist/broker/credential-cache.d.ts.map +1 -0
- package/dist/broker/credential-cache.js +69 -0
- package/dist/broker/credential-cache.js.map +1 -0
- package/dist/broker/credential-cache.test.d.ts +2 -0
- package/dist/broker/credential-cache.test.d.ts.map +1 -0
- package/dist/broker/credential-cache.test.js +120 -0
- package/dist/broker/credential-cache.test.js.map +1 -0
- package/dist/broker/credential-text.d.ts +45 -0
- package/dist/broker/credential-text.d.ts.map +1 -0
- package/dist/broker/credential-text.js +103 -0
- package/dist/broker/credential-text.js.map +1 -0
- package/dist/broker/credential-text.test.d.ts +2 -0
- package/dist/broker/credential-text.test.d.ts.map +1 -0
- package/dist/broker/credential-text.test.js +54 -0
- package/dist/broker/credential-text.test.js.map +1 -0
- package/dist/broker/env.d.ts +19 -0
- package/dist/broker/env.d.ts.map +1 -0
- package/dist/broker/env.js +16 -0
- package/dist/broker/env.js.map +1 -0
- package/dist/broker/main.d.ts +3 -0
- package/dist/broker/main.d.ts.map +1 -0
- package/dist/broker/main.js +51 -0
- package/dist/broker/main.js.map +1 -0
- package/dist/broker/proxy.d.ts +24 -0
- package/dist/broker/proxy.d.ts.map +1 -0
- package/dist/broker/proxy.integration.test.d.ts +2 -0
- package/dist/broker/proxy.integration.test.d.ts.map +1 -0
- package/dist/broker/proxy.integration.test.js +201 -0
- package/dist/broker/proxy.integration.test.js.map +1 -0
- package/dist/broker/proxy.js +147 -0
- package/dist/broker/proxy.js.map +1 -0
- package/dist/broker/rewrite.d.ts +44 -0
- package/dist/broker/rewrite.d.ts.map +1 -0
- package/dist/broker/rewrite.js +291 -0
- package/dist/broker/rewrite.js.map +1 -0
- package/dist/broker/rewrite.test.d.ts +2 -0
- package/dist/broker/rewrite.test.d.ts.map +1 -0
- package/dist/broker/rewrite.test.js +171 -0
- package/dist/broker/rewrite.test.js.map +1 -0
- package/dist/broker/socket-service.d.ts +18 -0
- package/dist/broker/socket-service.d.ts.map +1 -0
- package/dist/broker/socket-service.js +103 -0
- package/dist/broker/socket-service.js.map +1 -0
- package/dist/broker/socket-service.test.d.ts +2 -0
- package/dist/broker/socket-service.test.d.ts.map +1 -0
- package/dist/broker/socket-service.test.js +73 -0
- package/dist/broker/socket-service.test.js.map +1 -0
- package/dist/broker/token-bucket.d.ts +9 -0
- package/dist/broker/token-bucket.d.ts.map +1 -0
- package/dist/broker/token-bucket.js +18 -0
- package/dist/broker/token-bucket.js.map +1 -0
- package/dist/broker/usage-reporter.d.ts +18 -0
- package/dist/broker/usage-reporter.d.ts.map +1 -0
- package/dist/broker/usage-reporter.js +31 -0
- package/dist/broker/usage-reporter.js.map +1 -0
- package/dist/broker/usage-reporter.test.d.ts +2 -0
- package/dist/broker/usage-reporter.test.d.ts.map +1 -0
- package/dist/broker/usage-reporter.test.js +20 -0
- package/dist/broker/usage-reporter.test.js.map +1 -0
- package/dist/hono/agent-session.d.ts +3 -0
- package/dist/hono/agent-session.d.ts.map +1 -1
- package/dist/hono/agent-session.js +15 -0
- package/dist/hono/agent-session.js.map +1 -1
- package/dist/hono/agent.d.ts +2 -0
- package/dist/hono/agent.d.ts.map +1 -1
- package/dist/hono/agent.js +10 -2
- package/dist/hono/agent.js.map +1 -1
- package/dist/hono/avatar-casting.d.ts +1 -0
- package/dist/hono/avatar-casting.d.ts.map +1 -1
- package/dist/hono/avatar-casting.js +15 -2
- package/dist/hono/avatar-casting.js.map +1 -1
- package/dist/hono/avatar-casting.test.js +5 -0
- package/dist/hono/avatar-casting.test.js.map +1 -1
- package/dist/hono/baker-ui-tool-schemas.test.js +6 -0
- package/dist/hono/baker-ui-tool-schemas.test.js.map +1 -1
- package/dist/hono/bridge-job.d.ts +15 -1
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +85 -9
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +145 -1
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/connection-input.d.ts +11 -1
- package/dist/hono/connection-input.d.ts.map +1 -1
- package/dist/hono/connection-input.js +30 -5
- package/dist/hono/connection-input.js.map +1 -1
- package/dist/hono/connection-input.test.js +28 -8
- package/dist/hono/connection-input.test.js.map +1 -1
- package/dist/hono/convex.d.ts +8 -0
- package/dist/hono/convex.d.ts.map +1 -1
- package/dist/hono/convex.js +8 -0
- package/dist/hono/convex.js.map +1 -1
- package/dist/hono/credential-input.d.ts +39 -0
- package/dist/hono/credential-input.d.ts.map +1 -0
- package/dist/hono/credential-input.js +78 -0
- package/dist/hono/credential-input.js.map +1 -0
- package/dist/hono/credential-scrub.d.ts +41 -0
- package/dist/hono/credential-scrub.d.ts.map +1 -0
- package/dist/hono/credential-scrub.js +152 -0
- package/dist/hono/credential-scrub.js.map +1 -0
- package/dist/hono/credential-scrub.test.d.ts +2 -0
- package/dist/hono/credential-scrub.test.d.ts.map +1 -0
- package/dist/hono/credential-scrub.test.js +105 -0
- package/dist/hono/credential-scrub.test.js.map +1 -0
- package/dist/hono/latitude-telemetry.d.ts.map +1 -1
- package/dist/hono/latitude-telemetry.js +8 -2
- package/dist/hono/latitude-telemetry.js.map +1 -1
- package/dist/hono/prompt-context.d.ts +8 -1
- package/dist/hono/prompt-context.d.ts.map +1 -1
- package/dist/hono/prompt-context.js +45 -7
- package/dist/hono/prompt-context.js.map +1 -1
- package/dist/hono/prompt-context.test.js +35 -1
- package/dist/hono/prompt-context.test.js.map +1 -1
- package/dist/hono/relay.d.ts +10 -1
- package/dist/hono/relay.d.ts.map +1 -1
- package/dist/hono/relay.js +6 -0
- package/dist/hono/relay.js.map +1 -1
- package/dist/hono/server.d.ts.map +1 -1
- package/dist/hono/server.js +22 -0
- package/dist/hono/server.js.map +1 -1
- package/dist/hono/tag-input.d.ts +106 -3
- package/dist/hono/tag-input.d.ts.map +1 -1
- package/dist/hono/tag-input.js +20 -9
- package/dist/hono/tag-input.js.map +1 -1
- package/dist/hono/tag-input.test.js +19 -1
- package/dist/hono/tag-input.test.js.map +1 -1
- package/dist/hono/types.d.ts +8 -0
- package/dist/hono/types.d.ts.map +1 -1
- package/package.json +5 -2
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { request as httpRequest } from "node:http";
|
|
2
|
+
import { connect as http2Connect } from "node:http2";
|
|
3
|
+
import { connect as netConnect } from "node:net";
|
|
4
|
+
import { networkInterfaces } from "node:os";
|
|
5
|
+
import { connect as tlsConnect } from "node:tls";
|
|
6
|
+
import { generateCACertificate, getLocal } from "mockttp";
|
|
7
|
+
import { ProxyAgent, request } from "undici";
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
9
|
+
import { createBroker } from "./proxy.js";
|
|
10
|
+
const VALUE = "shpat_0a1b2c3d4e5f6a7b";
|
|
11
|
+
let upstream;
|
|
12
|
+
let broker;
|
|
13
|
+
let agent;
|
|
14
|
+
let metaHosts;
|
|
15
|
+
let brokerPort;
|
|
16
|
+
let brokerCaCert;
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
const upstreamCa = await generateCACertificate();
|
|
19
|
+
upstream = getLocal({ https: upstreamCa });
|
|
20
|
+
await upstream.start();
|
|
21
|
+
await upstream.forAnyRequest().thenCallback((req) => ({
|
|
22
|
+
statusCode: 200,
|
|
23
|
+
json: { sawAuthorization: req.headers.authorization ?? null, bodyBytes: req.body.buffer.byteLength },
|
|
24
|
+
}));
|
|
25
|
+
metaHosts = ["graph.facebook.com"];
|
|
26
|
+
const brokerCa = await generateCACertificate();
|
|
27
|
+
broker = createBroker({
|
|
28
|
+
ca: brokerCa,
|
|
29
|
+
port: 0,
|
|
30
|
+
upstreamCAs: [upstreamCa.cert],
|
|
31
|
+
fetchCredentials: () => Promise.resolve([
|
|
32
|
+
{ name: "shop", value: VALUE, allowedHosts: ["localhost"] },
|
|
33
|
+
{ name: "meta-token", value: "EAAG-meta-value-1", allowedHosts: [...metaHosts] },
|
|
34
|
+
]),
|
|
35
|
+
reportUsage: () => Promise.resolve(),
|
|
36
|
+
});
|
|
37
|
+
const port = await broker.start();
|
|
38
|
+
brokerPort = port;
|
|
39
|
+
brokerCaCert = brokerCa.cert;
|
|
40
|
+
agent = new ProxyAgent({ uri: `http://127.0.0.1:${port}`, requestTls: { ca: brokerCa.cert } });
|
|
41
|
+
});
|
|
42
|
+
afterEach(async () => {
|
|
43
|
+
await agent.close();
|
|
44
|
+
await broker.stop();
|
|
45
|
+
await upstream.stop();
|
|
46
|
+
});
|
|
47
|
+
/** A GET over HTTP/2 through the broker, as curl makes one: CONNECT, then TLS with h2 negotiated. */
|
|
48
|
+
function http2GetThroughBroker(path) {
|
|
49
|
+
const authority = `localhost:${upstream.port}`;
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const connect = httpRequest({ host: "127.0.0.1", port: brokerPort, method: "CONNECT", path: authority });
|
|
52
|
+
connect.on("error", reject);
|
|
53
|
+
connect.on("connect", (_res, socket) => {
|
|
54
|
+
const session = http2Connect(`https://${authority}`, {
|
|
55
|
+
createConnection: () => tlsConnect({ socket, servername: "localhost", ALPNProtocols: ["h2"], ca: brokerCaCert }),
|
|
56
|
+
});
|
|
57
|
+
session.on("error", reject);
|
|
58
|
+
const stream = session.request({ ":method": "GET", ":path": path });
|
|
59
|
+
let status = 0;
|
|
60
|
+
let body = "";
|
|
61
|
+
stream.on("response", (headers) => {
|
|
62
|
+
status = Number(headers[":status"]);
|
|
63
|
+
});
|
|
64
|
+
stream.setEncoding("utf8");
|
|
65
|
+
stream.on("data", (chunk) => {
|
|
66
|
+
body += chunk;
|
|
67
|
+
});
|
|
68
|
+
stream.on("end", () => {
|
|
69
|
+
session.close();
|
|
70
|
+
resolve({ status, body });
|
|
71
|
+
});
|
|
72
|
+
stream.on("error", reject);
|
|
73
|
+
stream.end();
|
|
74
|
+
});
|
|
75
|
+
connect.end();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
describe("credential broker proxy", () => {
|
|
79
|
+
it("sends a placeholder written in the URL over HTTP/2", async () => {
|
|
80
|
+
const response = await http2GetThroughBroker("/echo?key={{credential:shop}}");
|
|
81
|
+
expect(response.status).toBe(200);
|
|
82
|
+
const [seen] = await upstream.getMockedEndpoints().then((endpoints) => endpoints[0]?.getSeenRequests() ?? []);
|
|
83
|
+
expect(seen?.url).toContain(`key=${VALUE}`);
|
|
84
|
+
});
|
|
85
|
+
it("sends the value to an allowed host and hands the agent back only the placeholder", async () => {
|
|
86
|
+
const response = await request(`https://localhost:${upstream.port}/echo`, {
|
|
87
|
+
dispatcher: agent,
|
|
88
|
+
headers: { authorization: "Bearer {{credential:shop}}" },
|
|
89
|
+
});
|
|
90
|
+
const body = await response.body.text();
|
|
91
|
+
const [seen] = await upstream.getMockedEndpoints().then((endpoints) => endpoints[0]?.getSeenRequests() ?? []);
|
|
92
|
+
expect(seen?.headers.authorization).toBe(`Bearer ${VALUE}`);
|
|
93
|
+
expect(JSON.parse(body)).toEqual({ sawAuthorization: "Bearer {{credential:shop}}", bodyBytes: 0 });
|
|
94
|
+
});
|
|
95
|
+
it("hands back a Basic auth header an allowed host echoes without the pair it carried", async () => {
|
|
96
|
+
// base64 of `admin:VALUE` is not base64 of VALUE: the pair has to be scrubbed as sent.
|
|
97
|
+
const response = await request(`https://localhost:${upstream.port}/echo`, {
|
|
98
|
+
dispatcher: agent,
|
|
99
|
+
headers: { authorization: `Basic ${btoa("shop:{{credential:shop}}")}` },
|
|
100
|
+
});
|
|
101
|
+
const body = await response.body.text();
|
|
102
|
+
expect(body).not.toContain(btoa(`shop:${VALUE}`).slice(8));
|
|
103
|
+
expect(JSON.parse(body)).toMatchObject({ sawAuthorization: "Basic {{credential:shop}}" });
|
|
104
|
+
});
|
|
105
|
+
it.each([
|
|
106
|
+
"application/x-ndjson",
|
|
107
|
+
"application/yaml",
|
|
108
|
+
"application/octet-stream",
|
|
109
|
+
])("hands back a value echoed in a %s response only as its placeholder", async (contentType) => {
|
|
110
|
+
await upstream
|
|
111
|
+
.forGet("/typed")
|
|
112
|
+
.asPriority(2)
|
|
113
|
+
.thenCallback((req) => ({
|
|
114
|
+
statusCode: 200,
|
|
115
|
+
headers: { "content-type": contentType },
|
|
116
|
+
body: `{"echo":"${String(req.headers.authorization)}"}\n`,
|
|
117
|
+
}));
|
|
118
|
+
const response = await request(`https://localhost:${upstream.port}/typed`, {
|
|
119
|
+
dispatcher: agent,
|
|
120
|
+
headers: { authorization: "Bearer {{credential:shop}}" },
|
|
121
|
+
});
|
|
122
|
+
expect(await response.body.text()).toBe('{"echo":"Bearer {{credential:shop}}"}\n');
|
|
123
|
+
});
|
|
124
|
+
it("refuses a credential bound for a host it is not allowed for, before it leaves the sandbox", async () => {
|
|
125
|
+
const response = await request(`https://localhost:${upstream.port}/echo`, {
|
|
126
|
+
dispatcher: agent,
|
|
127
|
+
headers: { authorization: "Bearer {{credential:meta-token}}" },
|
|
128
|
+
});
|
|
129
|
+
const body = await response.body.text();
|
|
130
|
+
expect(response.statusCode).toBe(403);
|
|
131
|
+
expect(body).toContain('Baker credential "meta-token" is not allowed for host "localhost"');
|
|
132
|
+
const seen = await upstream.getMockedEndpoints().then((endpoints) => endpoints[0]?.getSeenRequests() ?? []);
|
|
133
|
+
expect(seen).toEqual([]);
|
|
134
|
+
});
|
|
135
|
+
it("sends a credential to a host the user allowed since the broker last fetched the table", async () => {
|
|
136
|
+
await request(`https://localhost:${upstream.port}/warm`, {
|
|
137
|
+
dispatcher: agent,
|
|
138
|
+
headers: { authorization: "Bearer {{credential:shop}}" },
|
|
139
|
+
}).then((response) => response.body.text());
|
|
140
|
+
metaHosts = ["graph.facebook.com", "localhost"];
|
|
141
|
+
const response = await request(`https://localhost:${upstream.port}/echo`, {
|
|
142
|
+
dispatcher: agent,
|
|
143
|
+
headers: { authorization: "Bearer {{credential:meta-token}}" },
|
|
144
|
+
});
|
|
145
|
+
expect(response.statusCode).toBe(200);
|
|
146
|
+
expect(await response.body.json()).toMatchObject({ sawAuthorization: "Bearer {{credential:meta-token}}" });
|
|
147
|
+
});
|
|
148
|
+
it("streams an upload larger than the broker reads through intact", async () => {
|
|
149
|
+
const upload = Buffer.alloc(12 * 1024 * 1024, "a");
|
|
150
|
+
const response = await request(`https://localhost:${upstream.port}/upload`, {
|
|
151
|
+
dispatcher: agent,
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: { "content-type": "text/plain" },
|
|
154
|
+
body: upload,
|
|
155
|
+
});
|
|
156
|
+
expect(await response.body.json()).toEqual({ sawAuthorization: null, bodyBytes: upload.byteLength });
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
/** This machine's address on a real network: what a stranger reaching the sandbox's port would appear as. */
|
|
160
|
+
function externalAddress() {
|
|
161
|
+
const address = Object.values(networkInterfaces())
|
|
162
|
+
.flat()
|
|
163
|
+
.find((candidate) => candidate !== undefined && !candidate.internal && candidate.family === "IPv4");
|
|
164
|
+
if (address === undefined)
|
|
165
|
+
throw new Error("this test needs a network interface besides loopback");
|
|
166
|
+
return address.address;
|
|
167
|
+
}
|
|
168
|
+
/** Asks the broker, from `host`, to relay a WebSocket to `target`; resolves with its first response line, or "" when it hangs up. */
|
|
169
|
+
function upgradeThroughBroker(host, target) {
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
const socket = netConnect({ host, port: brokerPort });
|
|
172
|
+
let received = "";
|
|
173
|
+
socket.setTimeout(3_000, () => socket.destroy());
|
|
174
|
+
socket.on("connect", () => socket.write(`GET ${target} HTTP/1.1\r\nHost: ${new URL(target).host}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n`));
|
|
175
|
+
socket.on("data", (chunk) => {
|
|
176
|
+
received += chunk.toString("latin1");
|
|
177
|
+
if (received.includes("\r\n"))
|
|
178
|
+
socket.destroy();
|
|
179
|
+
});
|
|
180
|
+
socket.on("close", () => resolve(received.split("\r\n")[0] ?? ""));
|
|
181
|
+
socket.on("error", (error) => error.code === "ECONNRESET" || error.code === "ECONNREFUSED" ? resolve("") : reject(error));
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
describe("the broker's reach", () => {
|
|
185
|
+
let echo;
|
|
186
|
+
beforeEach(async () => {
|
|
187
|
+
echo = getLocal();
|
|
188
|
+
await echo.start();
|
|
189
|
+
await echo.forAnyWebSocket().thenEcho();
|
|
190
|
+
});
|
|
191
|
+
afterEach(async () => {
|
|
192
|
+
await echo.stop();
|
|
193
|
+
});
|
|
194
|
+
it("relays a WebSocket for this machine", async () => {
|
|
195
|
+
expect(await upgradeThroughBroker("127.0.0.1", `http://localhost:${echo.port}/`)).toContain("101");
|
|
196
|
+
});
|
|
197
|
+
it("relays no WebSocket for anyone else who reaches its port", async () => {
|
|
198
|
+
expect(await upgradeThroughBroker(externalAddress(), `http://localhost:${echo.port}/`)).not.toContain("101");
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
//# sourceMappingURL=proxy.integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.integration.test.js","sourceRoot":"","sources":["../../src/broker/proxy.integration.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The credential broker: an explicit HTTP(S) proxy (mockttp, TLS intercepted
|
|
3
|
+
* with the broker's own CA) that swaps `{{credential:NAME}}` for the value on
|
|
4
|
+
* the way out and swaps the value back to the placeholder on the way in.
|
|
5
|
+
*
|
|
6
|
+
* Two rules, in order:
|
|
7
|
+
* - A request carrying a placeholder is rewritten by `rewriteRequest` and its
|
|
8
|
+
* response is scrubbed. mockttp's `beforeResponse` buffers the whole
|
|
9
|
+
* response, so these responses — event streams included — arrive in one
|
|
10
|
+
* piece. Headers are always scrubbed; a UTF-8 body, whatever its content
|
|
11
|
+
* type, is scrubbed up to 10 MB, and anything larger or binary passes through as-is.
|
|
12
|
+
* - Every other request passes straight through, streamed and untouched: no
|
|
13
|
+
* value was put into it, so there is nothing of ours to take out.
|
|
14
|
+
*
|
|
15
|
+
* Memory: the placeholder check reads each request body in full, and a
|
|
16
|
+
* credential-carrying request's response is buffered in full by mockttp
|
|
17
|
+
* before it is scrubbed (over 10 MB it then passes through unscrubbed).
|
|
18
|
+
* mockttp's `maxBodySize` cannot bound this: in 4.6.3 a body over the limit
|
|
19
|
+
* that a callback matcher has read is forwarded empty, so a large upload would
|
|
20
|
+
* hang or arrive truncated (see the upload test in `proxy.integration.test.ts`).
|
|
21
|
+
*/
|
|
22
|
+
import { getLocal } from "mockttp";
|
|
23
|
+
import { createCredentialCache } from "./credential-cache.js";
|
|
24
|
+
import { scrub } from "./credential-text.js";
|
|
25
|
+
import { carriesPlaceholder, MAX_REWRITE_BODY_BYTES, rewriteRequest, sentBasicPairs, } from "./rewrite.js";
|
|
26
|
+
import { createUsageReporter } from "./usage-reporter.js";
|
|
27
|
+
export const DEFAULT_PROXY_PORT = 8877;
|
|
28
|
+
const LOOPBACK_ADDRESSES = new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
|
|
29
|
+
/** Anyone but this machine: the sandbox's ports are reachable from outside it. */
|
|
30
|
+
function fromElsewhere(req) {
|
|
31
|
+
return !LOOPBACK_ADDRESSES.has(req.remoteIpAddress ?? "");
|
|
32
|
+
}
|
|
33
|
+
async function outgoing(req) {
|
|
34
|
+
return { url: req.url, headers: req.headers, body: (await req.body.getDecodedBuffer()) ?? null };
|
|
35
|
+
}
|
|
36
|
+
function scrubHeaders(headers, credentials) {
|
|
37
|
+
let changed = false;
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
40
|
+
const scrubbed = Array.isArray(value)
|
|
41
|
+
? value.map((item) => scrub(item, credentials))
|
|
42
|
+
: value === undefined
|
|
43
|
+
? undefined
|
|
44
|
+
: scrub(value, credentials);
|
|
45
|
+
if (JSON.stringify(scrubbed) !== JSON.stringify(value))
|
|
46
|
+
changed = true;
|
|
47
|
+
out[name] = scrubbed;
|
|
48
|
+
}
|
|
49
|
+
return changed ? out : undefined;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The scrubbed body, or undefined when it is not UTF-8 text, over 10 MB, or
|
|
53
|
+
* holds no value. Any content type: a value echoed as NDJSON, YAML or JSON sent
|
|
54
|
+
* as `application/octet-stream` reaches the agent all the same.
|
|
55
|
+
*/
|
|
56
|
+
function scrubBody(raw, credentials) {
|
|
57
|
+
if (raw === undefined || raw.byteLength > MAX_REWRITE_BODY_BYTES)
|
|
58
|
+
return undefined;
|
|
59
|
+
let text;
|
|
60
|
+
try {
|
|
61
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(raw);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const scrubbed = scrub(text, credentials);
|
|
67
|
+
return scrubbed === text ? undefined : scrubbed;
|
|
68
|
+
}
|
|
69
|
+
export function createBroker(options) {
|
|
70
|
+
const log = options.log ?? (() => undefined);
|
|
71
|
+
const cache = createCredentialCache({ fetchCredentials: options.fetchCredentials, log });
|
|
72
|
+
const usage = createUsageReporter({ reportUsage: options.reportUsage, log });
|
|
73
|
+
const server = getLocal({
|
|
74
|
+
https: { key: options.ca.key, cert: options.ca.cert },
|
|
75
|
+
recordTraffic: false,
|
|
76
|
+
});
|
|
77
|
+
const upstreamTrust = { additionalTrustedCAs: (options.upstreamCAs ?? []).map((cert) => ({ cert })) };
|
|
78
|
+
let stopUsage = () => undefined;
|
|
79
|
+
async function decide(req) {
|
|
80
|
+
const request = await outgoing(req);
|
|
81
|
+
let decision = rewriteRequest(request, await cache.get());
|
|
82
|
+
if (decision.kind === "reject" && decision.stale && cache.refresh()) {
|
|
83
|
+
decision = rewriteRequest(request, await cache.get());
|
|
84
|
+
}
|
|
85
|
+
return decision;
|
|
86
|
+
}
|
|
87
|
+
async function beforeRequest(req) {
|
|
88
|
+
const decision = await decide(req);
|
|
89
|
+
const host = new URL(req.url).hostname;
|
|
90
|
+
if (decision.kind === "pass")
|
|
91
|
+
return undefined;
|
|
92
|
+
if (decision.kind === "reject") {
|
|
93
|
+
log(`refused ${decision.status} for ${host}`);
|
|
94
|
+
return {
|
|
95
|
+
response: {
|
|
96
|
+
statusCode: decision.status,
|
|
97
|
+
headers: { "content-type": "text/plain; charset=utf-8", "x-baker-credential-broker": "refused" },
|
|
98
|
+
body: decision.message,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
for (const name of decision.used)
|
|
103
|
+
usage.record(name, host);
|
|
104
|
+
log(`sent ${decision.used.join(", ")} to ${host}`);
|
|
105
|
+
return {
|
|
106
|
+
url: decision.url,
|
|
107
|
+
headers: decision.headers,
|
|
108
|
+
...(decision.body === undefined ? {} : { body: decision.body }),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async function beforeResponse(res, req) {
|
|
112
|
+
const resolved = await cache.scrubbable();
|
|
113
|
+
const credentials = [...resolved, ...sentBasicPairs(req.headers, resolved)];
|
|
114
|
+
const headers = scrubHeaders(res.headers, credentials);
|
|
115
|
+
const body = scrubBody(await res.body.getDecodedBuffer(), credentials);
|
|
116
|
+
if (headers === undefined && body === undefined)
|
|
117
|
+
return undefined;
|
|
118
|
+
return { ...(headers === undefined ? {} : { headers }), ...(body === undefined ? {} : { body }) };
|
|
119
|
+
}
|
|
120
|
+
async function registerRules() {
|
|
121
|
+
await server.forAnyRequest().matching(fromElsewhere).always().thenCloseConnection();
|
|
122
|
+
// mockttp listens on every interface, and WebSocket upgrades take their own rule set.
|
|
123
|
+
await server.forAnyWebSocket().matching(fromElsewhere).always().thenCloseConnection();
|
|
124
|
+
await server
|
|
125
|
+
.forAnyRequest()
|
|
126
|
+
.matching(async (req) => carriesPlaceholder(await outgoing(req)))
|
|
127
|
+
.always()
|
|
128
|
+
.thenPassThrough({ ...upstreamTrust, beforeRequest, beforeResponse });
|
|
129
|
+
await server.forAnyRequest().always().thenPassThrough(upstreamTrust);
|
|
130
|
+
await server.forAnyWebSocket().always().thenPassThrough(upstreamTrust);
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
cache,
|
|
134
|
+
async start() {
|
|
135
|
+
await server.start(options.port ?? DEFAULT_PROXY_PORT);
|
|
136
|
+
await registerRules();
|
|
137
|
+
stopUsage = usage.start();
|
|
138
|
+
return server.port;
|
|
139
|
+
},
|
|
140
|
+
async stop() {
|
|
141
|
+
stopUsage();
|
|
142
|
+
await usage.flush();
|
|
143
|
+
await server.stop();
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/broker/proxy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type ResolvedCredential, type ScrubbableCredential } from "./credential-text.ts";
|
|
2
|
+
export type RequestHeaders = Record<string, string | string[] | undefined>;
|
|
3
|
+
export type OutgoingRequest = {
|
|
4
|
+
url: string;
|
|
5
|
+
headers: RequestHeaders;
|
|
6
|
+
/** The decoded (content-encoding removed) body, or null when there is none. */
|
|
7
|
+
body: Uint8Array | null;
|
|
8
|
+
};
|
|
9
|
+
export type RewriteDecision = {
|
|
10
|
+
kind: "pass";
|
|
11
|
+
} | {
|
|
12
|
+
kind: "rewrite";
|
|
13
|
+
url: string;
|
|
14
|
+
headers: RequestHeaders;
|
|
15
|
+
body?: string;
|
|
16
|
+
used: string[];
|
|
17
|
+
} | {
|
|
18
|
+
kind: "reject";
|
|
19
|
+
status: 403 | 404 | 413 | 503;
|
|
20
|
+
message: string;
|
|
21
|
+
/** Set when a fresher credential table could change the answer (a name or website added since the last fetch). */
|
|
22
|
+
stale?: true;
|
|
23
|
+
};
|
|
24
|
+
/** The largest body the broker reads for placeholders or scrubs values out of. */
|
|
25
|
+
export declare const MAX_REWRITE_BODY_BYTES: number;
|
|
26
|
+
/**
|
|
27
|
+
* The `user:value` pairs a request's Basic auth headers carry, as sent. A
|
|
28
|
+
* response that echoes the header holds the pair's base64, which is not the
|
|
29
|
+
* base64 of the value alone, so each is scrubbed as a credential of its own.
|
|
30
|
+
*/
|
|
31
|
+
export declare function sentBasicPairs(headers: RequestHeaders, credentials: readonly ScrubbableCredential[]): ScrubbableCredential[];
|
|
32
|
+
/** Whether a body is text the broker may read and rewrite (by content type, or valid UTF-8 when there is none). */
|
|
33
|
+
export declare function isTextBody(contentType: string | undefined, body: Uint8Array): boolean;
|
|
34
|
+
/** Whether a placeholder appears anywhere the broker looks — the URL, a header or the body. */
|
|
35
|
+
export declare function carriesPlaceholder(request: OutgoingRequest): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* `credentials` is null when the table could not be fetched. Only a
|
|
38
|
+
* placeholder in the URL or a header — where the agent clearly meant to
|
|
39
|
+
* authenticate — is ever refused; one that appears only in the body is text
|
|
40
|
+
* (a chat message, a prompt) and passes through unless it names a credential
|
|
41
|
+
* this host may receive.
|
|
42
|
+
*/
|
|
43
|
+
export declare function rewriteRequest(request: OutgoingRequest, credentials: readonly ResolvedCredential[] | null): RewriteDecision;
|
|
44
|
+
//# sourceMappingURL=rewrite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rewrite.d.ts","sourceRoot":"","sources":["../../src/broker/rewrite.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,oBAAoB,EAAc,MAAM,sBAAsB,CAAC;AAEtG,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AAE3E,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,cAAc,CAAC;IACxB,+EAA+E;IAC/E,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,cAAc,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,GACxF;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,kHAAkH;IAClH,KAAK,CAAC,EAAE,IAAI,CAAC;CACd,CAAC;AAEN,kFAAkF;AAClF,eAAO,MAAM,sBAAsB,QAAmB,CAAC;AAoDvD;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,SAAS,oBAAoB,EAAE,GAC3C,oBAAoB,EAAE,CAUxB;AAwDD,mHAAmH;AACnH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAErF;AA0GD,+FAA+F;AAC/F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAEpE;AAsCD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,eAAe,EACxB,WAAW,EAAE,SAAS,kBAAkB,EAAE,GAAG,IAAI,GAChD,eAAe,CA4BjB"}
|