@yolk-sdk/emulators 0.1.0-canary.96
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/LICENSE +21 -0
- package/README.md +2135 -0
- package/dist/anthropic.d.mts +269 -0
- package/dist/anthropic.d.mts.map +1 -0
- package/dist/anthropic.mjs +177 -0
- package/dist/anthropic.mjs.map +1 -0
- package/dist/chat-completions.d.mts +288 -0
- package/dist/chat-completions.d.mts.map +1 -0
- package/dist/chat-completions.mjs +451 -0
- package/dist/chat-completions.mjs.map +1 -0
- package/dist/codex.d.mts +265 -0
- package/dist/codex.d.mts.map +1 -0
- package/dist/codex.mjs +199 -0
- package/dist/codex.mjs.map +1 -0
- package/dist/dropbox/api.d.mts +63 -0
- package/dist/dropbox/api.d.mts.map +1 -0
- package/dist/dropbox/api.mjs +565 -0
- package/dist/dropbox/api.mjs.map +1 -0
- package/dist/dropbox/state.d.mts +136 -0
- package/dist/dropbox/state.d.mts.map +1 -0
- package/dist/dropbox/state.mjs +209 -0
- package/dist/dropbox/state.mjs.map +1 -0
- package/dist/dropbox.d.mts +79 -0
- package/dist/dropbox.d.mts.map +1 -0
- package/dist/dropbox.mjs +124 -0
- package/dist/dropbox.mjs.map +1 -0
- package/dist/email-fixtures.d.mts +36 -0
- package/dist/email-fixtures.d.mts.map +1 -0
- package/dist/email-fixtures.mjs +1080 -0
- package/dist/email-fixtures.mjs.map +1 -0
- package/dist/email.d.mts +160 -0
- package/dist/email.d.mts.map +1 -0
- package/dist/email.mjs +608 -0
- package/dist/email.mjs.map +1 -0
- package/dist/emulator-compose.d.mts +40 -0
- package/dist/emulator-compose.d.mts.map +1 -0
- package/dist/emulator-compose.mjs +91 -0
- package/dist/emulator-compose.mjs.map +1 -0
- package/dist/emulator-http.d.mts +53 -0
- package/dist/emulator-http.d.mts.map +1 -0
- package/dist/emulator-http.mjs +117 -0
- package/dist/emulator-http.mjs.map +1 -0
- package/dist/emulator-kernel.d.mts +176 -0
- package/dist/emulator-kernel.d.mts.map +1 -0
- package/dist/emulator-kernel.mjs +413 -0
- package/dist/emulator-kernel.mjs.map +1 -0
- package/dist/fixture-route.d.mts +129 -0
- package/dist/fixture-route.d.mts.map +1 -0
- package/dist/fixture-route.mjs +257 -0
- package/dist/fixture-route.mjs.map +1 -0
- package/dist/fortnox/api.d.mts +92 -0
- package/dist/fortnox/api.d.mts.map +1 -0
- package/dist/fortnox/api.mjs +750 -0
- package/dist/fortnox/api.mjs.map +1 -0
- package/dist/fortnox/state.d.mts +533 -0
- package/dist/fortnox/state.d.mts.map +1 -0
- package/dist/fortnox/state.mjs +612 -0
- package/dist/fortnox/state.mjs.map +1 -0
- package/dist/fortnox.d.mts +128 -0
- package/dist/fortnox.d.mts.map +1 -0
- package/dist/fortnox.mjs +403 -0
- package/dist/fortnox.mjs.map +1 -0
- package/dist/gateway-evaluate-recordings.d.mts +12 -0
- package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
- package/dist/gateway-evaluate-recordings.mjs +128 -0
- package/dist/gateway-evaluate-recordings.mjs.map +1 -0
- package/dist/gateway.d.mts +272 -0
- package/dist/gateway.d.mts.map +1 -0
- package/dist/gateway.mjs +400 -0
- package/dist/gateway.mjs.map +1 -0
- package/dist/github/api.d.mts +63 -0
- package/dist/github/api.d.mts.map +1 -0
- package/dist/github/api.mjs +527 -0
- package/dist/github/api.mjs.map +1 -0
- package/dist/github/state.d.mts +272 -0
- package/dist/github/state.d.mts.map +1 -0
- package/dist/github/state.mjs +303 -0
- package/dist/github/state.mjs.map +1 -0
- package/dist/github.d.mts +77 -0
- package/dist/github.d.mts.map +1 -0
- package/dist/github.mjs +180 -0
- package/dist/github.mjs.map +1 -0
- package/dist/google/calendar.d.mts +10 -0
- package/dist/google/calendar.d.mts.map +1 -0
- package/dist/google/calendar.mjs +252 -0
- package/dist/google/calendar.mjs.map +1 -0
- package/dist/google/drive.d.mts +8 -0
- package/dist/google/drive.d.mts.map +1 -0
- package/dist/google/drive.mjs +252 -0
- package/dist/google/drive.mjs.map +1 -0
- package/dist/google/gmail.d.mts +10 -0
- package/dist/google/gmail.d.mts.map +1 -0
- package/dist/google/gmail.mjs +591 -0
- package/dist/google/gmail.mjs.map +1 -0
- package/dist/google/shared.d.mts +107 -0
- package/dist/google/shared.d.mts.map +1 -0
- package/dist/google/shared.mjs +146 -0
- package/dist/google/shared.mjs.map +1 -0
- package/dist/google/state.d.mts +385 -0
- package/dist/google/state.d.mts.map +1 -0
- package/dist/google/state.mjs +496 -0
- package/dist/google/state.mjs.map +1 -0
- package/dist/google.d.mts +75 -0
- package/dist/google.d.mts.map +1 -0
- package/dist/google.mjs +210 -0
- package/dist/google.mjs.map +1 -0
- package/dist/linkedin-search/api.d.mts +45 -0
- package/dist/linkedin-search/api.d.mts.map +1 -0
- package/dist/linkedin-search/api.mjs +181 -0
- package/dist/linkedin-search/api.mjs.map +1 -0
- package/dist/linkedin-search/state.d.mts +123 -0
- package/dist/linkedin-search/state.d.mts.map +1 -0
- package/dist/linkedin-search/state.mjs +261 -0
- package/dist/linkedin-search/state.mjs.map +1 -0
- package/dist/linkedin-search.d.mts +69 -0
- package/dist/linkedin-search.d.mts.map +1 -0
- package/dist/linkedin-search.mjs +158 -0
- package/dist/linkedin-search.mjs.map +1 -0
- package/dist/mcp/api.d.mts +44 -0
- package/dist/mcp/api.d.mts.map +1 -0
- package/dist/mcp/api.mjs +557 -0
- package/dist/mcp/api.mjs.map +1 -0
- package/dist/mcp/recordings.d.mts +40 -0
- package/dist/mcp/recordings.d.mts.map +1 -0
- package/dist/mcp/recordings.mjs +2390 -0
- package/dist/mcp/recordings.mjs.map +1 -0
- package/dist/mcp/state.d.mts +71 -0
- package/dist/mcp/state.d.mts.map +1 -0
- package/dist/mcp/state.mjs +83 -0
- package/dist/mcp/state.mjs.map +1 -0
- package/dist/mcp.d.mts +84 -0
- package/dist/mcp.d.mts.map +1 -0
- package/dist/mcp.mjs +241 -0
- package/dist/mcp.mjs.map +1 -0
- package/dist/messages.d.mts +183 -0
- package/dist/messages.d.mts.map +1 -0
- package/dist/messages.mjs +532 -0
- package/dist/messages.mjs.map +1 -0
- package/dist/microsoft/api.d.mts +47 -0
- package/dist/microsoft/api.d.mts.map +1 -0
- package/dist/microsoft/api.mjs +178 -0
- package/dist/microsoft/api.mjs.map +1 -0
- package/dist/microsoft/calendar.d.mts +30 -0
- package/dist/microsoft/calendar.d.mts.map +1 -0
- package/dist/microsoft/calendar.mjs +271 -0
- package/dist/microsoft/calendar.mjs.map +1 -0
- package/dist/microsoft/drive.d.mts +36 -0
- package/dist/microsoft/drive.d.mts.map +1 -0
- package/dist/microsoft/drive.mjs +298 -0
- package/dist/microsoft/drive.mjs.map +1 -0
- package/dist/microsoft/graph.d.mts +198 -0
- package/dist/microsoft/graph.d.mts.map +1 -0
- package/dist/microsoft/graph.mjs +264 -0
- package/dist/microsoft/graph.mjs.map +1 -0
- package/dist/microsoft/mail.d.mts +48 -0
- package/dist/microsoft/mail.d.mts.map +1 -0
- package/dist/microsoft/mail.mjs +488 -0
- package/dist/microsoft/mail.mjs.map +1 -0
- package/dist/microsoft/state.d.mts +480 -0
- package/dist/microsoft/state.d.mts.map +1 -0
- package/dist/microsoft/state.mjs +625 -0
- package/dist/microsoft/state.mjs.map +1 -0
- package/dist/microsoft.d.mts +168 -0
- package/dist/microsoft.d.mts.map +1 -0
- package/dist/microsoft.mjs +483 -0
- package/dist/microsoft.mjs.map +1 -0
- package/dist/node.d.mts +47 -0
- package/dist/node.d.mts.map +1 -0
- package/dist/node.mjs +178 -0
- package/dist/node.mjs.map +1 -0
- package/dist/notion/api.d.mts +51 -0
- package/dist/notion/api.d.mts.map +1 -0
- package/dist/notion/api.mjs +507 -0
- package/dist/notion/api.mjs.map +1 -0
- package/dist/notion/state.d.mts +329 -0
- package/dist/notion/state.d.mts.map +1 -0
- package/dist/notion/state.mjs +432 -0
- package/dist/notion/state.mjs.map +1 -0
- package/dist/notion.d.mts +72 -0
- package/dist/notion.d.mts.map +1 -0
- package/dist/notion.mjs +127 -0
- package/dist/notion.mjs.map +1 -0
- package/dist/openai.d.mts +165 -0
- package/dist/openai.d.mts.map +1 -0
- package/dist/openai.mjs +141 -0
- package/dist/openai.mjs.map +1 -0
- package/dist/opencode-recordings.d.mts +7 -0
- package/dist/opencode-recordings.d.mts.map +1 -0
- package/dist/opencode-recordings.mjs +222 -0
- package/dist/opencode-recordings.mjs.map +1 -0
- package/dist/opencode.d.mts +70 -0
- package/dist/opencode.d.mts.map +1 -0
- package/dist/opencode.mjs +217 -0
- package/dist/opencode.mjs.map +1 -0
- package/dist/r2-fixtures.d.mts +36 -0
- package/dist/r2-fixtures.d.mts.map +1 -0
- package/dist/r2-fixtures.mjs +245 -0
- package/dist/r2-fixtures.mjs.map +1 -0
- package/dist/r2-guard.d.mts +43 -0
- package/dist/r2-guard.d.mts.map +1 -0
- package/dist/r2-guard.mjs +215 -0
- package/dist/r2-guard.mjs.map +1 -0
- package/dist/r2.d.mts +166 -0
- package/dist/r2.d.mts.map +1 -0
- package/dist/r2.mjs +517 -0
- package/dist/r2.mjs.map +1 -0
- package/dist/responses.d.mts +231 -0
- package/dist/responses.d.mts.map +1 -0
- package/dist/responses.mjs +556 -0
- package/dist/responses.mjs.map +1 -0
- package/dist/route-evidence.d.mts +45 -0
- package/dist/route-evidence.d.mts.map +1 -0
- package/dist/route-evidence.mjs +56 -0
- package/dist/route-evidence.mjs.map +1 -0
- package/dist/router.d.mts +93 -0
- package/dist/router.d.mts.map +1 -0
- package/dist/router.mjs +259 -0
- package/dist/router.mjs.map +1 -0
- package/dist/stateful-core.d.mts +17 -0
- package/dist/stateful-core.d.mts.map +1 -0
- package/dist/stateful-core.mjs +41 -0
- package/dist/stateful-core.mjs.map +1 -0
- package/dist/stateful-emulator.d.mts +565 -0
- package/dist/stateful-emulator.d.mts.map +1 -0
- package/dist/stateful-emulator.mjs +1228 -0
- package/dist/stateful-emulator.mjs.map +1 -0
- package/dist/stateful-secrets.d.mts +84 -0
- package/dist/stateful-secrets.d.mts.map +1 -0
- package/dist/stateful-secrets.mjs +216 -0
- package/dist/stateful-secrets.mjs.map +1 -0
- package/dist/subscription-usage-recordings.d.mts +9 -0
- package/dist/subscription-usage-recordings.d.mts.map +1 -0
- package/dist/subscription-usage-recordings.mjs +53 -0
- package/dist/subscription-usage-recordings.mjs.map +1 -0
- package/dist/subscription-usage.d.mts +53 -0
- package/dist/subscription-usage.d.mts.map +1 -0
- package/dist/subscription-usage.mjs +21 -0
- package/dist/subscription-usage.mjs.map +1 -0
- package/dist/telegram/api.d.mts +34 -0
- package/dist/telegram/api.d.mts.map +1 -0
- package/dist/telegram/api.mjs +257 -0
- package/dist/telegram/api.mjs.map +1 -0
- package/dist/telegram/state.d.mts +128 -0
- package/dist/telegram/state.d.mts.map +1 -0
- package/dist/telegram/state.mjs +154 -0
- package/dist/telegram/state.mjs.map +1 -0
- package/dist/telegram.d.mts +62 -0
- package/dist/telegram.d.mts.map +1 -0
- package/dist/telegram.mjs +127 -0
- package/dist/telegram.mjs.map +1 -0
- package/dist/todoist/api.d.mts +55 -0
- package/dist/todoist/api.d.mts.map +1 -0
- package/dist/todoist/api.mjs +415 -0
- package/dist/todoist/api.mjs.map +1 -0
- package/dist/todoist/state.d.mts +275 -0
- package/dist/todoist/state.d.mts.map +1 -0
- package/dist/todoist/state.mjs +345 -0
- package/dist/todoist/state.mjs.map +1 -0
- package/dist/todoist.d.mts +68 -0
- package/dist/todoist.d.mts.map +1 -0
- package/dist/todoist.mjs +181 -0
- package/dist/todoist.mjs.map +1 -0
- package/dist/xai.d.mts +267 -0
- package/dist/xai.d.mts.map +1 -0
- package/dist/xai.mjs +252 -0
- package/dist/xai.mjs.map +1 -0
- package/package.json +157 -0
- package/src/anthropic.ts +289 -0
- package/src/chat-completions.ts +924 -0
- package/src/codex.ts +296 -0
- package/src/dropbox/api.ts +1084 -0
- package/src/dropbox/state.ts +364 -0
- package/src/dropbox.ts +203 -0
- package/src/email-fixtures.ts +1297 -0
- package/src/email.ts +1108 -0
- package/src/emulator-compose.ts +147 -0
- package/src/emulator-http.ts +184 -0
- package/src/emulator-kernel.ts +844 -0
- package/src/fixture-route.ts +561 -0
- package/src/fortnox/api.ts +1352 -0
- package/src/fortnox/state.ts +798 -0
- package/src/fortnox.ts +801 -0
- package/src/gateway-evaluate-recordings.ts +153 -0
- package/src/gateway.ts +530 -0
- package/src/github/api.ts +986 -0
- package/src/github/state.ts +439 -0
- package/src/github.ts +271 -0
- package/src/google/calendar.ts +486 -0
- package/src/google/drive.ts +471 -0
- package/src/google/gmail.ts +1011 -0
- package/src/google/shared.ts +321 -0
- package/src/google/state.ts +686 -0
- package/src/google.ts +298 -0
- package/src/linkedin-search/api.ts +363 -0
- package/src/linkedin-search/state.ts +373 -0
- package/src/linkedin-search.ts +241 -0
- package/src/mcp/api.ts +995 -0
- package/src/mcp/recordings.ts +2665 -0
- package/src/mcp/state.ts +125 -0
- package/src/mcp.ts +319 -0
- package/src/messages.ts +844 -0
- package/src/microsoft/api.ts +426 -0
- package/src/microsoft/calendar.ts +491 -0
- package/src/microsoft/drive.ts +541 -0
- package/src/microsoft/graph.ts +550 -0
- package/src/microsoft/mail.ts +833 -0
- package/src/microsoft/state.ts +822 -0
- package/src/microsoft.ts +982 -0
- package/src/node.ts +293 -0
- package/src/notion/api.ts +976 -0
- package/src/notion/state.ts +512 -0
- package/src/notion.ts +210 -0
- package/src/openai.ts +198 -0
- package/src/opencode-recordings.ts +257 -0
- package/src/opencode.ts +271 -0
- package/src/r2-fixtures.ts +295 -0
- package/src/r2-guard.ts +323 -0
- package/src/r2.ts +890 -0
- package/src/responses.ts +901 -0
- package/src/route-evidence.ts +90 -0
- package/src/router.ts +490 -0
- package/src/stateful-core.ts +70 -0
- package/src/stateful-emulator.ts +2571 -0
- package/src/stateful-secrets.ts +299 -0
- package/src/subscription-usage-recordings.ts +77 -0
- package/src/subscription-usage.ts +68 -0
- package/src/telegram/api.ts +437 -0
- package/src/telegram/state.ts +229 -0
- package/src/telegram.ts +227 -0
- package/src/todoist/api.ts +736 -0
- package/src/todoist/state.ts +456 -0
- package/src/todoist.ts +316 -0
- package/src/xai.ts +341 -0
package/src/r2-guard.ts
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The credential guard of the R2 port emulator (internal; used by `r2.ts`, and imported by
|
|
3
|
+
* `test/r2.test.ts` for the exhaustive parity tests).
|
|
4
|
+
*
|
|
5
|
+
* The copied lists here mirror `@yolk-sdk/conformance` (`isPortCredentialKey`, the credential query
|
|
6
|
+
* parameter names of `hasLiveCredentialParam`, `bearerPattern`, and `apiKeyPatterns`) and the R2
|
|
7
|
+
* conformance guard `findR2PortFixtureSecrets` (canonical placeholders and decodings). Emulators
|
|
8
|
+
* never import SDK code, so `test/r2.test.ts` holds one sample per entry of every list here,
|
|
9
|
+
* asserts the sample count equals the list length, and checks each sample against the shared
|
|
10
|
+
* scan; a new entry without a sample fails.
|
|
11
|
+
*
|
|
12
|
+
* @experimental
|
|
13
|
+
*/
|
|
14
|
+
import { Predicate } from 'effect'
|
|
15
|
+
import type * as Schema from 'effect/Schema'
|
|
16
|
+
import { textClosureOutcome } from './stateful-secrets.ts'
|
|
17
|
+
|
|
18
|
+
type JsonObject = Schema.JsonObject
|
|
19
|
+
|
|
20
|
+
const isJsonObject = (value: Schema.Json): value is JsonObject =>
|
|
21
|
+
value !== null && Predicate.isObject(value) && !Array.isArray(value)
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The credential field names of the shared port scan (`isPortCredentialKey`): `credential(s)`
|
|
25
|
+
* plus the credential field names, AWS-style `accessKeyId` / `secretAccessKey` / `sessionToken`
|
|
26
|
+
* included. One regular-expression source per name; matched whole and case-insensitively.
|
|
27
|
+
*/
|
|
28
|
+
export const r2CredentialKeyNames: ReadonlyArray<string> = [
|
|
29
|
+
'credentials?',
|
|
30
|
+
'access[_-]?token',
|
|
31
|
+
'refresh[_-]?token',
|
|
32
|
+
'id[_-]?token',
|
|
33
|
+
'auth[_-]?token',
|
|
34
|
+
'api[_-]?token',
|
|
35
|
+
'session[_-]?token',
|
|
36
|
+
'private[_-]?token',
|
|
37
|
+
'bearer[_-]?token',
|
|
38
|
+
'oauth[_-]?token',
|
|
39
|
+
'token',
|
|
40
|
+
'client[_-]?secret',
|
|
41
|
+
'secret(?:[_-]?key)?',
|
|
42
|
+
'private[_-]?key',
|
|
43
|
+
'password',
|
|
44
|
+
'passwd',
|
|
45
|
+
'api[_-]?key',
|
|
46
|
+
'access[_-]?key[_-]?id',
|
|
47
|
+
'secret[_-]?access[_-]?key',
|
|
48
|
+
'authorization'
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
const credentialKeyPattern = new RegExp(`^(?:${r2CredentialKeyNames.join('|')})$`, 'i')
|
|
52
|
+
|
|
53
|
+
/** True for a key whose whole value is a credential (dropped, never compared or recorded). */
|
|
54
|
+
export const isR2CredentialKey = (key: string): boolean => credentialKeyPattern.test(key)
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The credential query or form parameter names of the shared port scan (`hasLiveCredentialParam`).
|
|
58
|
+
* One regular-expression source per name.
|
|
59
|
+
*/
|
|
60
|
+
export const r2CredentialParamNames: ReadonlyArray<string> = [
|
|
61
|
+
'api[_-]?key',
|
|
62
|
+
'key',
|
|
63
|
+
'token',
|
|
64
|
+
'access[_-]?token',
|
|
65
|
+
'refresh[_-]?token',
|
|
66
|
+
'id[_-]?token',
|
|
67
|
+
'auth',
|
|
68
|
+
'secret',
|
|
69
|
+
'password',
|
|
70
|
+
'client[_-]?secret',
|
|
71
|
+
'x-amz-signature',
|
|
72
|
+
'x-amz-credential',
|
|
73
|
+
'x-amz-security-token'
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
// A credential parameter: a name at the start or after `?` / `&`, `=`, and a value (any character
|
|
77
|
+
// but `&` or `#`).
|
|
78
|
+
const credentialParamAt = new RegExp(`(?:^|[?&])(?:${r2CredentialParamNames.join('|')})=[^&#]`, 'i')
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The shared scan's token patterns (`bearerPattern` and `apiKeyPatterns`): a bearer token, common
|
|
82
|
+
* API-key prefixes, JSON Web Tokens, and PEM private keys.
|
|
83
|
+
*/
|
|
84
|
+
export const r2TokenPatterns: ReadonlyArray<RegExp> = [
|
|
85
|
+
/\bbearer\s+[A-Za-z0-9._~+/=-]{8,}/i,
|
|
86
|
+
/\bsk-[A-Za-z0-9_-]{16,}/,
|
|
87
|
+
/\b[sr]k_(live|test)_[A-Za-z0-9]{16,}/,
|
|
88
|
+
/\bxai-[A-Za-z0-9]{20,}/,
|
|
89
|
+
/\bvck_[A-Za-z0-9]{16,}/,
|
|
90
|
+
/\bgh[pousr]_[A-Za-z0-9]{20,}/,
|
|
91
|
+
/\bgithub_pat_[A-Za-z0-9_]{20,}/,
|
|
92
|
+
/\bAKIA[0-9A-Z]{16}\b/,
|
|
93
|
+
/\bAIza[0-9A-Za-z_-]{35}/,
|
|
94
|
+
/\bxox[abprs]-[A-Za-z0-9-]{10,}/,
|
|
95
|
+
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/,
|
|
96
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
// The SigV4 rules of the R2 conformance guard (`findR2PortFixtureSecrets` in
|
|
100
|
+
// `@yolk-sdk/connectors/r2-storage/conformance`), copied: the exact canonical placeholder
|
|
101
|
+
// occurrences are blanked out, then any credential name left in any decoding layer is refused.
|
|
102
|
+
|
|
103
|
+
/** The canonical placeholders (`syntheticPortCredentialParams`), as the fixtures write them. */
|
|
104
|
+
const canonicalPlaceholders = new Map<string, string>([
|
|
105
|
+
['x-amz-signature', 'yolk-synthetic-signature'],
|
|
106
|
+
[
|
|
107
|
+
'x-amz-credential',
|
|
108
|
+
encodeURIComponent('yolk-synthetic-access-key-id/20260930/auto/s3/aws4_request')
|
|
109
|
+
]
|
|
110
|
+
])
|
|
111
|
+
|
|
112
|
+
const canonicalNamesAt = /(^|[?&])(x-amz-signature|x-amz-credential)=/gi
|
|
113
|
+
|
|
114
|
+
const canonicalValueEnd = /^(?:[&#\s"<>]|$)/
|
|
115
|
+
|
|
116
|
+
const withoutCanonicalPlaceholders = (text: string): string => {
|
|
117
|
+
let masked = text
|
|
118
|
+
|
|
119
|
+
for (const match of text.matchAll(canonicalNamesAt)) {
|
|
120
|
+
const nameStart = match.index + (match[1] ?? '').length
|
|
121
|
+
const valueStart = match.index + match[0].length
|
|
122
|
+
const placeholder = canonicalPlaceholders.get((match[2] ?? '').toLowerCase()) ?? ''
|
|
123
|
+
const valueEnd = valueStart + placeholder.length
|
|
124
|
+
|
|
125
|
+
if (
|
|
126
|
+
placeholder.length > 0 &&
|
|
127
|
+
text.startsWith(placeholder, valueStart) &&
|
|
128
|
+
canonicalValueEnd.test(text.slice(valueEnd))
|
|
129
|
+
) {
|
|
130
|
+
const blank = ' '.repeat(valueEnd - nameStart)
|
|
131
|
+
|
|
132
|
+
masked = `${masked.slice(0, nameStart)}${blank}${masked.slice(valueEnd)}`
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return masked
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const percentDecodedOnce = (text: string): string =>
|
|
140
|
+
text.replace(/%([0-9A-Fa-f]{2})/g, (_match, hex: string) =>
|
|
141
|
+
String.fromCharCode(Number.parseInt(hex, 16))
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
const codePoint = (value: number): string => String.fromCodePoint(Math.min(value, 0x10ffff))
|
|
145
|
+
|
|
146
|
+
const escapesDecodedOnce = (text: string): string =>
|
|
147
|
+
text.replace(
|
|
148
|
+
/\\u([0-9A-Fa-f]{4})|\\x([0-9A-Fa-f]{2})|\\\\|&#[xX]([0-9A-Fa-f]{1,6});?|&#([0-9]{1,7});?/g,
|
|
149
|
+
(match, unicode?: string, hex?: string, entityHex?: string, entity?: string) => {
|
|
150
|
+
const hexDigits = unicode ?? hex ?? entityHex
|
|
151
|
+
|
|
152
|
+
if (hexDigits !== undefined) return codePoint(Number.parseInt(hexDigits, 16))
|
|
153
|
+
|
|
154
|
+
if (entity !== undefined) return codePoint(Number.parseInt(entity, 10))
|
|
155
|
+
|
|
156
|
+
return match === '\\\\' ? '\\' : match
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const maxRoundsPerDecoding = 3
|
|
161
|
+
|
|
162
|
+
/** `text` and every variant within three percent rounds and three escape rounds, in any order. */
|
|
163
|
+
const decodedVariants = (text: string): ReadonlyArray<string> => {
|
|
164
|
+
const seen = new Set<string>([text])
|
|
165
|
+
|
|
166
|
+
let frontier: ReadonlyArray<{ text: string; percent: number; escapes: number }> = [
|
|
167
|
+
{ text, percent: 0, escapes: 0 }
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
while (frontier.length > 0) {
|
|
171
|
+
frontier = frontier.flatMap(variant =>
|
|
172
|
+
[
|
|
173
|
+
...(variant.percent < maxRoundsPerDecoding
|
|
174
|
+
? [{ ...variant, text: percentDecodedOnce(variant.text), percent: variant.percent + 1 }]
|
|
175
|
+
: []),
|
|
176
|
+
...(variant.escapes < maxRoundsPerDecoding
|
|
177
|
+
? [{ ...variant, text: escapesDecodedOnce(variant.text), escapes: variant.escapes + 1 }]
|
|
178
|
+
: [])
|
|
179
|
+
].filter(candidate => {
|
|
180
|
+
if (seen.has(candidate.text)) return false
|
|
181
|
+
|
|
182
|
+
seen.add(candidate.text)
|
|
183
|
+
|
|
184
|
+
return true
|
|
185
|
+
})
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return [...seen]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const sigV4CredentialNames = /x-amz-credential|x-amz-signature|x-amz-security-token/i
|
|
193
|
+
|
|
194
|
+
/** True when `text` holds a SigV4 credential name, a credential parameter, or a token. */
|
|
195
|
+
const credentialText = (text: string): boolean =>
|
|
196
|
+
sigV4CredentialNames.test(text) ||
|
|
197
|
+
credentialParamAt.test(text) ||
|
|
198
|
+
r2TokenPatterns.some(pattern => pattern.test(text))
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* True when `text`, once its exact canonical placeholder occurrences are blanked out, holds a
|
|
202
|
+
* credential (`credentialText`) raw, in any variant within three percent and three escape rounds
|
|
203
|
+
* (the R2 guard's decodings), or anywhere in the fail-closed closure of `textClosureOutcome`
|
|
204
|
+
* (any depth of percent-encoding and JSON escaping; a work cap counts as a credential).
|
|
205
|
+
*/
|
|
206
|
+
const carriesCredential = (text: string): boolean => {
|
|
207
|
+
const masked = withoutCanonicalPlaceholders(text)
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
decodedVariants(masked).some(credentialText) ||
|
|
211
|
+
textClosureOutcome(masked, credentialText) !== 'clear'
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* True when `text` repeats a guarded value: raw, in any of R2's own decodings (`decodedVariants`:
|
|
217
|
+
* percent, `\uXXXX` of any code point, `\xXX`, numeric HTML references, in any order), or
|
|
218
|
+
* anywhere in the shared closure (a work cap counts as a repeat). Every non-empty value is
|
|
219
|
+
* guarded: unlike the shared `textRepeatsSecret`, there is no minimum length.
|
|
220
|
+
*/
|
|
221
|
+
const repeatsGuardedValue = (text: string, guarded: ReadonlyArray<string>): boolean => {
|
|
222
|
+
if (guarded.length === 0) return false
|
|
223
|
+
|
|
224
|
+
const holds = (candidate: string) => guarded.some(value => candidate.includes(value))
|
|
225
|
+
|
|
226
|
+
return decodedVariants(text).some(holds) || textClosureOutcome(text, holds) !== 'clear'
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** True when `text` carries a credential or repeats a guarded value. */
|
|
230
|
+
export const textCarriesCredential = (text: string, guarded: ReadonlyArray<string>): boolean =>
|
|
231
|
+
carriesCredential(text) || repeatsGuardedValue(text, guarded)
|
|
232
|
+
|
|
233
|
+
const numericText = /^[+-]?(?:[0-9]+\.?[0-9]*|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* The texts a number is matched by: as JavaScript prints it (`1e+21`, `12345678`) and, for an
|
|
237
|
+
* integer, its full digit string (`1000000000000000000000`).
|
|
238
|
+
*/
|
|
239
|
+
export const numberTexts = (value: number): ReadonlyArray<string> =>
|
|
240
|
+
Number.isSafeInteger(value) || !Number.isInteger(value)
|
|
241
|
+
? [String(value)]
|
|
242
|
+
: [String(value), BigInt(value).toString()]
|
|
243
|
+
|
|
244
|
+
/** A guarded value and, when it is numeric text (`0012345678`), the forms of its number. */
|
|
245
|
+
const guardedForms = (value: string): ReadonlyArray<string> => {
|
|
246
|
+
if (value.length === 0) return []
|
|
247
|
+
|
|
248
|
+
const number = Number(value)
|
|
249
|
+
|
|
250
|
+
return numericText.test(value) && Number.isFinite(number)
|
|
251
|
+
? [value, ...numberTexts(number)]
|
|
252
|
+
: [value]
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Every key, string, and number under a credential field, at any depth (the values to guard). */
|
|
256
|
+
const credentialSubtree = (value: Schema.Json): ReadonlyArray<string> => {
|
|
257
|
+
if (Predicate.isString(value)) return guardedForms(value)
|
|
258
|
+
|
|
259
|
+
if (Predicate.isNumber(value)) return numberTexts(value)
|
|
260
|
+
|
|
261
|
+
if (Array.isArray(value)) return value.flatMap(credentialSubtree)
|
|
262
|
+
|
|
263
|
+
return isJsonObject(value)
|
|
264
|
+
? Object.entries(value).flatMap(([key, item]) => [
|
|
265
|
+
...guardedForms(key),
|
|
266
|
+
...credentialSubtree(item)
|
|
267
|
+
])
|
|
268
|
+
: []
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** The guarded values of every credential field of `value`, at any depth, deduplicated. */
|
|
272
|
+
export const guardedValues = (value: Schema.Json): ReadonlyArray<string> => {
|
|
273
|
+
const collect = (item: Schema.Json): ReadonlyArray<string> => {
|
|
274
|
+
if (Array.isArray(item)) return item.flatMap(collect)
|
|
275
|
+
|
|
276
|
+
if (!isJsonObject(item)) return []
|
|
277
|
+
|
|
278
|
+
return Object.entries(item).flatMap(([key, child]) =>
|
|
279
|
+
isR2CredentialKey(key) ? credentialSubtree(child) : collect(child)
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return [...new Set(collect(value))]
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** True when `value` holds an own `__proto__` key at any depth (an admission bypass otherwise). */
|
|
287
|
+
export const hasOwnProtoKey = (value: Schema.Json): boolean => {
|
|
288
|
+
if (Array.isArray(value)) return value.some(hasOwnProtoKey)
|
|
289
|
+
|
|
290
|
+
if (!isJsonObject(value)) return false
|
|
291
|
+
|
|
292
|
+
return Object.hasOwn(value, '__proto__') || Object.values(value).some(hasOwnProtoKey)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const strippedOf = (value: Schema.Json): Schema.Json => {
|
|
296
|
+
if (Array.isArray(value)) return value.map(strippedOf)
|
|
297
|
+
|
|
298
|
+
return isJsonObject(value) ? withoutCredentials(value) : value
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* A copy without credential fields, at any depth (`redactPortPayload` of the shared scan), built
|
|
303
|
+
* with own data properties only (`Object.fromEntries`).
|
|
304
|
+
*/
|
|
305
|
+
export const withoutCredentials = (value: JsonObject): JsonObject =>
|
|
306
|
+
Object.fromEntries(
|
|
307
|
+
Object.entries(value).flatMap(([key, item]) =>
|
|
308
|
+
isR2CredentialKey(key) ? [] : [[key, strippedOf(item)]]
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
/** Every object key, string value, and number (`numberTexts`) of `value`. */
|
|
313
|
+
export const textsOf = (value: Schema.Json): ReadonlyArray<string> => {
|
|
314
|
+
if (Predicate.isString(value)) return [value]
|
|
315
|
+
|
|
316
|
+
if (Predicate.isNumber(value)) return numberTexts(value)
|
|
317
|
+
|
|
318
|
+
if (Array.isArray(value)) return value.flatMap(textsOf)
|
|
319
|
+
|
|
320
|
+
return isJsonObject(value)
|
|
321
|
+
? Object.entries(value).flatMap(([key, item]) => [key, ...textsOf(item)])
|
|
322
|
+
: []
|
|
323
|
+
}
|