@koda-sl/baker-bridge 0.123.0-dev.f9f855bf1 → 0.124.0-dev.7e3937022

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.
Files changed (177) hide show
  1. package/README.md +55 -17
  2. package/dist/broker/baker-api.d.ts +23 -0
  3. package/dist/broker/baker-api.d.ts.map +1 -0
  4. package/dist/broker/baker-api.js +55 -0
  5. package/dist/broker/baker-api.js.map +1 -0
  6. package/dist/broker/broker-client.d.ts +10 -0
  7. package/dist/broker/broker-client.d.ts.map +1 -0
  8. package/dist/broker/broker-client.js +60 -0
  9. package/dist/broker/broker-client.js.map +1 -0
  10. package/dist/broker/ca.d.ts +38 -0
  11. package/dist/broker/ca.d.ts.map +1 -0
  12. package/dist/broker/ca.js +80 -0
  13. package/dist/broker/ca.js.map +1 -0
  14. package/dist/broker/ca.test.d.ts +2 -0
  15. package/dist/broker/ca.test.d.ts.map +1 -0
  16. package/dist/broker/ca.test.js +58 -0
  17. package/dist/broker/ca.test.js.map +1 -0
  18. package/dist/broker/credential-cache.d.ts +24 -0
  19. package/dist/broker/credential-cache.d.ts.map +1 -0
  20. package/dist/broker/credential-cache.js +69 -0
  21. package/dist/broker/credential-cache.js.map +1 -0
  22. package/dist/broker/credential-cache.test.d.ts +2 -0
  23. package/dist/broker/credential-cache.test.d.ts.map +1 -0
  24. package/dist/broker/credential-cache.test.js +120 -0
  25. package/dist/broker/credential-cache.test.js.map +1 -0
  26. package/dist/broker/credential-text.d.ts +45 -0
  27. package/dist/broker/credential-text.d.ts.map +1 -0
  28. package/dist/broker/credential-text.js +103 -0
  29. package/dist/broker/credential-text.js.map +1 -0
  30. package/dist/broker/credential-text.test.d.ts +2 -0
  31. package/dist/broker/credential-text.test.d.ts.map +1 -0
  32. package/dist/broker/credential-text.test.js +54 -0
  33. package/dist/broker/credential-text.test.js.map +1 -0
  34. package/dist/broker/env.d.ts +19 -0
  35. package/dist/broker/env.d.ts.map +1 -0
  36. package/dist/broker/env.js +16 -0
  37. package/dist/broker/env.js.map +1 -0
  38. package/dist/broker/main.d.ts +3 -0
  39. package/dist/broker/main.d.ts.map +1 -0
  40. package/dist/broker/main.js +51 -0
  41. package/dist/broker/main.js.map +1 -0
  42. package/dist/broker/proxy.d.ts +24 -0
  43. package/dist/broker/proxy.d.ts.map +1 -0
  44. package/dist/broker/proxy.integration.test.d.ts +2 -0
  45. package/dist/broker/proxy.integration.test.d.ts.map +1 -0
  46. package/dist/broker/proxy.integration.test.js +201 -0
  47. package/dist/broker/proxy.integration.test.js.map +1 -0
  48. package/dist/broker/proxy.js +147 -0
  49. package/dist/broker/proxy.js.map +1 -0
  50. package/dist/broker/rewrite.d.ts +44 -0
  51. package/dist/broker/rewrite.d.ts.map +1 -0
  52. package/dist/broker/rewrite.js +291 -0
  53. package/dist/broker/rewrite.js.map +1 -0
  54. package/dist/broker/rewrite.test.d.ts +2 -0
  55. package/dist/broker/rewrite.test.d.ts.map +1 -0
  56. package/dist/broker/rewrite.test.js +171 -0
  57. package/dist/broker/rewrite.test.js.map +1 -0
  58. package/dist/broker/socket-service.d.ts +18 -0
  59. package/dist/broker/socket-service.d.ts.map +1 -0
  60. package/dist/broker/socket-service.js +103 -0
  61. package/dist/broker/socket-service.js.map +1 -0
  62. package/dist/broker/socket-service.test.d.ts +2 -0
  63. package/dist/broker/socket-service.test.d.ts.map +1 -0
  64. package/dist/broker/socket-service.test.js +73 -0
  65. package/dist/broker/socket-service.test.js.map +1 -0
  66. package/dist/broker/token-bucket.d.ts +9 -0
  67. package/dist/broker/token-bucket.d.ts.map +1 -0
  68. package/dist/broker/token-bucket.js +18 -0
  69. package/dist/broker/token-bucket.js.map +1 -0
  70. package/dist/broker/usage-reporter.d.ts +18 -0
  71. package/dist/broker/usage-reporter.d.ts.map +1 -0
  72. package/dist/broker/usage-reporter.js +31 -0
  73. package/dist/broker/usage-reporter.js.map +1 -0
  74. package/dist/broker/usage-reporter.test.d.ts +2 -0
  75. package/dist/broker/usage-reporter.test.d.ts.map +1 -0
  76. package/dist/broker/usage-reporter.test.js +20 -0
  77. package/dist/broker/usage-reporter.test.js.map +1 -0
  78. package/dist/hono/agent-session.d.ts +3 -0
  79. package/dist/hono/agent-session.d.ts.map +1 -1
  80. package/dist/hono/agent-session.js +15 -0
  81. package/dist/hono/agent-session.js.map +1 -1
  82. package/dist/hono/agent.d.ts +2 -0
  83. package/dist/hono/agent.d.ts.map +1 -1
  84. package/dist/hono/agent.js +10 -2
  85. package/dist/hono/agent.js.map +1 -1
  86. package/dist/hono/avatar-accent.d.ts +17 -5
  87. package/dist/hono/avatar-accent.d.ts.map +1 -1
  88. package/dist/hono/avatar-accent.js +31 -64
  89. package/dist/hono/avatar-accent.js.map +1 -1
  90. package/dist/hono/avatar-accent.test.js +46 -35
  91. package/dist/hono/avatar-accent.test.js.map +1 -1
  92. package/dist/hono/avatar-casting.d.ts +1 -0
  93. package/dist/hono/avatar-casting.d.ts.map +1 -1
  94. package/dist/hono/avatar-casting.js +15 -2
  95. package/dist/hono/avatar-casting.js.map +1 -1
  96. package/dist/hono/avatar-casting.test.js +5 -0
  97. package/dist/hono/avatar-casting.test.js.map +1 -1
  98. package/dist/hono/avatar-face-question.d.ts +23 -6
  99. package/dist/hono/avatar-face-question.d.ts.map +1 -1
  100. package/dist/hono/avatar-face-question.js +48 -59
  101. package/dist/hono/avatar-face-question.js.map +1 -1
  102. package/dist/hono/avatar-face-question.test.js +46 -101
  103. package/dist/hono/avatar-face-question.test.js.map +1 -1
  104. package/dist/hono/baker-ui-tool-schemas.test.js +6 -0
  105. package/dist/hono/baker-ui-tool-schemas.test.js.map +1 -1
  106. package/dist/hono/bridge-job.d.ts +19 -1
  107. package/dist/hono/bridge-job.d.ts.map +1 -1
  108. package/dist/hono/bridge-job.js +94 -15
  109. package/dist/hono/bridge-job.js.map +1 -1
  110. package/dist/hono/bridge-job.test.js +152 -2
  111. package/dist/hono/bridge-job.test.js.map +1 -1
  112. package/dist/hono/connection-input.d.ts +3 -0
  113. package/dist/hono/connection-input.d.ts.map +1 -1
  114. package/dist/hono/connection-input.js +28 -4
  115. package/dist/hono/connection-input.js.map +1 -1
  116. package/dist/hono/connection-input.test.js +11 -0
  117. package/dist/hono/connection-input.test.js.map +1 -1
  118. package/dist/hono/convex.d.ts +8 -0
  119. package/dist/hono/convex.d.ts.map +1 -1
  120. package/dist/hono/convex.js +8 -0
  121. package/dist/hono/convex.js.map +1 -1
  122. package/dist/hono/credential-input.d.ts +39 -0
  123. package/dist/hono/credential-input.d.ts.map +1 -0
  124. package/dist/hono/credential-input.js +78 -0
  125. package/dist/hono/credential-input.js.map +1 -0
  126. package/dist/hono/credential-scrub.d.ts +41 -0
  127. package/dist/hono/credential-scrub.d.ts.map +1 -0
  128. package/dist/hono/credential-scrub.js +152 -0
  129. package/dist/hono/credential-scrub.js.map +1 -0
  130. package/dist/hono/credential-scrub.test.d.ts +2 -0
  131. package/dist/hono/credential-scrub.test.d.ts.map +1 -0
  132. package/dist/hono/credential-scrub.test.js +105 -0
  133. package/dist/hono/credential-scrub.test.js.map +1 -0
  134. package/dist/hono/jev.d.ts +16 -0
  135. package/dist/hono/jev.d.ts.map +1 -0
  136. package/dist/hono/jev.js +60 -0
  137. package/dist/hono/jev.js.map +1 -0
  138. package/dist/hono/jev.test.d.ts +2 -0
  139. package/dist/hono/jev.test.d.ts.map +1 -0
  140. package/dist/hono/jev.test.js +15 -0
  141. package/dist/hono/jev.test.js.map +1 -0
  142. package/dist/hono/latitude-telemetry.d.ts.map +1 -1
  143. package/dist/hono/latitude-telemetry.js +8 -2
  144. package/dist/hono/latitude-telemetry.js.map +1 -1
  145. package/dist/hono/openrouter-session.d.ts.map +1 -1
  146. package/dist/hono/openrouter-session.js +12 -1
  147. package/dist/hono/openrouter-session.js.map +1 -1
  148. package/dist/hono/openrouter-session.test.js +9 -3
  149. package/dist/hono/openrouter-session.test.js.map +1 -1
  150. package/dist/hono/prompt-context.d.ts +8 -1
  151. package/dist/hono/prompt-context.d.ts.map +1 -1
  152. package/dist/hono/prompt-context.js +45 -7
  153. package/dist/hono/prompt-context.js.map +1 -1
  154. package/dist/hono/prompt-context.test.js +35 -1
  155. package/dist/hono/prompt-context.test.js.map +1 -1
  156. package/dist/hono/relay.d.ts +10 -1
  157. package/dist/hono/relay.d.ts.map +1 -1
  158. package/dist/hono/relay.js +6 -0
  159. package/dist/hono/relay.js.map +1 -1
  160. package/dist/hono/server.d.ts.map +1 -1
  161. package/dist/hono/server.js +22 -0
  162. package/dist/hono/server.js.map +1 -1
  163. package/dist/hono/tag-input.d.ts +106 -3
  164. package/dist/hono/tag-input.d.ts.map +1 -1
  165. package/dist/hono/tag-input.js +20 -9
  166. package/dist/hono/tag-input.js.map +1 -1
  167. package/dist/hono/tag-input.test.js +19 -1
  168. package/dist/hono/tag-input.test.js.map +1 -1
  169. package/dist/hono/types.d.ts +8 -0
  170. package/dist/hono/types.d.ts.map +1 -1
  171. package/dist/review/digest.d.ts +8 -1
  172. package/dist/review/digest.d.ts.map +1 -1
  173. package/dist/review/digest.js +10 -43
  174. package/dist/review/digest.js.map +1 -1
  175. package/dist/review/digest.test.js +11 -6
  176. package/dist/review/digest.test.js.map +1 -1
  177. package/package.json +5 -2
