@openlimiter/core 1.2.0 → 1.3.1
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/dist/acquire/antigravity-probe.d.ts +139 -0
- package/dist/acquire/antigravity-probe.d.ts.map +1 -0
- package/dist/acquire/antigravity-probe.js +599 -0
- package/dist/acquire/antigravity-probe.js.map +1 -0
- package/dist/acquire/cadence.d.ts +74 -0
- package/dist/acquire/cadence.d.ts.map +1 -0
- package/dist/acquire/cadence.js +171 -0
- package/dist/acquire/cadence.js.map +1 -0
- package/dist/acquire/coordination.d.ts +157 -0
- package/dist/acquire/coordination.d.ts.map +1 -0
- package/dist/acquire/coordination.js +292 -0
- package/dist/acquire/coordination.js.map +1 -0
- package/dist/acquire/credentials.d.ts +113 -0
- package/dist/acquire/credentials.d.ts.map +1 -0
- package/dist/acquire/credentials.js +316 -0
- package/dist/acquire/credentials.js.map +1 -0
- package/dist/acquire/identity.d.ts +20 -0
- package/dist/acquire/identity.d.ts.map +1 -0
- package/dist/acquire/identity.js +20 -0
- package/dist/acquire/identity.js.map +1 -0
- package/dist/acquire/index.d.ts +10 -0
- package/dist/acquire/index.d.ts.map +1 -0
- package/dist/acquire/index.js +10 -0
- package/dist/acquire/index.js.map +1 -0
- package/dist/acquire/providers.d.ts +86 -0
- package/dist/acquire/providers.d.ts.map +1 -0
- package/dist/acquire/providers.js +175 -0
- package/dist/acquire/providers.js.map +1 -0
- package/dist/acquire/runner.d.ts +146 -0
- package/dist/acquire/runner.d.ts.map +1 -0
- package/dist/acquire/runner.js +345 -0
- package/dist/acquire/runner.js.map +1 -0
- package/dist/acquire/transport.d.ts +194 -0
- package/dist/acquire/transport.d.ts.map +1 -0
- package/dist/acquire/transport.js +434 -0
- package/dist/acquire/transport.js.map +1 -0
- package/dist/acquire/windows-credential.d.ts +74 -0
- package/dist/acquire/windows-credential.d.ts.map +1 -0
- package/dist/acquire/windows-credential.js +124 -0
- package/dist/acquire/windows-credential.js.map +1 -0
- package/dist/cache.d.ts +28 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +81 -2
- package/dist/cache.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/normalizer.d.ts +2 -0
- package/dist/normalizer.d.ts.map +1 -1
- package/dist/normalizer.js +39 -2
- package/dist/normalizer.js.map +1 -1
- package/dist/types.d.ts +36 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The network layer of the Node acquisition path, closed the same way the
|
|
3
|
+
* desktop's is.
|
|
4
|
+
*
|
|
5
|
+
* There is no function here that fetches a caller supplied URL. There is a
|
|
6
|
+
* closed list of endpoints, each carrying one constant address and one constant
|
|
7
|
+
* method, and a transport that is handed a request built from that list. The
|
|
8
|
+
* whole reachable internet is the table below, and nothing read off disk or out
|
|
9
|
+
* of a provider response can widen it.
|
|
10
|
+
*
|
|
11
|
+
* Failures are a closed vocabulary with no payload, so a header, a body or a
|
|
12
|
+
* token cannot end up inside an error string no matter who formats it. A
|
|
13
|
+
* response outside the success range has its body dropped unread.
|
|
14
|
+
*/
|
|
15
|
+
import { OPENLIMITER_USER_AGENT } from "./identity.js";
|
|
16
|
+
/** One request's total budget, connect to last body byte. */
|
|
17
|
+
export const ACQUISITION_TIMEOUT_MILLISECONDS = 15_000;
|
|
18
|
+
/** Largest response body accepted, the bound every state file already uses. */
|
|
19
|
+
export const MAX_ACQUISITION_RESPONSE_BYTES = 1_048_576;
|
|
20
|
+
/* ------------------------------------------------------------- addresses */
|
|
21
|
+
/** The Claude account usage report, read with Claude Code's own OAuth token. */
|
|
22
|
+
export const CLAUDE_OAUTH_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
23
|
+
/** The Codex usage report, read with the session the Codex CLI holds. */
|
|
24
|
+
export const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
25
|
+
/** Gemini CLI's Code Assist account bootstrap, which names the project. */
|
|
26
|
+
export const GEMINI_CLI_LOAD_URL = "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist";
|
|
27
|
+
/** Gemini CLI's per model quota report. */
|
|
28
|
+
export const GEMINI_CLI_QUOTA_URL = "https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota";
|
|
29
|
+
/** The Grok Build billing report the official Grok CLI reads. */
|
|
30
|
+
export const GROK_USAGE_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
|
|
31
|
+
/** The Kimi Code usage report the official Kimi CLI reads. */
|
|
32
|
+
export const KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
|
|
33
|
+
/** The OpenRouter inference key report: limit, remaining and usage. */
|
|
34
|
+
export const OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
|
|
35
|
+
/**
|
|
36
|
+
* Every address this path may speak to.
|
|
37
|
+
*
|
|
38
|
+
* Adding a provider means adding an entry here, in code, in review. Antigravity
|
|
39
|
+
* has no entry of its own: it reads the same Code Assist pair as Gemini CLI
|
|
40
|
+
* with the credential the Antigravity client stored, which is why this table is
|
|
41
|
+
* shorter than the provider list.
|
|
42
|
+
*/
|
|
43
|
+
export const ACQUISITION_ENDPOINTS = {
|
|
44
|
+
claude_usage: { url: CLAUDE_OAUTH_USAGE_URL, method: "GET" },
|
|
45
|
+
codex_usage: { url: CODEX_USAGE_URL, method: "GET" },
|
|
46
|
+
code_assist_load: { url: GEMINI_CLI_LOAD_URL, method: "POST" },
|
|
47
|
+
code_assist_quota: { url: GEMINI_CLI_QUOTA_URL, method: "POST" },
|
|
48
|
+
grok_billing: { url: GROK_USAGE_URL, method: "GET" },
|
|
49
|
+
kimi_usage: { url: KIMI_USAGE_URL, method: "GET" },
|
|
50
|
+
openrouter_key: { url: OPENROUTER_KEY_URL, method: "GET" }
|
|
51
|
+
};
|
|
52
|
+
/* ---------------------------------------------------------------- headers */
|
|
53
|
+
/** The beta contract the OAuth usage route answers. */
|
|
54
|
+
export const CLAUDE_OAUTH_BETA_HEADER = "anthropic-beta";
|
|
55
|
+
export const CLAUDE_OAUTH_BETA_VALUE = "oauth-2025-04-20";
|
|
56
|
+
/** The account header the ChatGPT backend reads beside the bearer token. */
|
|
57
|
+
export const CODEX_ACCOUNT_HEADER = "chatgpt-account-id";
|
|
58
|
+
/** The account identity the Grok billing service reads beside the token. */
|
|
59
|
+
export const GROK_ACCOUNT_HEADER = "x-userid";
|
|
60
|
+
/**
|
|
61
|
+
* The Code Assist bootstrap body, the same metadata the maintained client
|
|
62
|
+
* sends for an account that is already onboarded. Undefined project fields are
|
|
63
|
+
* omitted exactly as that client omits them.
|
|
64
|
+
*/
|
|
65
|
+
export const CODE_ASSIST_LOAD_BODY = JSON.stringify({
|
|
66
|
+
metadata: {
|
|
67
|
+
ideType: "IDE_UNSPECIFIED",
|
|
68
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
69
|
+
pluginType: "GEMINI"
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* Whether a secret may be written into a header at all.
|
|
74
|
+
*
|
|
75
|
+
* The value is read off disk from a file this process does not own. A control
|
|
76
|
+
* character in it would let a hostile credential file inject a second header,
|
|
77
|
+
* so anything that is not bounded printable ASCII is refused before it can
|
|
78
|
+
* reach a request. Nothing about the refusal names the value.
|
|
79
|
+
*/
|
|
80
|
+
export function usableHeaderSecret(value) {
|
|
81
|
+
return value.length > 0 &&
|
|
82
|
+
value.length <= 8_192 &&
|
|
83
|
+
/^[\x20-\x7E]+$/u.test(value);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Whether an account identifier may be written into a header.
|
|
87
|
+
*
|
|
88
|
+
* Narrower than a secret, because this one is an opaque identifier rather than
|
|
89
|
+
* a token: letters, digits and the three separators the observed identifiers
|
|
90
|
+
* use, and nothing else.
|
|
91
|
+
*/
|
|
92
|
+
export function usableHeaderAccountId(value) {
|
|
93
|
+
return /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/u.test(value);
|
|
94
|
+
}
|
|
95
|
+
function bearer(secret) {
|
|
96
|
+
return {
|
|
97
|
+
authorization: "Bearer " + secret,
|
|
98
|
+
"user-agent": OPENLIMITER_USER_AGENT,
|
|
99
|
+
accept: "application/json"
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** The Claude usage request. Our identity, and the documented beta contract. */
|
|
103
|
+
export function claudeUsageRequest(secret) {
|
|
104
|
+
if (!usableHeaderSecret(secret))
|
|
105
|
+
return null;
|
|
106
|
+
return {
|
|
107
|
+
endpoint: "claude_usage",
|
|
108
|
+
url: ACQUISITION_ENDPOINTS.claude_usage.url,
|
|
109
|
+
method: "GET",
|
|
110
|
+
headers: { ...bearer(secret), [CLAUDE_OAUTH_BETA_HEADER]: CLAUDE_OAUTH_BETA_VALUE },
|
|
111
|
+
body: null
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** The Codex usage request, with the account header the backend requires. */
|
|
115
|
+
export function codexUsageRequest(secret, accountId) {
|
|
116
|
+
if (!usableHeaderSecret(secret) || !usableHeaderAccountId(accountId))
|
|
117
|
+
return null;
|
|
118
|
+
return {
|
|
119
|
+
endpoint: "codex_usage",
|
|
120
|
+
url: ACQUISITION_ENDPOINTS.codex_usage.url,
|
|
121
|
+
method: "GET",
|
|
122
|
+
headers: { ...bearer(secret), [CODEX_ACCOUNT_HEADER]: accountId },
|
|
123
|
+
body: null
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** The Code Assist bootstrap, which answers with the companion project. */
|
|
127
|
+
export function codeAssistLoadRequest(secret) {
|
|
128
|
+
if (!usableHeaderSecret(secret))
|
|
129
|
+
return null;
|
|
130
|
+
return {
|
|
131
|
+
endpoint: "code_assist_load",
|
|
132
|
+
url: ACQUISITION_ENDPOINTS.code_assist_load.url,
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: { ...bearer(secret), "content-type": "application/json" },
|
|
135
|
+
body: CODE_ASSIST_LOAD_BODY
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Shape of a companion project identifier, and the reason there is one.
|
|
140
|
+
*
|
|
141
|
+
* The value arrives in a provider response body and is then written into the
|
|
142
|
+
* body of our next request. That is the one place a provider gets to influence
|
|
143
|
+
* what we send, so it is held to an opaque identifier and nothing else.
|
|
144
|
+
*/
|
|
145
|
+
export const CODE_ASSIST_PROJECT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/u;
|
|
146
|
+
/** The per model quota request, scoped to a validated companion project. */
|
|
147
|
+
export function codeAssistQuotaRequest(secret, project) {
|
|
148
|
+
if (!usableHeaderSecret(secret))
|
|
149
|
+
return null;
|
|
150
|
+
if (!CODE_ASSIST_PROJECT_PATTERN.test(project))
|
|
151
|
+
return null;
|
|
152
|
+
return {
|
|
153
|
+
endpoint: "code_assist_quota",
|
|
154
|
+
url: ACQUISITION_ENDPOINTS.code_assist_quota.url,
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: { ...bearer(secret), "content-type": "application/json" },
|
|
157
|
+
body: JSON.stringify({ project, userAgent: OPENLIMITER_USER_AGENT })
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The Grok billing request.
|
|
162
|
+
*
|
|
163
|
+
* The account header is an identifier for the person's own account and is sent
|
|
164
|
+
* when the credential names one. Nothing else is: the desktop sends xAI's
|
|
165
|
+
* `x-xai-token-auth: xai-grok-cli` marker beside it, and that value NAMES THE
|
|
166
|
+
* VENDOR'S OWN TOOL, which is the same claim a copied user agent makes. Rule 1
|
|
167
|
+
* says we identify as OpenLimiter and take whatever that costs us, so the
|
|
168
|
+
* marker is not sent here and no client version is invented either.
|
|
169
|
+
*
|
|
170
|
+
* The consequence is honestly unknown rather than assumed: no Grok login exists
|
|
171
|
+
* on the machine this was written on, so whether the billing route answers a
|
|
172
|
+
* request that does not claim to be the Grok CLI is verified on the first
|
|
173
|
+
* install that has one, and the row says so until then.
|
|
174
|
+
*/
|
|
175
|
+
export function grokBillingRequest(secret, accountId) {
|
|
176
|
+
if (!usableHeaderSecret(secret))
|
|
177
|
+
return null;
|
|
178
|
+
const account = accountId !== null && usableHeaderAccountId(accountId)
|
|
179
|
+
? accountId
|
|
180
|
+
: null;
|
|
181
|
+
return {
|
|
182
|
+
endpoint: "grok_billing",
|
|
183
|
+
url: ACQUISITION_ENDPOINTS.grok_billing.url,
|
|
184
|
+
method: "GET",
|
|
185
|
+
headers: {
|
|
186
|
+
...bearer(secret),
|
|
187
|
+
...(account === null ? {} : { [GROK_ACCOUNT_HEADER]: account })
|
|
188
|
+
},
|
|
189
|
+
body: null
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/** The Kimi Code usage request. */
|
|
193
|
+
export function kimiUsageRequest(secret) {
|
|
194
|
+
if (!usableHeaderSecret(secret))
|
|
195
|
+
return null;
|
|
196
|
+
return {
|
|
197
|
+
endpoint: "kimi_usage",
|
|
198
|
+
url: ACQUISITION_ENDPOINTS.kimi_usage.url,
|
|
199
|
+
method: "GET",
|
|
200
|
+
headers: bearer(secret),
|
|
201
|
+
body: null
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** The OpenRouter key report, the one documented interface in this table. */
|
|
205
|
+
export function openrouterKeyRequest(secret) {
|
|
206
|
+
if (!usableHeaderSecret(secret))
|
|
207
|
+
return null;
|
|
208
|
+
return {
|
|
209
|
+
endpoint: "openrouter_key",
|
|
210
|
+
url: ACQUISITION_ENDPOINTS.openrouter_key.url,
|
|
211
|
+
method: "GET",
|
|
212
|
+
headers: bearer(secret),
|
|
213
|
+
body: null
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/* -------------------------------------------------------------- outcomes */
|
|
217
|
+
/**
|
|
218
|
+
* What one request achieved, in words this product owns.
|
|
219
|
+
*
|
|
220
|
+
* Every value here is a decision about what to do next, never a description of
|
|
221
|
+
* a response body. `blocked` and `rate_limited` are separated because they earn
|
|
222
|
+
* different backoffs, and `drift` is separated from `remote_error` because only
|
|
223
|
+
* drift says the meaning of a stored reading is in doubt.
|
|
224
|
+
*
|
|
225
|
+
* `identity_refused` is the one that cost the most to learn. A provider can
|
|
226
|
+
* answer 200 to an honest client and still leave out the field the next request
|
|
227
|
+
* needs, which reads exactly like drift and is not: nothing changed shape, the
|
|
228
|
+
* provider simply serves that field to its own tools. Measured on 2026-09-07
|
|
229
|
+
* against Code Assist, which answered 200 with tier information and no
|
|
230
|
+
* companion project. Calling it drift would have had this build retry every
|
|
231
|
+
* fifteen minutes forever against an answer that will never differ.
|
|
232
|
+
*/
|
|
233
|
+
export const ACQUISITION_OUTCOMES = [
|
|
234
|
+
"ok",
|
|
235
|
+
"unauthorized",
|
|
236
|
+
"rate_limited",
|
|
237
|
+
"blocked",
|
|
238
|
+
"remote_error",
|
|
239
|
+
"transport",
|
|
240
|
+
"too_large",
|
|
241
|
+
"drift",
|
|
242
|
+
"identity_refused"
|
|
243
|
+
];
|
|
244
|
+
/**
|
|
245
|
+
* Turn a status into a decision.
|
|
246
|
+
*
|
|
247
|
+
* 401 and 403 are told apart on purpose. A lapsed token is a person's next
|
|
248
|
+
* step, and a refusal to serve this client at all is a reason to stay away for
|
|
249
|
+
* a day. Everything from 500 up is the provider having a bad minute and is
|
|
250
|
+
* retried on the ordinary interval.
|
|
251
|
+
*/
|
|
252
|
+
export function outcomeForStatus(status) {
|
|
253
|
+
if (status >= 200 && status < 300)
|
|
254
|
+
return "ok";
|
|
255
|
+
if (status === 401)
|
|
256
|
+
return "unauthorized";
|
|
257
|
+
if (status === 403)
|
|
258
|
+
return "blocked";
|
|
259
|
+
if (status === 429)
|
|
260
|
+
return "rate_limited";
|
|
261
|
+
return "remote_error";
|
|
262
|
+
}
|
|
263
|
+
/** Retry-After in seconds, when the header is a count of seconds we believe. */
|
|
264
|
+
export function retryAfterSeconds(value) {
|
|
265
|
+
if (value === null)
|
|
266
|
+
return null;
|
|
267
|
+
const trimmed = value.trim();
|
|
268
|
+
if (!/^\d{1,7}$/u.test(trimmed))
|
|
269
|
+
return null;
|
|
270
|
+
const seconds = Number.parseInt(trimmed, 10);
|
|
271
|
+
return Number.isFinite(seconds) && seconds > 0 ? seconds : null;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* The header names an acquisition request may carry.
|
|
275
|
+
*
|
|
276
|
+
* Closed, and checked at the boundary below rather than trusted from the
|
|
277
|
+
* builders above. The builders are the only callers today, but the transport is
|
|
278
|
+
* exported and a future caller that assembled a request by hand would otherwise
|
|
279
|
+
* be able to put anything on the wire under this product's name.
|
|
280
|
+
*/
|
|
281
|
+
export const ALLOWED_REQUEST_HEADERS = [
|
|
282
|
+
"authorization",
|
|
283
|
+
"accept",
|
|
284
|
+
"user-agent",
|
|
285
|
+
"content-type",
|
|
286
|
+
CLAUDE_OAUTH_BETA_HEADER,
|
|
287
|
+
CODEX_ACCOUNT_HEADER,
|
|
288
|
+
GROK_ACCOUNT_HEADER
|
|
289
|
+
];
|
|
290
|
+
/**
|
|
291
|
+
* Whether one header's VALUE is the value that header is allowed to hold.
|
|
292
|
+
*
|
|
293
|
+
* Checking names alone was half a check. A name allowlist stops a cookie going
|
|
294
|
+
* out; it does nothing about an authorization header carrying something that is
|
|
295
|
+
* not a bearer token, or a beta header carrying a contract we never agreed to,
|
|
296
|
+
* or an account header carrying a path. Each of these has exactly one shape and
|
|
297
|
+
* this is where each one is held to it.
|
|
298
|
+
*/
|
|
299
|
+
function validHeaderValue(name, value, method) {
|
|
300
|
+
if (!/^[\x20-\x7E]+$/u.test(value))
|
|
301
|
+
return false;
|
|
302
|
+
if (name === "user-agent")
|
|
303
|
+
return value === OPENLIMITER_USER_AGENT;
|
|
304
|
+
if (name === "accept")
|
|
305
|
+
return value === "application/json";
|
|
306
|
+
if (name === "content-type") {
|
|
307
|
+
return value === "application/json" && method === "POST";
|
|
308
|
+
}
|
|
309
|
+
if (name === "authorization") {
|
|
310
|
+
return value.startsWith("Bearer ") && usableHeaderSecret(value.slice(7));
|
|
311
|
+
}
|
|
312
|
+
if (name === CLAUDE_OAUTH_BETA_HEADER)
|
|
313
|
+
return value === CLAUDE_OAUTH_BETA_VALUE;
|
|
314
|
+
if (name === CODEX_ACCOUNT_HEADER || name === GROK_ACCOUNT_HEADER) {
|
|
315
|
+
return usableHeaderAccountId(value);
|
|
316
|
+
}
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Whether a request body is the body that endpoint is allowed to carry.
|
|
321
|
+
*
|
|
322
|
+
* Two endpoints take a body and both bodies have a fixed shape. The bootstrap's
|
|
323
|
+
* is a constant. The quota read's carries the one field a provider response
|
|
324
|
+
* ever influences, which is exactly why it is checked here rather than trusted
|
|
325
|
+
* from the builder: this is the last point before the wire, and the last chance
|
|
326
|
+
* to notice that a project identifier grew into something else.
|
|
327
|
+
*/
|
|
328
|
+
function validRequestBody(request) {
|
|
329
|
+
if (request.endpoint === "code_assist_load") {
|
|
330
|
+
return request.body === CODE_ASSIST_LOAD_BODY;
|
|
331
|
+
}
|
|
332
|
+
if (request.endpoint === "code_assist_quota") {
|
|
333
|
+
if (request.body === null)
|
|
334
|
+
return false;
|
|
335
|
+
let parsed;
|
|
336
|
+
try {
|
|
337
|
+
parsed = JSON.parse(request.body);
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
const fields = parsed;
|
|
346
|
+
const keys = Object.keys(fields).sort();
|
|
347
|
+
if (keys.length !== 2 || keys[0] !== "project" || keys[1] !== "userAgent") {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
const project = fields["project"];
|
|
351
|
+
return typeof project === "string" &&
|
|
352
|
+
CODE_ASSIST_PROJECT_PATTERN.test(project) &&
|
|
353
|
+
fields["userAgent"] === OPENLIMITER_USER_AGENT;
|
|
354
|
+
}
|
|
355
|
+
/* Every other endpoint is a GET that asks for something and sends nothing. */
|
|
356
|
+
return request.body === null && request.method === "GET";
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Whether a request may leave this process.
|
|
360
|
+
*
|
|
361
|
+
* Address, method, every header name, every header VALUE and the body, all
|
|
362
|
+
* answered against constants in this file. A request that fails any of them
|
|
363
|
+
* never reaches the network.
|
|
364
|
+
*/
|
|
365
|
+
export function validAcquisitionRequest(request) {
|
|
366
|
+
const endpoint = ACQUISITION_ENDPOINTS[request.endpoint];
|
|
367
|
+
if (endpoint === undefined)
|
|
368
|
+
return false;
|
|
369
|
+
if (request.url !== endpoint.url || request.method !== endpoint.method)
|
|
370
|
+
return false;
|
|
371
|
+
if (request.headers["user-agent"] !== OPENLIMITER_USER_AGENT)
|
|
372
|
+
return false;
|
|
373
|
+
for (const name of Object.keys(request.headers)) {
|
|
374
|
+
if (!ALLOWED_REQUEST_HEADERS.includes(name))
|
|
375
|
+
return false;
|
|
376
|
+
const value = request.headers[name];
|
|
377
|
+
if (value === undefined)
|
|
378
|
+
return false;
|
|
379
|
+
if (!validHeaderValue(name, value, request.method))
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
return validRequestBody(request);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* The transport the product uses, built on the runtime's own fetch.
|
|
386
|
+
*
|
|
387
|
+
* Redirects are refused rather than followed: a redirect on any of these
|
|
388
|
+
* addresses would carry the bearer token somewhere the closed table above never
|
|
389
|
+
* named. The body is bounded before it is decoded, and the whole request lives
|
|
390
|
+
* under one deadline. A transport level failure is an exception, which the
|
|
391
|
+
* runner turns into the `transport` outcome without ever formatting it.
|
|
392
|
+
*/
|
|
393
|
+
export function createFetchTransport(fetchImplementation = globalThis.fetch) {
|
|
394
|
+
return async (request) => {
|
|
395
|
+
/* Payload free on purpose. The runner turns this into the `transport`
|
|
396
|
+
outcome, and an error carrying a URL or a header would be the one place
|
|
397
|
+
a credential could reach a log. */
|
|
398
|
+
if (!validAcquisitionRequest(request)) {
|
|
399
|
+
throw new Error("Refused a request outside the acquisition contract");
|
|
400
|
+
}
|
|
401
|
+
const controller = new AbortController();
|
|
402
|
+
const timer = setTimeout(() => controller.abort(), ACQUISITION_TIMEOUT_MILLISECONDS);
|
|
403
|
+
try {
|
|
404
|
+
const response = await fetchImplementation(request.url, {
|
|
405
|
+
method: request.method,
|
|
406
|
+
headers: { ...request.headers },
|
|
407
|
+
redirect: "error",
|
|
408
|
+
signal: controller.signal,
|
|
409
|
+
...(request.body === null ? {} : { body: request.body })
|
|
410
|
+
});
|
|
411
|
+
const retryAfter = retryAfterSeconds(response.headers.get("retry-after"));
|
|
412
|
+
if (response.status < 200 || response.status >= 300) {
|
|
413
|
+
/* The body of a refusal is never read. It cannot help, and reading it
|
|
414
|
+
is how a provider's prose reaches a log. */
|
|
415
|
+
return { status: response.status, body: "", retryAfterSeconds: retryAfter };
|
|
416
|
+
}
|
|
417
|
+
const buffer = await response.arrayBuffer();
|
|
418
|
+
if (buffer.byteLength > MAX_ACQUISITION_RESPONSE_BYTES) {
|
|
419
|
+
/* Status zero is this layer's way of saying the answer was refused
|
|
420
|
+
before it was read, which the runner reads as too large. */
|
|
421
|
+
return { status: 0, body: "", retryAfterSeconds: retryAfter };
|
|
422
|
+
}
|
|
423
|
+
return {
|
|
424
|
+
status: response.status,
|
|
425
|
+
body: new TextDecoder("utf-8", { fatal: false }).decode(buffer),
|
|
426
|
+
retryAfterSeconds: retryAfter
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
finally {
|
|
430
|
+
clearTimeout(timer);
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/acquire/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAEvD,6DAA6D;AAC7D,MAAM,CAAC,MAAM,gCAAgC,GAAG,MAAM,CAAC;AAEvD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,8BAA8B,GAAG,SAAS,CAAC;AAExD,6EAA6E;AAE7E,gFAAgF;AAChF,MAAM,CAAC,MAAM,sBAAsB,GAAG,2CAA2C,CAAC;AAElF,yEAAyE;AACzE,MAAM,CAAC,MAAM,eAAe,GAAG,4CAA4C,CAAC;AAE5E,2EAA2E;AAC3E,MAAM,CAAC,MAAM,mBAAmB,GAC9B,+DAA+D,CAAC;AAElE,2CAA2C;AAC3C,MAAM,CAAC,MAAM,oBAAoB,GAC/B,kEAAkE,CAAC;AAErE,iEAAiE;AACjE,MAAM,CAAC,MAAM,cAAc,GAAG,2DAA2D,CAAC;AAE1F,8DAA8D;AAC9D,MAAM,CAAC,MAAM,cAAc,GAAG,uCAAuC,CAAC;AAEtE,uEAAuE;AACvE,MAAM,CAAC,MAAM,kBAAkB,GAAG,kCAAkC,CAAC;AAErE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,YAAY,EAAE,EAAE,GAAG,EAAE,sBAAsB,EAAE,MAAM,EAAE,KAAK,EAAE;IAC5D,WAAW,EAAE,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,KAAK,EAAE;IACpD,gBAAgB,EAAE,EAAE,GAAG,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,EAAE;IAC9D,iBAAiB,EAAE,EAAE,GAAG,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,EAAE;IAChE,YAAY,EAAE,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE;IACpD,UAAU,EAAE,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE;IAClD,cAAc,EAAE,EAAE,GAAG,EAAE,kBAAkB,EAAE,MAAM,EAAE,KAAK,EAAE;CAC0B,CAAC;AAIvF,8EAA8E;AAE9E,uDAAuD;AACvD,MAAM,CAAC,MAAM,wBAAwB,GAAG,gBAAgB,CAAC;AACzD,MAAM,CAAC,MAAM,uBAAuB,GAAG,kBAAkB,CAAC;AAE1D,4EAA4E;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;AAEzD,4EAA4E;AAC5E,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC;AAE9C;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC,SAAS,CAAC;IAClD,QAAQ,EAAE;QACR,OAAO,EAAE,iBAAiB;QAC1B,QAAQ,EAAE,sBAAsB;QAChC,UAAU,EAAE,QAAQ;KACrB;CACF,CAAC,CAAC;AAUH;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC;QACrB,KAAK,CAAC,MAAM,IAAI,KAAK;QACrB,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,OAAO,sCAAsC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,MAAM,CAAC,MAAc;IAC5B,OAAO;QACL,aAAa,EAAE,SAAS,GAAG,MAAM;QACjC,YAAY,EAAE,sBAAsB;QACpC,MAAM,EAAE,kBAAkB;KAC3B,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO;QACL,QAAQ,EAAE,cAAc;QACxB,GAAG,EAAE,qBAAqB,CAAC,YAAY,CAAC,GAAG;QAC3C,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,wBAAwB,CAAC,EAAE,uBAAuB,EAAE;QACnF,IAAI,EAAE,IAAI;KACX,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,iBAAiB,CAC/B,MAAc,EACd,SAAiB;IAEjB,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAClF,OAAO;QACL,QAAQ,EAAE,aAAa;QACvB,GAAG,EAAE,qBAAqB,CAAC,WAAW,CAAC,GAAG;QAC1C,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,oBAAoB,CAAC,EAAE,SAAS,EAAE;QACjE,IAAI,EAAE,IAAI;KACX,CAAC;AACJ,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,qBAAqB,CAAC,MAAc;IAClD,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO;QACL,QAAQ,EAAE,kBAAkB;QAC5B,GAAG,EAAE,qBAAqB,CAAC,gBAAgB,CAAC,GAAG;QAC/C,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAClE,IAAI,EAAE,qBAAqB;KAC5B,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,sCAAsC,CAAC;AAElF,4EAA4E;AAC5E,MAAM,UAAU,sBAAsB,CACpC,MAAc,EACd,OAAe;IAEf,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,IAAI,CAAC,2BAA2B,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,OAAO;QACL,QAAQ,EAAE,mBAAmB;QAC7B,GAAG,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,GAAG;QAChD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAClE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,sBAAsB,EAAE,CAAC;KACrE,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAc,EACd,SAAwB;IAExB,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,OAAO,GAAG,SAAS,KAAK,IAAI,IAAI,qBAAqB,CAAC,SAAS,CAAC;QACpE,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,IAAI,CAAC;IACT,OAAO;QACL,QAAQ,EAAE,cAAc;QACxB,GAAG,EAAE,qBAAqB,CAAC,YAAY,CAAC,GAAG;QAC3C,MAAM,EAAE,KAAK;QACb,OAAO,EAAE;YACP,GAAG,MAAM,CAAC,MAAM,CAAC;YACjB,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,mBAAmB,CAAC,EAAE,OAAO,EAAE,CAAC;SAChE;QACD,IAAI,EAAE,IAAI;KACX,CAAC;AACJ,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO;QACL,QAAQ,EAAE,YAAY;QACtB,GAAG,EAAE,qBAAqB,CAAC,UAAU,CAAC,GAAG;QACzC,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC;QACvB,IAAI,EAAE,IAAI;KACX,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,oBAAoB,CAAC,MAAc;IACjD,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO;QACL,QAAQ,EAAE,gBAAgB;QAC1B,GAAG,EAAE,qBAAqB,CAAC,cAAc,CAAC,GAAG;QAC7C,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC;QACvB,IAAI,EAAE,IAAI;KACX,CAAC;AACJ,CAAC;AAED,6EAA6E;AAE7E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,IAAI;IACJ,cAAc;IACd,cAAc;IACd,SAAS;IACT,cAAc;IACd,WAAW;IACX,WAAW;IACX,OAAO;IACP,kBAAkB;CACV,CAAC;AAeX;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG;QAAE,OAAO,IAAI,CAAC;IAC/C,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,cAAc,CAAC;IAC1C,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,SAAS,CAAC;IACrC,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,cAAc,CAAC;IAC1C,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,iBAAiB,CAAC,KAAoB;IACpD,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC7C,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAClE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAsB;IACxD,eAAe;IACf,QAAQ;IACR,YAAY;IACZ,cAAc;IACd,wBAAwB;IACxB,oBAAoB;IACpB,mBAAmB;CACpB,CAAC;AAEF;;;;;;;;GAQG;AACH,SAAS,gBAAgB,CAAC,IAAY,EAAE,KAAa,EAAE,MAAc;IACnE,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACjD,IAAI,IAAI,KAAK,YAAY;QAAE,OAAO,KAAK,KAAK,sBAAsB,CAAC;IACnE,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,KAAK,kBAAkB,CAAC;IAC3D,IAAI,IAAI,KAAK,cAAc,EAAE,CAAC;QAC5B,OAAO,KAAK,KAAK,kBAAkB,IAAI,MAAM,KAAK,MAAM,CAAC;IAC3D,CAAC;IACD,IAAI,IAAI,KAAK,eAAe,EAAE,CAAC;QAC7B,OAAO,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,kBAAkB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,IAAI,KAAK,wBAAwB;QAAE,OAAO,KAAK,KAAK,uBAAuB,CAAC;IAChF,IAAI,IAAI,KAAK,oBAAoB,IAAI,IAAI,KAAK,mBAAmB,EAAE,CAAC;QAClE,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,gBAAgB,CAAC,OAA2B;IACnD,IAAI,OAAO,CAAC,QAAQ,KAAK,kBAAkB,EAAE,CAAC;QAC5C,OAAO,OAAO,CAAC,IAAI,KAAK,qBAAqB,CAAC;IAChD,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,KAAK,mBAAmB,EAAE,CAAC;QAC7C,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QACxC,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAY,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3E,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,MAAM,GAAG,MAAiC,CAAC;QACjD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,WAAW,EAAE,CAAC;YAC1E,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QAClC,OAAO,OAAO,OAAO,KAAK,QAAQ;YAChC,2BAA2B,CAAC,IAAI,CAAC,OAAO,CAAC;YACzC,MAAM,CAAC,WAAW,CAAC,KAAK,sBAAsB,CAAC;IACnD,CAAC;IACD,8EAA8E;IAC9E,OAAO,OAAO,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC;AAC3D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAA2B;IACjE,MAAM,QAAQ,GAAG,qBAAqB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzD,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACzC,IAAI,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,GAAG,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACrF,IAAI,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,sBAAsB;QAAE,OAAO,KAAK,CAAC;IAC3E,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAChD,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAC1D,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QACtC,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;IACnE,CAAC;IACD,OAAO,gBAAgB,CAAC,OAAO,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAClC,sBAA+C,UAAU,CAAC,KAAK;IAE/D,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE;QACvB;;6CAEqC;QACrC,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACxE,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CACtB,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EACxB,gCAAgC,CACjC,CAAC;QACF,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,GAAG,EAAE;gBACtD,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,OAAO,EAAE,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE;gBAC/B,QAAQ,EAAE,OAAO;gBACjB,MAAM,EAAE,UAAU,CAAC,MAAM;gBACzB,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;aACzD,CAAC,CAAC;YACH,MAAM,UAAU,GAAG,iBAAiB,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC;YAC1E,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;gBACpD;8DAC8C;gBAC9C,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,iBAAiB,EAAE,UAAU,EAAE,CAAC;YAC9E,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC5C,IAAI,MAAM,CAAC,UAAU,GAAG,8BAA8B,EAAE,CAAC;gBACvD;8EAC8D;gBAC9D,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,iBAAiB,EAAE,UAAU,EAAE,CAAC;YAChE,CAAC;YACD,OAAO;gBACL,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,IAAI,EAAE,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;gBAC/D,iBAAiB,EAAE,UAAU;aAC9B,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading one Windows Credential Manager entry, with no native dependency.
|
|
3
|
+
*
|
|
4
|
+
* The Antigravity client keeps its token in the credential store rather than in
|
|
5
|
+
* a file, so a Windows machine has nothing on disk to read. Every published way
|
|
6
|
+
* to reach the store from Node is a native module with an install script, which
|
|
7
|
+
* is a compiler on a user's machine and a post install hook in our supply chain
|
|
8
|
+
* for the sake of one string. This asks Windows itself instead: PowerShell with
|
|
9
|
+
* no profile, a small block that declares CredRead through the platform
|
|
10
|
+
* invocation layer, and base64 on the way out so a credential containing a
|
|
11
|
+
* quote or a newline cannot change the shape of what comes back.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here writes to the store, and the target is a constant supplied by
|
|
14
|
+
* the caller rather than anything read off disk.
|
|
15
|
+
*/
|
|
16
|
+
/** How long the helper may run before it is abandoned. */
|
|
17
|
+
export declare const WINDOWS_CREDENTIAL_TIMEOUT_MILLISECONDS = 5000;
|
|
18
|
+
/** Largest credential blob accepted, matching the desktop's own bound. */
|
|
19
|
+
export declare const MAX_WINDOWS_CREDENTIAL_BYTES = 16384;
|
|
20
|
+
/**
|
|
21
|
+
* The one shape a credential target may take.
|
|
22
|
+
*
|
|
23
|
+
* Bounded printable ASCII with no quote and no newline, so the value can never
|
|
24
|
+
* end a string literal inside the script below. A target outside this shape is
|
|
25
|
+
* refused rather than escaped: there is exactly one target in this product and
|
|
26
|
+
* it is a constant.
|
|
27
|
+
*/
|
|
28
|
+
export declare const CREDENTIAL_TARGET_PATTERN: RegExp;
|
|
29
|
+
export type CredentialCommandRunner = (executable: string, argumentsList: readonly string[], timeoutMilliseconds: number) => Promise<{
|
|
30
|
+
ok: true;
|
|
31
|
+
stdout: string;
|
|
32
|
+
} | {
|
|
33
|
+
ok: false;
|
|
34
|
+
}>;
|
|
35
|
+
export type WindowsCredentialOutcome = {
|
|
36
|
+
ok: true;
|
|
37
|
+
value: string;
|
|
38
|
+
} | {
|
|
39
|
+
ok: false;
|
|
40
|
+
reason: "absent" | "unreadable" | "invalid";
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The script the helper runs.
|
|
44
|
+
*
|
|
45
|
+
* It declares only CredRead and CredFree, reads the blob as bytes, and writes
|
|
46
|
+
* base64 to standard output. A missing entry prints nothing and exits zero,
|
|
47
|
+
* which is how "this machine has no Antigravity login" reaches us as an absence
|
|
48
|
+
* rather than as an error.
|
|
49
|
+
*/
|
|
50
|
+
export declare function credentialReadScript(target: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Decode what the helper printed.
|
|
53
|
+
*
|
|
54
|
+
* Empty output is an absent entry, which is the ordinary answer on a machine
|
|
55
|
+
* that never ran the Antigravity client. Anything that is not base64 of valid
|
|
56
|
+
* UTF-16 or UTF-8 text is unreadable, and neither branch ever echoes what it
|
|
57
|
+
* saw.
|
|
58
|
+
*/
|
|
59
|
+
export declare function decodeCredentialOutput(stdout: string): WindowsCredentialOutcome;
|
|
60
|
+
export interface WindowsCredentialOptions {
|
|
61
|
+
readonly runCommand: CredentialCommandRunner;
|
|
62
|
+
readonly powerShellExecutable?: string;
|
|
63
|
+
readonly timeoutMilliseconds?: number;
|
|
64
|
+
}
|
|
65
|
+
/** The executable the helper is run through, named rather than searched for. */
|
|
66
|
+
export declare const DEFAULT_POWERSHELL_EXECUTABLE = "powershell.exe";
|
|
67
|
+
/**
|
|
68
|
+
* Read one credential target through the helper.
|
|
69
|
+
*
|
|
70
|
+
* The runner is injected so this whole path is provable without a credential on
|
|
71
|
+
* the machine and so a test can never reach the real store.
|
|
72
|
+
*/
|
|
73
|
+
export declare function readWindowsCredentialWith(target: string, options: WindowsCredentialOptions): Promise<WindowsCredentialOutcome>;
|
|
74
|
+
//# sourceMappingURL=windows-credential.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows-credential.d.ts","sourceRoot":"","sources":["../../src/acquire/windows-credential.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,0DAA0D;AAC1D,eAAO,MAAM,uCAAuC,OAAQ,CAAC;AAE7D,0EAA0E;AAC1E,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,QAA6C,CAAC;AAEpF,MAAM,MAAM,uBAAuB,GAAG,CACpC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,mBAAmB,EAAE,MAAM,KACxB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC;AAE3D,MAAM,MAAM,wBAAwB,GAChC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,QAAQ,GAAG,YAAY,GAAG,SAAS,CAAA;CAAE,CAAC;AAE/D;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA2B3D;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,wBAAwB,CAwB/E;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,UAAU,EAAE,uBAAuB,CAAC;IAC7C,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED,gFAAgF;AAChF,eAAO,MAAM,6BAA6B,mBAAmB,CAAC;AAE9D;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,wBAAwB,CAAC,CAiBnC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading one Windows Credential Manager entry, with no native dependency.
|
|
3
|
+
*
|
|
4
|
+
* The Antigravity client keeps its token in the credential store rather than in
|
|
5
|
+
* a file, so a Windows machine has nothing on disk to read. Every published way
|
|
6
|
+
* to reach the store from Node is a native module with an install script, which
|
|
7
|
+
* is a compiler on a user's machine and a post install hook in our supply chain
|
|
8
|
+
* for the sake of one string. This asks Windows itself instead: PowerShell with
|
|
9
|
+
* no profile, a small block that declares CredRead through the platform
|
|
10
|
+
* invocation layer, and base64 on the way out so a credential containing a
|
|
11
|
+
* quote or a newline cannot change the shape of what comes back.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here writes to the store, and the target is a constant supplied by
|
|
14
|
+
* the caller rather than anything read off disk.
|
|
15
|
+
*/
|
|
16
|
+
/** How long the helper may run before it is abandoned. */
|
|
17
|
+
export const WINDOWS_CREDENTIAL_TIMEOUT_MILLISECONDS = 5_000;
|
|
18
|
+
/** Largest credential blob accepted, matching the desktop's own bound. */
|
|
19
|
+
export const MAX_WINDOWS_CREDENTIAL_BYTES = 16_384;
|
|
20
|
+
/**
|
|
21
|
+
* The one shape a credential target may take.
|
|
22
|
+
*
|
|
23
|
+
* Bounded printable ASCII with no quote and no newline, so the value can never
|
|
24
|
+
* end a string literal inside the script below. A target outside this shape is
|
|
25
|
+
* refused rather than escaped: there is exactly one target in this product and
|
|
26
|
+
* it is a constant.
|
|
27
|
+
*/
|
|
28
|
+
export const CREDENTIAL_TARGET_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.:@\\/-]{0,127}$/u;
|
|
29
|
+
/**
|
|
30
|
+
* The script the helper runs.
|
|
31
|
+
*
|
|
32
|
+
* It declares only CredRead and CredFree, reads the blob as bytes, and writes
|
|
33
|
+
* base64 to standard output. A missing entry prints nothing and exits zero,
|
|
34
|
+
* which is how "this machine has no Antigravity login" reaches us as an absence
|
|
35
|
+
* rather than as an error.
|
|
36
|
+
*/
|
|
37
|
+
export function credentialReadScript(target) {
|
|
38
|
+
return [
|
|
39
|
+
"$ErrorActionPreference = 'Stop';",
|
|
40
|
+
"Add-Type -Namespace OpenLimiterCred -Name Native -MemberDefinition @'",
|
|
41
|
+
"[DllImport(\"advapi32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]",
|
|
42
|
+
"public static extern bool CredRead(string target, uint type, uint flags, out IntPtr credential);",
|
|
43
|
+
"[DllImport(\"advapi32.dll\", SetLastError = true)]",
|
|
44
|
+
"public static extern void CredFree(IntPtr buffer);",
|
|
45
|
+
"'@;",
|
|
46
|
+
"$handle = [IntPtr]::Zero;",
|
|
47
|
+
"if (-not [OpenLimiterCred.Native]::CredRead('" + target + "', 1, 0, [ref] $handle)) { exit 0 };",
|
|
48
|
+
"try {",
|
|
49
|
+
/* CREDENTIALW packs its blob size and blob pointer at different offsets on
|
|
50
|
+
a 32 bit and a 64 bit host, so the offsets are derived from the pointer
|
|
51
|
+
width rather than hard coded to whichever one this machine happens to
|
|
52
|
+
be. Reading the wrong offset would hand back another field's bytes. */
|
|
53
|
+
" $wide = [IntPtr]::Size -eq 8;",
|
|
54
|
+
" $sizeOffset = if ($wide) { 32 } else { 24 };",
|
|
55
|
+
" $blobOffset = if ($wide) { 40 } else { 28 };",
|
|
56
|
+
" $size = [Runtime.InteropServices.Marshal]::ReadInt32($handle, $sizeOffset);",
|
|
57
|
+
" $blob = [Runtime.InteropServices.Marshal]::ReadIntPtr($handle, $blobOffset);",
|
|
58
|
+
" if ($size -le 0 -or $size -gt " + String(MAX_WINDOWS_CREDENTIAL_BYTES) + ") { exit 0 };",
|
|
59
|
+
" $bytes = New-Object byte[] $size;",
|
|
60
|
+
" [Runtime.InteropServices.Marshal]::Copy($blob, $bytes, 0, $size);",
|
|
61
|
+
" [Console]::Out.Write([Convert]::ToBase64String($bytes));",
|
|
62
|
+
"} finally { [OpenLimiterCred.Native]::CredFree($handle) }"
|
|
63
|
+
].join("\n");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Decode what the helper printed.
|
|
67
|
+
*
|
|
68
|
+
* Empty output is an absent entry, which is the ordinary answer on a machine
|
|
69
|
+
* that never ran the Antigravity client. Anything that is not base64 of valid
|
|
70
|
+
* UTF-16 or UTF-8 text is unreadable, and neither branch ever echoes what it
|
|
71
|
+
* saw.
|
|
72
|
+
*/
|
|
73
|
+
export function decodeCredentialOutput(stdout) {
|
|
74
|
+
const trimmed = stdout.trim();
|
|
75
|
+
if (trimmed === "")
|
|
76
|
+
return { ok: false, reason: "absent" };
|
|
77
|
+
if (!/^[A-Za-z0-9+/=]+$/u.test(trimmed))
|
|
78
|
+
return { ok: false, reason: "unreadable" };
|
|
79
|
+
let bytes;
|
|
80
|
+
try {
|
|
81
|
+
bytes = Buffer.from(trimmed, "base64");
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return { ok: false, reason: "unreadable" };
|
|
85
|
+
}
|
|
86
|
+
if (bytes.byteLength === 0 || bytes.byteLength > MAX_WINDOWS_CREDENTIAL_BYTES) {
|
|
87
|
+
return { ok: false, reason: "unreadable" };
|
|
88
|
+
}
|
|
89
|
+
/* The store holds the blob the writer chose, and this one is UTF-8 JSON. A
|
|
90
|
+
zero byte in an even position is the signature of UTF-16, which the same
|
|
91
|
+
writer uses on some releases, so both are read and neither is guessed. */
|
|
92
|
+
const utf16 = bytes.byteLength % 2 === 0 && bytes[1] === 0;
|
|
93
|
+
const text = utf16
|
|
94
|
+
? bytes.toString("utf16le")
|
|
95
|
+
: bytes.toString("utf8");
|
|
96
|
+
const value = text.replace(/\0+$/u, "");
|
|
97
|
+
return value.length === 0
|
|
98
|
+
? { ok: false, reason: "unreadable" }
|
|
99
|
+
: { ok: true, value };
|
|
100
|
+
}
|
|
101
|
+
/** The executable the helper is run through, named rather than searched for. */
|
|
102
|
+
export const DEFAULT_POWERSHELL_EXECUTABLE = "powershell.exe";
|
|
103
|
+
/**
|
|
104
|
+
* Read one credential target through the helper.
|
|
105
|
+
*
|
|
106
|
+
* The runner is injected so this whole path is provable without a credential on
|
|
107
|
+
* the machine and so a test can never reach the real store.
|
|
108
|
+
*/
|
|
109
|
+
export async function readWindowsCredentialWith(target, options) {
|
|
110
|
+
if (!CREDENTIAL_TARGET_PATTERN.test(target))
|
|
111
|
+
return { ok: false, reason: "invalid" };
|
|
112
|
+
const result = await options.runCommand(options.powerShellExecutable ?? DEFAULT_POWERSHELL_EXECUTABLE, [
|
|
113
|
+
"-NoProfile",
|
|
114
|
+
"-NonInteractive",
|
|
115
|
+
"-ExecutionPolicy",
|
|
116
|
+
"Bypass",
|
|
117
|
+
"-Command",
|
|
118
|
+
credentialReadScript(target)
|
|
119
|
+
], options.timeoutMilliseconds ?? WINDOWS_CREDENTIAL_TIMEOUT_MILLISECONDS);
|
|
120
|
+
return result.ok
|
|
121
|
+
? decodeCredentialOutput(result.stdout)
|
|
122
|
+
: { ok: false, reason: "unreadable" };
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=windows-credential.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows-credential.js","sourceRoot":"","sources":["../../src/acquire/windows-credential.ts"],"names":[],"mappings":"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"}
|