@@ -0,0 +1,291 @@
1
+ /**
2
+ * The decision the credential broker makes for one outgoing request: send it
3
+ * as-is, send it with placeholders swapped for values, or answer it itself
4
+ * with a refusal the agent can act on. Pure — the proxy wiring lives in
5
+ * `proxy.ts`.
6
+ */
7
+ import { parse as legacyParseUrl } from "node:url";
8
+ import { substitute } from "./credential-text.js";
9
+ /** The largest body the broker reads for placeholders or scrubs values out of. */
10
+ export const MAX_REWRITE_BODY_BYTES = 10 * 1024 * 1024;
11
+ const PLACEHOLDER_MARKER = "{{credential:";
12
+ const ENCODED_PLACEHOLDER = /%7B%7Bcredential%3A([a-z0-9_-]{1,64})%7D%7D/gi;
13
+ /** `%7B%7Bcredential%3Aname%7D%7D` — what an HTTP client makes of a placeholder in a URL — back to `{{credential:name}}`. */
14
+ function decodePlaceholders(text) {
15
+ return text.replace(ENCODED_PLACEHOLDER, (_match, name) => `{{credential:${name}}}`);
16
+ }
17
+ /** Collects what every substitution in one request used, was refused, or could not find. */
18
+ function createSubstituter(credentials, host) {
19
+ const used = new Set();
20
+ const denied = new Map();
21
+ const unknown = new Set();
22
+ return {
23
+ used,
24
+ denied,
25
+ unknown,
26
+ apply(text, encode = (value) => value) {
27
+ const encoded = credentials.map((credential) => ({ ...credential, value: encode(credential.value) }));
28
+ const result = substitute(text, encoded, host);
29
+ for (const name of result.used)
30
+ used.add(name);
31
+ for (const { name, allowedHosts } of result.denied)
32
+ denied.set(name, allowedHosts);
33
+ for (const name of result.unknown)
34
+ unknown.add(name);
35
+ return result.out;
36
+ },
37
+ };
38
+ }
39
+ const BASIC_AUTH = /^(Basic\s+)([A-Za-z0-9+/=_-]+)\s*$/i;
40
+ /** The `user:password` a Basic auth header carries. */
41
+ function basicCredentials(value) {
42
+ const match = BASIC_AUTH.exec(value);
43
+ return match === null ? null : Buffer.from(match[2] ?? "", "base64").toString("utf8");
44
+ }
45
+ /** The `user:password` inside a Basic auth header, when it carries a placeholder. */
46
+ function basicPair(value) {
47
+ const pair = basicCredentials(value);
48
+ return pair?.includes(PLACEHOLDER_MARKER) ? { scheme: BASIC_AUTH.exec(value)?.[1] ?? "Basic ", pair } : null;
49
+ }
50
+ function rewriteHeaderValue(value, substituter) {
51
+ const basic = basicPair(value);
52
+ if (basic === null)
53
+ return substituter.apply(value);
54
+ return basic.scheme + Buffer.from(substituter.apply(basic.pair), "utf8").toString("base64");
55
+ }
56
+ /**
57
+ * The `user:value` pairs a request's Basic auth headers carry, as sent. A
58
+ * response that echoes the header holds the pair's base64, which is not the
59
+ * base64 of the value alone, so each is scrubbed as a credential of its own.
60
+ */
61
+ export function sentBasicPairs(headers, credentials) {
62
+ return Object.entries(headers).flatMap(([name, values]) => name.startsWith(":")
63
+ ? []
64
+ : [values ?? []].flat().flatMap((value) => {
65
+ const pair = basicCredentials(value);
66
+ const credential = pair === null ? undefined : credentials.find((c) => pair.includes(c.value));
67
+ return pair === null || credential === undefined ? [] : [{ name: credential.name, value: pair }];
68
+ }));
69
+ }
70
+ /**
71
+ * HTTP/2 pseudo-headers (`:path`, `:authority`) are returned as they came:
72
+ * mockttp regenerates them from the URL, which is rewritten on its own, and
73
+ * refuses a request whose `:path` was changed.
74
+ */
75
+ function rewriteHeaders(headers, substituter) {
76
+ const out = {};
77
+ for (const [name, value] of Object.entries(headers)) {
78
+ if (value === undefined)
79
+ continue;
80
+ if (name.startsWith(":")) {
81
+ out[name] = value;
82
+ continue;
83
+ }
84
+ out[name] = Array.isArray(value)
85
+ ? value.map((item) => rewriteHeaderValue(item, substituter))
86
+ : rewriteHeaderValue(value, substituter);
87
+ }
88
+ return out;
89
+ }
90
+ function headerValue(headers, name) {
91
+ const value = headers[name];
92
+ return Array.isArray(value) ? value[0] : value;
93
+ }
94
+ function isUtf8(bytes) {
95
+ try {
96
+ new TextDecoder("utf-8", { fatal: true }).decode(bytes);
97
+ return true;
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ }
103
+ /** How a value must be escaped to sit inside this body, or null when the body is not text. */
104
+ function bodyFormat(contentType, body) {
105
+ const type = contentType?.split(";")[0]?.trim().toLowerCase();
106
+ if (type === undefined || type === "")
107
+ return isUtf8(body) ? "text" : null;
108
+ if (type === "application/json" || type.endsWith("+json"))
109
+ return "json";
110
+ if (type === "application/x-www-form-urlencoded")
111
+ return "form";
112
+ if (type.startsWith("text/") ||
113
+ type.endsWith("/xml") ||
114
+ type.endsWith("+xml") ||
115
+ type === "application/graphql" ||
116
+ type === "application/javascript") {
117
+ return "text";
118
+ }
119
+ return null;
120
+ }
121
+ /** Whether a body is text the broker may read and rewrite (by content type, or valid UTF-8 when there is none). */
122
+ export function isTextBody(contentType, body) {
123
+ return bodyFormat(contentType, body) !== null;
124
+ }
125
+ const ENCODE_FOR = {
126
+ json: (value) => JSON.stringify(value).slice(1, -1),
127
+ form: encodeURIComponent,
128
+ text: (value) => value,
129
+ };
130
+ function headerText(headers) {
131
+ return Object.values(headers)
132
+ .flat()
133
+ .map((value) => (value === undefined ? "" : (basicPair(value)?.pair ?? value)))
134
+ .join("\n");
135
+ }
136
+ const BODY_TOO_LARGE = {
137
+ kind: "reject",
138
+ status: 413,
139
+ message: "Baker credentials can only be placed in text request bodies (JSON, form, text, XML or GraphQL) up to 10 MB. Put the placeholder in a header or the URL instead.",
140
+ };
141
+ function hasPlaceholder(text) {
142
+ return text.includes(PLACEHOLDER_MARKER) || decodePlaceholders(text) !== text;
143
+ }
144
+ const UNAVAILABLE = {
145
+ kind: "reject",
146
+ status: 503,
147
+ message: "Baker credentials are unavailable right now — try again in a minute.",
148
+ };
149
+ const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
150
+ /** A DNS name as the WHATWG parser emits it (IDNs arrive as `xn--`). Anything else could parse differently elsewhere. */
151
+ const PLAIN_HOSTNAME = /^[a-z0-9.-]+$/;
152
+ /**
153
+ * mockttp connects to `url.parse(<returned url>).hostname`, not to the name
154
+ * the check read. The two parsers disagree on unusual characters, so a
155
+ * credential is only substituted when both see the same host.
156
+ */
157
+ function upstreamHostMatches(url, host) {
158
+ return PLAIN_HOSTNAME.test(host) && legacyParseUrl(url).hostname === host;
159
+ }
160
+ const HOST_MISMATCH = {
161
+ kind: "reject",
162
+ status: 403,
163
+ message: "Baker credentials are only sent to a plain website address, and never inside the address itself. Put the credential in a header, the query string or the body.",
164
+ };
165
+ /** The host a `Host` / `:authority` value names, port stripped and lower-cased; null when it is not a host. */
166
+ function authorityHost(value) {
167
+ const candidate = `http://${value.trim()}`;
168
+ return URL.canParse(candidate) ? new URL(candidate).hostname.toLowerCase() : null;
169
+ }
170
+ /**
171
+ * The value goes to the site the check read, not to whatever site the agent's
172
+ * own `Host` or `:authority` names — on a shared CDN those can differ, and
173
+ * both headers are forwarded unchanged.
174
+ */
175
+ function hostHeadersMatch(headers, host) {
176
+ return ["host", ":authority"].every((name) => {
177
+ const values = headers[name];
178
+ if (values === undefined)
179
+ return true;
180
+ return [values].flat().every((value) => authorityHost(value) === host.toLowerCase());
181
+ });
182
+ }
183
+ function hostHeaderRefusal(headers, host, used) {
184
+ if (used.size === 0 || hostHeadersMatch(headers, host))
185
+ return null;
186
+ return {
187
+ kind: "reject",
188
+ status: 403,
189
+ message: `This request goes to "${host}" but its Host header names another website, so Baker did not send the credential. Remove the Host header.`,
190
+ };
191
+ }
192
+ const SETTINGS = "Settings → Credentials";
193
+ function refuse(substituter, host) {
194
+ const [unknown] = substituter.unknown;
195
+ if (unknown !== undefined) {
196
+ return {
197
+ kind: "reject",
198
+ status: 404,
199
+ message: `No Baker credential named "${unknown}". Run \`baker credentials list\` to see the names you can use.`,
200
+ stale: true,
201
+ };
202
+ }
203
+ const [denied] = substituter.denied;
204
+ if (denied === undefined)
205
+ return null;
206
+ const [name, allowedHosts] = denied;
207
+ const message = allowedHosts.length === 0
208
+ ? `Baker credential "${name}" has no websites it may be sent to yet. Ask the user to choose them in ${SETTINGS}.`
209
+ : `Baker credential "${name}" is not allowed for host "${host}". Allowed: ${allowedHosts.join(", ")}. Ask the user to add this website to the credential in ${SETTINGS}.`;
210
+ return { kind: "reject", status: 403, message, stale: true };
211
+ }
212
+ function bodyText(body) {
213
+ return body === null ? "" : Buffer.from(body).toString("latin1");
214
+ }
215
+ /** Whether a placeholder appears anywhere the broker looks — the URL, a header or the body. */
216
+ export function carriesPlaceholder(request) {
217
+ return [request.url, headerText(request.headers), bodyText(request.body)].some(hasPlaceholder);
218
+ }
219
+ /** Whether any placeholder in the body names a credential this host may receive — only then is the body rewritten. */
220
+ function bodyUsesCredential(body, credentials, host) {
221
+ if (body === null)
222
+ return false;
223
+ const text = decodePlaceholders(bodyText(body));
224
+ return hasPlaceholder(text) && substitute(text, credentials, host).used.length > 0;
225
+ }
226
+ /** The body with every placeholder for a credential this host may receive swapped in, escaped for its format. */
227
+ function rewriteBody(request, credentials, host) {
228
+ const raw = request.body ?? new Uint8Array();
229
+ const format = bodyFormat(headerValue(request.headers, "content-type"), raw);
230
+ if (format === null || raw.byteLength > MAX_REWRITE_BODY_BYTES)
231
+ return BODY_TOO_LARGE;
232
+ const substituter = createSubstituter(credentials, host);
233
+ const decoded = Buffer.from(raw).toString("utf8");
234
+ const text = substituter.apply(format === "form" ? decodePlaceholders(decoded) : decoded, ENCODE_FOR[format]);
235
+ return { kind: "body", text, used: substituter.used };
236
+ }
237
+ /** A refusal for plain http to anything but this machine. */
238
+ function insecureRefusal(target) {
239
+ if (target.protocol === "https:" || LOOPBACK_HOSTS.has(target.hostname))
240
+ return null;
241
+ return {
242
+ kind: "reject",
243
+ status: 403,
244
+ message: `Baker credentials are only sent over https. Use https://${target.host}${target.pathname} instead.`,
245
+ };
246
+ }
247
+ /**
248
+ * `credentials` is null when the table could not be fetched. Only a
249
+ * placeholder in the URL or a header — where the agent clearly meant to
250
+ * authenticate — is ever refused; one that appears only in the body is text
251
+ * (a chat message, a prompt) and passes through unless it names a credential
252
+ * this host may receive.
253
+ */
254
+ export function rewriteRequest(request, credentials) {
255
+ if (!carriesPlaceholder(request))
256
+ return { kind: "pass" };
257
+ const url = decodePlaceholders(request.url);
258
+ if (!URL.canParse(request.url))
259
+ return HOST_MISMATCH;
260
+ const target = new URL(request.url);
261
+ const host = target.hostname;
262
+ const authPlaceholder = [url, headerText(request.headers)].some(hasPlaceholder);
263
+ if (credentials === null)
264
+ return authPlaceholder ? UNAVAILABLE : { kind: "pass" };
265
+ const bodyCarriesCredential = bodyUsesCredential(request.body, credentials, host);
266
+ if (!authPlaceholder && !bodyCarriesCredential)
267
+ return { kind: "pass" };
268
+ const insecure = insecureRefusal(target);
269
+ if (insecure !== null)
270
+ return insecure;
271
+ const substituter = createSubstituter(credentials, host);
272
+ const rewrittenUrl = substituter.apply(url, encodeURIComponent);
273
+ if (!upstreamHostMatches(rewrittenUrl, host))
274
+ return HOST_MISMATCH;
275
+ const headers = rewriteHeaders(request.headers, substituter);
276
+ const body = bodyCarriesCredential ? rewriteBody(request, credentials, host) : undefined;
277
+ if (body?.kind === "reject")
278
+ return body;
279
+ const used = new Set([...substituter.used, ...(body?.used ?? [])]);
280
+ const refusal = refuse(substituter, host) ?? hostHeaderRefusal(request.headers, host, used);
281
+ if (refusal !== null)
282
+ return refusal;
283
+ return {
284
+ kind: "rewrite",
285
+ url: rewrittenUrl,
286
+ headers,
287
+ ...(body === undefined ? {} : { body: body.text }),
288
+ used: [...used],
289
+ };
290
+ }
291
+ //# sourceMappingURL=rewrite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rewrite.js","sourceRoot":"","sources":["../../src/broker/rewrite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rewrite.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rewrite.test.d.ts","sourceRoot":"","sources":["../../src/broker/rewrite.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,171 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { rewriteRequest } from "./rewrite.js";
3
+ const credentials = [
4
+ { name: "meta-token", value: "EAAG-secret/value+1", allowedHosts: ["graph.facebook.com"] },
5
+ { name: "shop", value: 'shpat_"quoted"&x=1', allowedHosts: ["acme.myshopify.com"] },
6
+ { name: "nowhere", value: "unscoped-secret-1", allowedHosts: [] },
7
+ ];
8
+ function request(overrides = {}) {
9
+ return {
10
+ url: "https://graph.facebook.com/v21.0/me",
11
+ headers: { accept: "application/json" },
12
+ body: null,
13
+ ...overrides,
14
+ };
15
+ }
16
+ const text = (value) => new TextEncoder().encode(value);
17
+ describe("rewriteRequest", () => {
18
+ it("passes a request that carries no placeholder untouched", () => {
19
+ expect(rewriteRequest(request({ body: text('{"a":1}') }), credentials)).toEqual({ kind: "pass" });
20
+ });
21
+ it("swaps a placeholder in a header for the value when the host is allowed", () => {
22
+ const decision = rewriteRequest(request({ headers: { authorization: "Bearer {{credential:meta-token}}", accept: "*/*" } }), credentials);
23
+ expect(decision).toEqual({
24
+ kind: "rewrite",
25
+ url: "https://graph.facebook.com/v21.0/me",
26
+ headers: { authorization: "Bearer EAAG-secret/value+1", accept: "*/*" },
27
+ used: ["meta-token"],
28
+ });
29
+ });
30
+ it("swaps a placeholder in a JSON body, escaped so the body stays valid JSON", () => {
31
+ const decision = rewriteRequest(request({
32
+ url: "https://acme.myshopify.com/admin/graphql.json",
33
+ headers: { "content-type": "application/json; charset=utf-8" },
34
+ body: text('{"token":"{{credential:shop}}"}'),
35
+ }), credentials);
36
+ expect(decision).toMatchObject({ kind: "rewrite", used: ["shop"] });
37
+ const body = decision.kind === "rewrite" ? (decision.body ?? "") : "";
38
+ expect(JSON.parse(body)).toEqual({ token: 'shpat_"quoted"&x=1' });
39
+ });
40
+ it("swaps a placeholder in a form body, form-encoded", () => {
41
+ const decision = rewriteRequest(request({
42
+ url: "https://acme.myshopify.com/oauth",
43
+ headers: { "content-type": "application/x-www-form-urlencoded" },
44
+ body: text("grant=x&secret=%7B%7Bcredential%3Ashop%7D%7D"),
45
+ }), credentials);
46
+ expect(decision).toMatchObject({ kind: "rewrite", used: ["shop"] });
47
+ const body = decision.kind === "rewrite" ? (decision.body ?? "") : "";
48
+ expect(new URLSearchParams(body).get("secret")).toBe('shpat_"quoted"&x=1');
49
+ });
50
+ it.each([
51
+ ["a text body over 10 MB", "text/plain", text(`{{credential:shop}}${"x".repeat(10 * 1024 * 1024)}`)],
52
+ ["a binary body", "application/octet-stream", text("\u0000\u0001{{credential:shop}}")],
53
+ ])("refuses a placeholder in %s with 413", (_kind, contentType, body) => {
54
+ const decision = rewriteRequest(request({ url: "https://acme.myshopify.com/upload", headers: { "content-type": contentType }, body }), credentials);
55
+ expect(decision).toMatchObject({ kind: "reject", status: 413 });
56
+ });
57
+ it("refuses to put a credential on plain http, even to an allowed host", () => {
58
+ const decision = rewriteRequest(request({ url: "http://graph.facebook.com/me", headers: { authorization: "Bearer {{credential:meta-token}}" } }), credentials);
59
+ expect(decision).toMatchObject({ kind: "reject", status: 403 });
60
+ expect(decision.kind === "reject" && decision.message).toContain("only sent over https");
61
+ });
62
+ it("swaps a placeholder hidden inside Basic auth, re-encoding the pair", () => {
63
+ const basic = `Basic ${btoa("admin:{{credential:shop}}")}`;
64
+ const decision = rewriteRequest(request({ url: "https://acme.myshopify.com/admin", headers: { authorization: basic } }), credentials);
65
+ expect(decision).toMatchObject({
66
+ kind: "rewrite",
67
+ headers: { authorization: `Basic ${Buffer.from('admin:shpat_"quoted"&x=1').toString("base64")}` },
68
+ used: ["shop"],
69
+ });
70
+ });
71
+ it.each([
72
+ ["literally", "https://acme.myshopify.com/admin?token={{credential:shop}}"],
73
+ ["percent-encoded", "https://acme.myshopify.com/admin?token=%7B%7Bcredential%3Ashop%7D%7D"],
74
+ ["percent-encoded in lower case", "https://acme.myshopify.com/admin?token=%7b%7bcredential%3ashop%7d%7d"],
75
+ ])("swaps a placeholder written %s in the URL for the URL-encoded value", (_form, url) => {
76
+ const decision = rewriteRequest(request({ url }), credentials);
77
+ expect(decision).toMatchObject({
78
+ kind: "rewrite",
79
+ url: `https://acme.myshopify.com/admin?token=${encodeURIComponent('shpat_"quoted"&x=1')}`,
80
+ used: ["shop"],
81
+ });
82
+ });
83
+ it("leaves HTTP/2 pseudo-headers as they came, since the proxy refuses a changed :path", () => {
84
+ const path = "/admin?token={{credential:shop}}";
85
+ const decision = rewriteRequest(request({ url: `https://acme.myshopify.com${path}`, headers: { ":method": "GET", ":path": path } }), credentials);
86
+ expect(decision).toMatchObject({
87
+ kind: "rewrite",
88
+ url: `https://acme.myshopify.com/admin?token=${encodeURIComponent('shpat_"quoted"&x=1')}`,
89
+ headers: { ":method": "GET", ":path": path },
90
+ });
91
+ });
92
+ it.each([
93
+ ["Host", "host"],
94
+ ["HTTP/2 :authority", ":authority"],
95
+ ])("refuses a credential when the %s header names a different site than the one checked", (_kind, header) => {
96
+ const decision = rewriteRequest(request({ headers: { [header]: "attacker.io", authorization: "Bearer {{credential:meta-token}}" } }), credentials);
97
+ expect(decision).toMatchObject({
98
+ kind: "reject",
99
+ status: 403,
100
+ message: expect.stringContaining("Host header names another website"),
101
+ });
102
+ });
103
+ it("accepts a Host header that names the checked site with a port and in any case", () => {
104
+ const decision = rewriteRequest(request({ headers: { host: "Graph.Facebook.com:443", authorization: "Bearer {{credential:meta-token}}" } }), credentials);
105
+ expect(decision).toMatchObject({ kind: "rewrite", used: ["meta-token"] });
106
+ });
107
+ it("refuses to send a credential to a host it is not allowed for, and says where it may go", () => {
108
+ const decision = rewriteRequest(request({ url: "https://attacker.io/collect", headers: { authorization: "Bearer {{credential:shop}}" } }), credentials);
109
+ expect(decision).toEqual({
110
+ kind: "reject",
111
+ status: 403,
112
+ message: 'Baker credential "shop" is not allowed for host "attacker.io". Allowed: acme.myshopify.com. Ask the user to add this website to the credential in Settings → Credentials.',
113
+ stale: true,
114
+ });
115
+ });
116
+ it("tells the agent to ask for websites when a credential has none yet", () => {
117
+ const decision = rewriteRequest(request({ headers: { "x-key": "{{credential:nowhere}}" } }), credentials);
118
+ expect(decision).toEqual({
119
+ kind: "reject",
120
+ status: 403,
121
+ message: 'Baker credential "nowhere" has no websites it may be sent to yet. Ask the user to choose them in Settings → Credentials.',
122
+ stale: true,
123
+ });
124
+ });
125
+ it.each([
126
+ ["not allowed for the host", "{{credential:shop}}"],
127
+ ["naming no stored credential", "{{credential:typo}}"],
128
+ ])("passes a body-only placeholder %s through untouched, as text", (_case, placeholder) => {
129
+ const decision = rewriteRequest(request({
130
+ url: "https://openrouter.ai/api/v1/chat/completions",
131
+ headers: { "content-type": "application/json" },
132
+ body: text(`{"messages":[{"content":"use ${placeholder}"}]}`),
133
+ }), credentials);
134
+ expect(decision).toEqual({ kind: "pass" });
135
+ });
136
+ it("answers 503 for a placeholder in a header when the credentials could not be fetched", () => {
137
+ const decision = rewriteRequest(request({ headers: { authorization: "Bearer {{credential:meta-token}}" } }), null);
138
+ expect(decision).toEqual({
139
+ kind: "reject",
140
+ status: 503,
141
+ message: "Baker credentials are unavailable right now — try again in a minute.",
142
+ });
143
+ });
144
+ it("passes a body-only placeholder when the credentials could not be fetched", () => {
145
+ const decision = rewriteRequest(request({ headers: { "content-type": "text/plain" }, body: text("{{credential:meta-token}}") }), null);
146
+ expect(decision).toEqual({ kind: "pass" });
147
+ });
148
+ it("answers 404 for a placeholder naming no stored credential", () => {
149
+ const decision = rewriteRequest(request({ headers: { "x-key": "{{credential:typo}}" } }), credentials);
150
+ expect(decision).toEqual({
151
+ kind: "reject",
152
+ status: 404,
153
+ message: 'No Baker credential named "typo". Run `baker credentials list` to see the names you can use.',
154
+ stale: true,
155
+ });
156
+ });
157
+ it("refuses a host whose name another URL parser would read differently", () => {
158
+ // mockttp re-parses the URL it is handed with url.parse(), which cuts a host at `{`, while the
159
+ // check reads the whole name: the credential must never go to a host the check did not see.
160
+ const decision = rewriteRequest(request({
161
+ url: "https://attacker.io{.acme.myshopify.com/admin",
162
+ headers: { authorization: "Bearer {{credential:shop}}" },
163
+ }), credentials);
164
+ expect(decision).toMatchObject({ kind: "reject", status: 403 });
165
+ });
166
+ it("refuses to put a credential into the address of the website itself", () => {
167
+ const decision = rewriteRequest(request({ url: "https://{{credential:shop}}.myshopify.com/admin" }), credentials);
168
+ expect(decision).toMatchObject({ kind: "reject", status: 403 });
169
+ });
170
+ });
171
+ //# sourceMappingURL=rewrite.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rewrite.test.js","sourceRoot":"","sources":["../../src/broker/rewrite.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { CredentialCache } from "./credential-cache.ts";
2
+ export declare const MAX_SCRUB_REQUEST_BYTES: number;
3
+ export type SocketService = {
4
+ listen(): Promise<void>;
5
+ close(): Promise<void>;
6
+ };
7
+ export declare function createSocketService(options: {
8
+ cache: CredentialCache;
9
+ socketPath: string;
10
+ ratePerSecond?: number;
11
+ /** Bytes of text a caller may have scrubbed: slows using /scrub as an oracle for guessing a value. */
12
+ scrubBytes?: {
13
+ capacity: number;
14
+ perSecond: number;
15
+ };
16
+ log?: (message: string) => void;
17
+ }): SocketService;
18
+ //# sourceMappingURL=socket-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-service.d.ts","sourceRoot":"","sources":["../../src/broker/socket-service.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAI7D,eAAO,MAAM,uBAAuB,QAAkB,CAAC;AAmBvD,MAAM,MAAM,aAAa,GAAG;IAAE,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC;AA6BhF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,KAAK,EAAE,eAAe,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sGAAsG;IACtG,UAAU,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,GAAG,aAAa,CAyChB"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The broker's local service for the bridge, over a unix socket the agent user
3
+ * can reach: scrub values out of text, and health. It
4
+ * never returns a value — `/scrub` only ever turns values into placeholders.
5
+ *
6
+ * The agent writes the text it asks to have scrubbed, so a scrub is an answer
7
+ * to "is any of these my password?". `/scrub` therefore leaves alone a value a
8
+ * wordlist could reach (`resistsGuessing`); the proxy still scrubs it from
9
+ * what a website sends back, where the agent does not choose the text.
10
+ */
11
+ import { chmod, mkdir, rm } from "node:fs/promises";
12
+ import { createServer } from "node:http";
13
+ import { dirname } from "node:path";
14
+ import { z } from "zod";
15
+ import { scrub } from "./credential-text.js";
16
+ import { createTokenBucket } from "./token-bucket.js";
17
+ export const MAX_SCRUB_REQUEST_BYTES = 2 * 1024 * 1024;
18
+ const DEFAULT_SCRUB_BYTES = { capacity: 8 * 1024 * 1024, perSecond: 256 * 1024 };
19
+ const scrubRequestSchema = z.object({ text: z.string() });
20
+ /** Bits a value must carry — its length times its per-character entropy — before `/scrub` may confirm it. */
21
+ const MIN_SCRUB_BITS = 64;
22
+ function resistsGuessing(value) {
23
+ const counts = new Map();
24
+ for (const char of value)
25
+ counts.set(char, (counts.get(char) ?? 0) + 1);
26
+ let perChar = 0;
27
+ for (const count of counts.values()) {
28
+ const p = count / value.length;
29
+ perChar -= p * Math.log2(p);
30
+ }
31
+ return perChar * value.length >= MIN_SCRUB_BITS;
32
+ }
33
+ class BodyTooLarge extends Error {
34
+ }
35
+ async function readBody(req, limit) {
36
+ const chunks = [];
37
+ let size = 0;
38
+ for await (const chunk of req) {
39
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
40
+ size += buffer.byteLength;
41
+ if (size > limit)
42
+ throw new BodyTooLarge();
43
+ chunks.push(buffer);
44
+ }
45
+ return Buffer.concat(chunks).toString("utf8");
46
+ }
47
+ function parseJson(raw) {
48
+ try {
49
+ return JSON.parse(raw);
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ function send(res, status, body) {
56
+ res.writeHead(status, { "content-type": "application/json" });
57
+ res.end(JSON.stringify(body));
58
+ }
59
+ export function createSocketService(options) {
60
+ const bucket = createTokenBucket({ ratePerSecond: options.ratePerSecond ?? 50, burst: options.ratePerSecond ?? 50 });
61
+ const scrubBytes = options.scrubBytes ?? DEFAULT_SCRUB_BYTES;
62
+ const bytes = createTokenBucket({ ratePerSecond: scrubBytes.perSecond, burst: scrubBytes.capacity });
63
+ async function handle(req, res) {
64
+ if (req.method === "GET" && req.url === "/health") {
65
+ return send(res, 200, { ok: true, credentials: options.cache.size() });
66
+ }
67
+ if (req.method !== "POST" || req.url !== "/scrub")
68
+ return send(res, 404, { error: "not found" });
69
+ if (!bucket.take())
70
+ return send(res, 429, { error: "too many scrub requests" });
71
+ const raw = await readBody(req, MAX_SCRUB_REQUEST_BYTES);
72
+ if (!bytes.take(Buffer.byteLength(raw)))
73
+ return send(res, 429, { error: "too much text scrubbed, slow down" });
74
+ const parsed = scrubRequestSchema.safeParse(parseJson(raw));
75
+ if (!parsed.success)
76
+ return send(res, 400, { error: "expected {text}" });
77
+ const credentials = (await options.cache.scrubbable()).filter((credential) => resistsGuessing(credential.value));
78
+ return send(res, 200, { text: scrub(parsed.data.text, credentials) });
79
+ }
80
+ const server = createServer((req, res) => {
81
+ handle(req, res).catch((error) => {
82
+ if (error instanceof BodyTooLarge)
83
+ return send(res, 413, { error: "text too large to scrub" });
84
+ options.log?.(`socket request failed: ${error instanceof Error ? error.name : "unknown"}`);
85
+ send(res, 500, { error: "scrub failed" });
86
+ });
87
+ });
88
+ return {
89
+ async listen() {
90
+ await mkdir(dirname(options.socketPath), { recursive: true });
91
+ await rm(options.socketPath, { force: true });
92
+ await new Promise((resolve, reject) => {
93
+ server.once("error", reject);
94
+ server.listen(options.socketPath, () => resolve());
95
+ });
96
+ await chmod(options.socketPath, 0o666);
97
+ },
98
+ async close() {
99
+ await new Promise((resolve) => server.close(() => resolve()));
100
+ },
101
+ };
102
+ }
103
+ //# sourceMappingURL=socket-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-service.js","sourceRoot":"","sources":["../../src/broker/socket-service.ts"],"names":[],"mappings":"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"}