@aexhq/sdk 0.46.4-canary → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +38 -0
- package/README.md +23 -31
- package/dist/client/aex.d.ts +33 -0
- package/dist/client/aex.js +98 -0
- package/dist/client/aex.js.map +1 -0
- package/dist/client/credentials.d.ts +25 -0
- package/dist/client/credentials.js +97 -0
- package/dist/client/credentials.js.map +1 -0
- package/dist/client/routing.d.ts +7 -0
- package/dist/client/routing.js +29 -0
- package/dist/client/routing.js.map +1 -0
- package/dist/downloads/download.d.ts +25 -0
- package/dist/downloads/download.js +53 -0
- package/dist/downloads/download.js.map +1 -0
- package/dist/generated/errors.d.ts +12 -0
- package/dist/generated/errors.js +81 -0
- package/dist/generated/errors.js.map +1 -0
- package/dist/generated/resources.d.ts +730 -0
- package/dist/generated/resources.js +606 -0
- package/dist/generated/resources.js.map +1 -0
- package/dist/generated/routes.d.ts +42 -0
- package/dist/generated/routes.js +2101 -0
- package/dist/generated/routes.js.map +1 -0
- package/dist/index.d.ts +20 -50
- package/dist/index.js +11 -62
- package/dist/index.js.map +1 -1
- package/dist/observations/stream.d.ts +1 -0
- package/dist/observations/stream.js +18 -0
- package/dist/observations/stream.js.map +1 -0
- package/dist/transport/errors.d.ts +60 -0
- package/dist/transport/errors.js +107 -0
- package/dist/transport/errors.js.map +1 -0
- package/dist/transport/pagination.d.ts +8 -0
- package/dist/transport/pagination.js +34 -0
- package/dist/transport/pagination.js.map +1 -0
- package/dist/transport/retry.d.ts +21 -0
- package/dist/transport/retry.js +37 -0
- package/dist/transport/retry.js.map +1 -0
- package/dist/transport/transport.d.ts +25 -0
- package/dist/transport/transport.js +28 -0
- package/dist/transport/transport.js.map +1 -0
- package/package.json +63 -30
- package/dist/_contracts/account-operations.d.ts +0 -101
- package/dist/_contracts/account-operations.js +0 -242
- package/dist/_contracts/account-types.d.ts +0 -461
- package/dist/_contracts/account-types.js +0 -1
- package/dist/_contracts/api-key.d.ts +0 -61
- package/dist/_contracts/api-key.js +0 -101
- package/dist/_contracts/api-routes.d.ts +0 -20
- package/dist/_contracts/api-routes.js +0 -109
- package/dist/_contracts/archive-limits.d.ts +0 -3
- package/dist/_contracts/archive-limits.js +0 -23
- package/dist/_contracts/asset-authoring.d.ts +0 -22
- package/dist/_contracts/asset-authoring.js +0 -106
- package/dist/_contracts/asset-bundle.d.ts +0 -64
- package/dist/_contracts/asset-bundle.js +0 -263
- package/dist/_contracts/asset-upload-helper.d.ts +0 -31
- package/dist/_contracts/asset-upload-helper.js +0 -84
- package/dist/_contracts/billing-admission.d.ts +0 -29
- package/dist/_contracts/billing-admission.js +0 -28
- package/dist/_contracts/bundle-manifest.d.ts +0 -89
- package/dist/_contracts/bundle-manifest.js +0 -158
- package/dist/_contracts/canonical-sha256.d.ts +0 -8
- package/dist/_contracts/canonical-sha256.js +0 -8
- package/dist/_contracts/connection-ticket.d.ts +0 -22
- package/dist/_contracts/connection-ticket.js +0 -54
- package/dist/_contracts/continuation-event.d.ts +0 -31
- package/dist/_contracts/continuation-event.js +0 -6
- package/dist/_contracts/contract-parse-error.d.ts +0 -12
- package/dist/_contracts/contract-parse-error.js +0 -51
- package/dist/_contracts/error-codes.d.ts +0 -26
- package/dist/_contracts/error-codes.js +0 -116
- package/dist/_contracts/error-factory.d.ts +0 -32
- package/dist/_contracts/error-factory.js +0 -174
- package/dist/_contracts/event-envelope.d.ts +0 -471
- package/dist/_contracts/event-envelope.js +0 -501
- package/dist/_contracts/event-stream-client.d.ts +0 -122
- package/dist/_contracts/event-stream-client.js +0 -445
- package/dist/_contracts/event-view.d.ts +0 -44
- package/dist/_contracts/event-view.js +0 -69
- package/dist/_contracts/failure-class.d.ts +0 -29
- package/dist/_contracts/failure-class.js +0 -73
- package/dist/_contracts/http.d.ts +0 -135
- package/dist/_contracts/http.js +0 -434
- package/dist/_contracts/ids.d.ts +0 -66
- package/dist/_contracts/ids.js +0 -119
- package/dist/_contracts/index.d.ts +0 -42
- package/dist/_contracts/index.js +0 -52
- package/dist/_contracts/internal.d.ts +0 -55
- package/dist/_contracts/internal.js +0 -113
- package/dist/_contracts/models.d.ts +0 -30
- package/dist/_contracts/models.js +0 -28
- package/dist/_contracts/operation-core.d.ts +0 -36
- package/dist/_contracts/operation-core.js +0 -70
- package/dist/_contracts/operations.d.ts +0 -218
- package/dist/_contracts/operations.js +0 -1496
- package/dist/_contracts/otlp-projection.d.ts +0 -78
- package/dist/_contracts/otlp-projection.js +0 -171
- package/dist/_contracts/post-hook.d.ts +0 -31
- package/dist/_contracts/post-hook.js +0 -61
- package/dist/_contracts/provider-fault.d.ts +0 -34
- package/dist/_contracts/provider-fault.js +0 -68
- package/dist/_contracts/retry-core.d.ts +0 -29
- package/dist/_contracts/retry-core.js +0 -79
- package/dist/_contracts/runner-event.d.ts +0 -117
- package/dist/_contracts/runner-event.js +0 -172
- package/dist/_contracts/runtime-kind.d.ts +0 -60
- package/dist/_contracts/runtime-kind.js +0 -70
- package/dist/_contracts/runtime-manifest.d.ts +0 -121
- package/dist/_contracts/runtime-manifest.js +0 -83
- package/dist/_contracts/runtime-security-profile.d.ts +0 -26
- package/dist/_contracts/runtime-security-profile.js +0 -73
- package/dist/_contracts/runtime-sizes.d.ts +0 -104
- package/dist/_contracts/runtime-sizes.js +0 -111
- package/dist/_contracts/runtime-types.d.ts +0 -618
- package/dist/_contracts/runtime-types.js +0 -58
- package/dist/_contracts/schemas/asset-bundle.d.ts +0 -70
- package/dist/_contracts/schemas/asset-bundle.js +0 -107
- package/dist/_contracts/schemas/asset-ref.d.ts +0 -61
- package/dist/_contracts/schemas/asset-ref.js +0 -118
- package/dist/_contracts/schemas/bundle-manifest.d.ts +0 -66
- package/dist/_contracts/schemas/bundle-manifest.js +0 -77
- package/dist/_contracts/schemas/index.d.ts +0 -32
- package/dist/_contracts/schemas/index.js +0 -30
- package/dist/_contracts/schemas/mcp-server.d.ts +0 -99
- package/dist/_contracts/schemas/mcp-server.js +0 -209
- package/dist/_contracts/schemas/models.d.ts +0 -29
- package/dist/_contracts/schemas/models.js +0 -51
- package/dist/_contracts/schemas/numeric.d.ts +0 -18
- package/dist/_contracts/schemas/numeric.js +0 -28
- package/dist/_contracts/schemas/post-hook.d.ts +0 -45
- package/dist/_contracts/schemas/post-hook.js +0 -68
- package/dist/_contracts/schemas/response-assets.d.ts +0 -75
- package/dist/_contracts/schemas/response-assets.js +0 -81
- package/dist/_contracts/schemas/response-billing.d.ts +0 -208
- package/dist/_contracts/schemas/response-billing.js +0 -139
- package/dist/_contracts/schemas/response-common.d.ts +0 -132
- package/dist/_contracts/schemas/response-common.js +0 -162
- package/dist/_contracts/schemas/response-identity.d.ts +0 -648
- package/dist/_contracts/schemas/response-identity.js +0 -131
- package/dist/_contracts/schemas/response-mcp-servers.d.ts +0 -51
- package/dist/_contracts/schemas/response-mcp-servers.js +0 -32
- package/dist/_contracts/schemas/response-secrets.d.ts +0 -50
- package/dist/_contracts/schemas/response-secrets.js +0 -32
- package/dist/_contracts/schemas/response-sessions-internal.d.ts +0 -200
- package/dist/_contracts/schemas/response-sessions-internal.js +0 -142
- package/dist/_contracts/schemas/response-sessions.d.ts +0 -1598
- package/dist/_contracts/schemas/response-sessions.js +0 -377
- package/dist/_contracts/schemas/response-webhooks.d.ts +0 -76
- package/dist/_contracts/schemas/response-webhooks.js +0 -42
- package/dist/_contracts/schemas/response-workspace.d.ts +0 -225
- package/dist/_contracts/schemas/response-workspace.js +0 -99
- package/dist/_contracts/schemas/runtime-kind.d.ts +0 -31
- package/dist/_contracts/schemas/runtime-kind.js +0 -29
- package/dist/_contracts/schemas/runtime-security-profile.d.ts +0 -28
- package/dist/_contracts/schemas/runtime-security-profile.js +0 -26
- package/dist/_contracts/schemas/runtime-sizes.d.ts +0 -70
- package/dist/_contracts/schemas/runtime-sizes.js +0 -127
- package/dist/_contracts/schemas/session-limits.d.ts +0 -34
- package/dist/_contracts/schemas/session-limits.js +0 -39
- package/dist/_contracts/schemas/session-machine.d.ts +0 -23
- package/dist/_contracts/schemas/session-machine.js +0 -24
- package/dist/_contracts/schemas/session-request-config.d.ts +0 -58
- package/dist/_contracts/schemas/session-request-config.js +0 -134
- package/dist/_contracts/schemas/session-webhook.d.ts +0 -11
- package/dist/_contracts/schemas/session-webhook.js +0 -38
- package/dist/_contracts/schemas/side-effect-audit.d.ts +0 -98
- package/dist/_contracts/schemas/side-effect-audit.js +0 -102
- package/dist/_contracts/schemas/submission-assets.d.ts +0 -117
- package/dist/_contracts/schemas/submission-assets.js +0 -147
- package/dist/_contracts/schemas/submission-body.d.ts +0 -251
- package/dist/_contracts/schemas/submission-body.js +0 -378
- package/dist/_contracts/schemas/submission-environment.d.ts +0 -79
- package/dist/_contracts/schemas/submission-environment.js +0 -179
- package/dist/_contracts/schemas/submission-request.d.ts +0 -158
- package/dist/_contracts/schemas/submission-request.js +0 -49
- package/dist/_contracts/schemas/submission-secrets.d.ts +0 -47
- package/dist/_contracts/schemas/submission-secrets.js +0 -108
- package/dist/_contracts/schemas/wire.d.ts +0 -118
- package/dist/_contracts/schemas/wire.js +0 -171
- package/dist/_contracts/schemas/workspace-resources.d.ts +0 -50
- package/dist/_contracts/schemas/workspace-resources.js +0 -87
- package/dist/_contracts/sdk-errors.d.ts +0 -212
- package/dist/_contracts/sdk-errors.js +0 -313
- package/dist/_contracts/sdk-secrets.d.ts +0 -67
- package/dist/_contracts/sdk-secrets.js +0 -427
- package/dist/_contracts/session-archive.d.ts +0 -16
- package/dist/_contracts/session-archive.js +0 -92
- package/dist/_contracts/session-artifacts.d.ts +0 -189
- package/dist/_contracts/session-artifacts.js +0 -264
- package/dist/_contracts/session-config.d.ts +0 -373
- package/dist/_contracts/session-config.js +0 -562
- package/dist/_contracts/session-cost-types.d.ts +0 -211
- package/dist/_contracts/session-cost-types.js +0 -69
- package/dist/_contracts/session-cost.d.ts +0 -8
- package/dist/_contracts/session-cost.js +0 -582
- package/dist/_contracts/session-custody.d.ts +0 -165
- package/dist/_contracts/session-custody.js +0 -345
- package/dist/_contracts/session-file-query.d.ts +0 -14
- package/dist/_contracts/session-file-query.js +0 -178
- package/dist/_contracts/session-record.d.ts +0 -112
- package/dist/_contracts/session-record.js +0 -165
- package/dist/_contracts/session-retention.d.ts +0 -201
- package/dist/_contracts/session-retention.js +0 -450
- package/dist/_contracts/side-effect-audit.d.ts +0 -126
- package/dist/_contracts/side-effect-audit.js +0 -520
- package/dist/_contracts/sse.d.ts +0 -74
- package/dist/_contracts/sse.js +0 -227
- package/dist/_contracts/stable.d.ts +0 -45
- package/dist/_contracts/stable.js +0 -62
- package/dist/_contracts/status.d.ts +0 -25
- package/dist/_contracts/status.js +0 -57
- package/dist/_contracts/submission-limits.d.ts +0 -61
- package/dist/_contracts/submission-limits.js +0 -60
- package/dist/_contracts/submission.d.ts +0 -547
- package/dist/_contracts/submission.js +0 -812
- package/dist/_contracts/suggest.d.ts +0 -15
- package/dist/_contracts/suggest.js +0 -53
- package/dist/_contracts/testing/response-bindings.d.ts +0 -45
- package/dist/_contracts/testing/response-bindings.js +0 -256
- package/dist/_contracts/testing/wire-conformance-entry.d.ts +0 -10
- package/dist/_contracts/testing/wire-conformance-entry.js +0 -8
- package/dist/_contracts/testing/wire-conformance.d.ts +0 -169
- package/dist/_contracts/testing/wire-conformance.js +0 -276
- package/dist/_contracts/turn-trace.d.ts +0 -28
- package/dist/_contracts/turn-trace.js +0 -1
- package/dist/_contracts/unknown-field-error.d.ts +0 -13
- package/dist/_contracts/unknown-field-error.js +0 -21
- package/dist/_contracts/value-guards.d.ts +0 -20
- package/dist/_contracts/value-guards.js +0 -34
- package/dist/_contracts/webhook-verify.d.ts +0 -34
- package/dist/_contracts/webhook-verify.js +0 -93
- package/dist/_contracts/wire-observer.d.ts +0 -49
- package/dist/_contracts/wire-observer.js +0 -34
- package/dist/_contracts/workflow-status.d.ts +0 -7
- package/dist/_contracts/workflow-status.js +0 -43
- package/dist/_contracts/workspace-resources.d.ts +0 -98
- package/dist/_contracts/workspace-resources.js +0 -39
- package/dist/archive-limits.d.ts +0 -1
- package/dist/archive-limits.js +0 -2
- package/dist/archive-limits.js.map +0 -1
- package/dist/asset-upload.d.ts +0 -47
- package/dist/asset-upload.js +0 -269
- package/dist/asset-upload.js.map +0 -1
- package/dist/bundle.d.ts +0 -9
- package/dist/bundle.js +0 -20
- package/dist/bundle.js.map +0 -1
- package/dist/canonical-zip.d.ts +0 -68
- package/dist/canonical-zip.js +0 -355
- package/dist/canonical-zip.js.map +0 -1
- package/dist/cli.mjs +0 -12048
- package/dist/cli.mjs.sha256 +0 -1
- package/dist/client-types.d.ts +0 -192
- package/dist/client-types.js +0 -2
- package/dist/client-types.js.map +0 -1
- package/dist/client.d.ts +0 -464
- package/dist/client.js +0 -1207
- package/dist/client.js.map +0 -1
- package/dist/event-projection.d.ts +0 -22
- package/dist/event-projection.js +0 -380
- package/dist/event-projection.js.map +0 -1
- package/dist/fetch-archive.d.ts +0 -16
- package/dist/fetch-archive.js +0 -252
- package/dist/fetch-archive.js.map +0 -1
- package/dist/file.d.ts +0 -96
- package/dist/file.js +0 -272
- package/dist/file.js.map +0 -1
- package/dist/instructions.d.ts +0 -20
- package/dist/instructions.js +0 -40
- package/dist/instructions.js.map +0 -1
- package/dist/legacy-session-provider-fault.d.ts +0 -7
- package/dist/legacy-session-provider-fault.js +0 -38
- package/dist/legacy-session-provider-fault.js.map +0 -1
- package/dist/mcp-server.d.ts +0 -84
- package/dist/mcp-server.js +0 -117
- package/dist/mcp-server.js.map +0 -1
- package/dist/node-fs.d.ts +0 -29
- package/dist/node-fs.js +0 -19
- package/dist/node-fs.js.map +0 -1
- package/dist/node-walk.d.ts +0 -69
- package/dist/node-walk.js +0 -151
- package/dist/node-walk.js.map +0 -1
- package/dist/path-basename.d.ts +0 -5
- package/dist/path-basename.js +0 -9
- package/dist/path-basename.js.map +0 -1
- package/dist/retry.d.ts +0 -70
- package/dist/retry.js +0 -155
- package/dist/retry.js.map +0 -1
- package/dist/secret.d.ts +0 -65
- package/dist/secret.js +0 -110
- package/dist/secret.js.map +0 -1
- package/dist/session-validate.d.ts +0 -100
- package/dist/session-validate.js +0 -303
- package/dist/session-validate.js.map +0 -1
- package/dist/skill.d.ts +0 -99
- package/dist/skill.js +0 -169
- package/dist/skill.js.map +0 -1
- package/dist/submission-wire.d.ts +0 -13
- package/dist/submission-wire.js +0 -69
- package/dist/submission-wire.js.map +0 -1
- package/dist/tool.d.ts +0 -41
- package/dist/tool.js +0 -76
- package/dist/tool.js.map +0 -1
- package/dist/version.d.ts +0 -9
- package/dist/version.js +0 -10
- package/dist/version.js.map +0 -1
- package/docs/authentication.md +0 -125
- package/docs/billing.md +0 -164
- package/docs/cleanup.md +0 -27
- package/docs/concepts/agent-tools.md +0 -47
- package/docs/concepts/composition.md +0 -60
- package/docs/concepts/providers-and-runtimes.md +0 -121
- package/docs/concepts/sessions.md +0 -51
- package/docs/concepts/subagents.md +0 -35
- package/docs/credentials.md +0 -116
- package/docs/defaults.md +0 -51
- package/docs/errors.md +0 -258
- package/docs/events.md +0 -143
- package/docs/files.md +0 -130
- package/docs/limits-and-quotas.md +0 -114
- package/docs/limits.md +0 -51
- package/docs/mcp.md +0 -47
- package/docs/networking.md +0 -114
- package/docs/provider-runtime-capabilities.md +0 -32
- package/docs/public-surface.json +0 -73
- package/docs/quickstart.md +0 -135
- package/docs/release.md +0 -44
- package/docs/retries.md +0 -108
- package/docs/secrets.md +0 -141
- package/docs/session-config.md +0 -51
- package/docs/session-record.md +0 -58
- package/docs/skills.md +0 -65
- package/docs/telemetry.md +0 -66
- package/docs/testing.md +0 -35
- package/docs/vision-skills.md +0 -94
- package/docs/webhooks.md +0 -143
|
@@ -1,562 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session-request config and composition refs for the public SDK/CLI surface.
|
|
3
|
-
*
|
|
4
|
-
* Public composition concepts:
|
|
5
|
-
*
|
|
6
|
-
* - `SkillRef` is the PUBLIC wire-level reference to a workspace skill: just
|
|
7
|
-
* `{ kind:"skill", name }`. The binding is BY NAME and mutable — the session
|
|
8
|
-
* resolves the name to the workspace skill's current bytes at submit time.
|
|
9
|
-
* It travels in `submission.skills` (NOT `submission.tools`).
|
|
10
|
-
*
|
|
11
|
-
* - `McpServerRef` is the non-secret part of an MCP server declaration:
|
|
12
|
-
* `name` and `url`. Bearer / cookie / per-request headers travel in
|
|
13
|
-
* the session's vaulted `secrets.mcpServers` block keyed by the same
|
|
14
|
-
* `name`, and never enter the hashed submission payload or the
|
|
15
|
-
* session snapshot.
|
|
16
|
-
*
|
|
17
|
-
* - `SessionRequestConfig` is the credential-free set of session parameters that
|
|
18
|
-
* can be persisted to disk (e.g. `aex start --config session.json`) or
|
|
19
|
-
* returned from ordinary application helper functions. It excludes
|
|
20
|
-
* `secrets`/`idempotencyKey`/`signal`; strings are already resolved at
|
|
21
|
-
* the call site before submission.
|
|
22
|
-
*
|
|
23
|
-
* - Skill bundle validation lives here so the SDK (zipping locally),
|
|
24
|
-
* hosted API (server-side unzip + manifest extraction) and runtime
|
|
25
|
-
* mount layer share a single source of truth for
|
|
26
|
-
* the limits, the path normaliser, and the manifest invariants. The
|
|
27
|
-
* DB CHECK constraints on `skill_bundles.manifest` mirror these.
|
|
28
|
-
*
|
|
29
|
-
* Keep this as the public source of truth for the SDK/CLI composition
|
|
30
|
-
* boundary.
|
|
31
|
-
*/
|
|
32
|
-
import { rethrowContractParseError, withContractParseError } from "./contract-parse-error.js";
|
|
33
|
-
import { ASSET_ID_PATTERN, MOUNT_PATH_MAX_LENGTH, MOUNT_PATH_PATTERN, assertValidMountPath, normalizeAssetRef, parseAssetRefWire } from "./schemas/asset-ref.js";
|
|
34
|
-
import { MCP_SERVER_NAME_PATTERN, REMOTE_MCP_STDIO_REJECTED_MESSAGE, normalizeMcpServerRef, parseMcpServerRefWire, rejectStdioMcpShape } from "./schemas/mcp-server.js";
|
|
35
|
-
import { parseSessionRequestConfigWire } from "./schemas/session-request-config.js";
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
// Skill ID + name format
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
/**
|
|
40
|
-
* Mirrors the server-side CHECK constraint
|
|
41
|
-
* `skill_bundles_id_format_chk = check (id ~ '^skl_[A-Za-z0-9_-]{8,128}$')`
|
|
42
|
-
* on persisted skill bundles. Keep the two in lockstep.
|
|
43
|
-
*/
|
|
44
|
-
export const SKILL_ID_PATTERN = /^skl_[A-Za-z0-9_-]{8,128}$/;
|
|
45
|
-
/**
|
|
46
|
-
* Human-readable, workspace-scoped name. Lowercase, kebab-friendly,
|
|
47
|
-
* 1..128 chars. The DB enforces the length bound via
|
|
48
|
-
* `skill_bundles_name_len_chk`; this regex tightens the SDK/CLI input
|
|
49
|
-
* surface so callers fail at the boundary rather than in the BFF.
|
|
50
|
-
*/
|
|
51
|
-
export const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{0,127}$/;
|
|
52
|
-
/**
|
|
53
|
-
* Provider-safe submitted tool name. Tool names share the same lowercase
|
|
54
|
-
* kebab/underscore envelope as skills. Submission parsing rejects `__`
|
|
55
|
-
* because SessionDO reserves that separator for MCP namespace routing.
|
|
56
|
-
*/
|
|
57
|
-
export const TOOL_NAME_PATTERN = SKILL_NAME_PATTERN;
|
|
58
|
-
/**
|
|
59
|
-
* Names reserved by the skills subsystem and therefore usable as neither a
|
|
60
|
-
* skill name nor a custom tool name. `skills` is the injected meta-tool (see
|
|
61
|
-
* {@link SKILLS_TOOL_NAME} in `submission.ts`); `skill` is its singular. Both
|
|
62
|
-
* the SDK factories and the BFF `parseSkills` / `parseTools` reject these.
|
|
63
|
-
*/
|
|
64
|
-
export const SKILL_RESERVED_NAMES = new Set(["skills", "skill"]);
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
// Runtime asset archive limits
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
/**
|
|
69
|
-
* One honest envelope for every file, skill, tool, or instruction archive that
|
|
70
|
-
* the managed runtime mounts. Raw asset storage is a broader concept and may
|
|
71
|
-
* have a different quota; these limits describe usable runtime inputs.
|
|
72
|
-
*/
|
|
73
|
-
export const ASSET_ARCHIVE_LIMITS = {
|
|
74
|
-
/**
|
|
75
|
-
* 16 MiB. Sized to the SHARED api Lambda (512 MB) that `GetObject`s and
|
|
76
|
-
* DEFLATE-decompresses every pinned archive on every submit, NOT to the
|
|
77
|
-
* single-tenant sandbox. Raisable once that preflight moves off shared infra.
|
|
78
|
-
*/
|
|
79
|
-
maxCompressedBytes: 16 * 1024 * 1024,
|
|
80
|
-
maxDecompressedBytes: 128 * 1024 * 1024,
|
|
81
|
-
maxEntries: 1_000,
|
|
82
|
-
maxMetadataBytes: 8 * 1024 * 1024
|
|
83
|
-
};
|
|
84
|
-
/** Skill-specific authoring constraints layered on the shared asset envelope. */
|
|
85
|
-
export const SKILL_BUNDLE_LIMITS = {
|
|
86
|
-
/** Compressed (.zip) ceiling. */
|
|
87
|
-
maxCompressedBytes: ASSET_ARCHIVE_LIMITS.maxCompressedBytes,
|
|
88
|
-
/** Sum of uncompressed file sizes. */
|
|
89
|
-
maxDecompressedBytes: ASSET_ARCHIVE_LIMITS.maxDecompressedBytes,
|
|
90
|
-
/** Number of regular file entries (directories don't count). */
|
|
91
|
-
maxFiles: ASSET_ARCHIVE_LIMITS.maxEntries,
|
|
92
|
-
/** Single-entry path length cap: `PATH_MAX`, the only real ceiling. */
|
|
93
|
-
maxPathLength: 4096,
|
|
94
|
-
/** Stored file mode for ordinary files. */
|
|
95
|
-
defaultFileMode: 0o644,
|
|
96
|
-
/** Stored directory mode. */
|
|
97
|
-
defaultDirMode: 0o755
|
|
98
|
-
};
|
|
99
|
-
/** Content-hash format: `sha256:<64 lowercase hex>`. */
|
|
100
|
-
export { CANONICAL_SHA256_DIGEST_PATTERN as INLINE_CONTENT_HASH_PATTERN } from "./canonical-sha256.js";
|
|
101
|
-
/**
|
|
102
|
-
* Checks the `AssetRef` discriminator on an already typed file reference.
|
|
103
|
-
*
|
|
104
|
-
* This compatibility predicate is not full validation for an untrusted value;
|
|
105
|
-
* use the owning parser when the complete asset-ref shape must be validated.
|
|
106
|
-
*/
|
|
107
|
-
export function isAssetRef(ref) {
|
|
108
|
-
return ref.kind === "asset";
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Asset ids are storage-neutral product ids. Current uploads derive the id from
|
|
112
|
-
* the content digest (`asset_<sha256hex>`), but callers must treat it as opaque.
|
|
113
|
-
*
|
|
114
|
-
* Declared in `schemas/asset-ref.ts` next to the schema that enforces it — this
|
|
115
|
-
* module imports that one, so it cannot live here without a cycle.
|
|
116
|
-
*/
|
|
117
|
-
export { ASSET_ID_PATTERN };
|
|
118
|
-
/** @deprecated Use `isAssetRef`. This direct alias remains for public compatibility. */
|
|
119
|
-
export const isFileAssetRef = isAssetRef;
|
|
120
|
-
/**
|
|
121
|
-
* The default mount DIRECTORY a `File` unzips into when the caller does not set
|
|
122
|
-
* `mountPath`. `/workspace` is also the agent's default working directory, so a
|
|
123
|
-
* file handed with no `mountPath` lands directly in the agent's cwd (e.g.
|
|
124
|
-
* `/workspace/source-video-subtitles.srt`).
|
|
125
|
-
*/
|
|
126
|
-
export const DEFAULT_FILE_MOUNT_PATH = "/workspace";
|
|
127
|
-
/**
|
|
128
|
-
* A `mountPath` is an ABSOLUTE container directory under the workspace, and
|
|
129
|
-
* {@link assertValidMountPath} is the assert the SDK `File` builders and the BFF
|
|
130
|
-
* asset-ref parser share so both reject the same malformed input.
|
|
131
|
-
*
|
|
132
|
-
* All three are declared in `schemas/asset-ref.ts`, next to the schema that
|
|
133
|
-
* enforces them — this module imports that one, so they cannot live here without
|
|
134
|
-
* a cycle.
|
|
135
|
-
*/
|
|
136
|
-
export { MOUNT_PATH_PATTERN, MOUNT_PATH_MAX_LENGTH, assertValidMountPath };
|
|
137
|
-
/**
|
|
138
|
-
* Common parser for any `kind: "asset"` ref (file / tool bundle).
|
|
139
|
-
*/
|
|
140
|
-
export function parseAssetRefFields(record, path) {
|
|
141
|
-
return withContractParseError("parseAssetRefFields", () => normalizeAssetRef(parseAssetRefWire(record, path)));
|
|
142
|
-
}
|
|
143
|
-
export class SkillBundleValidationError extends Error {
|
|
144
|
-
constructor(message) {
|
|
145
|
-
super(message);
|
|
146
|
-
this.name = "SkillBundleValidationError";
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Reject input paths that try to escape the bundle root or smuggle
|
|
151
|
-
* platform-specific syntax. Returns the canonical forward-slash
|
|
152
|
-
* relative path; never returns paths starting or ending with `/`.
|
|
153
|
-
*
|
|
154
|
-
* Rejects:
|
|
155
|
-
* - empty strings and pure whitespace
|
|
156
|
-
* - absolute paths (`/foo`, `C:\foo`, `\\server\share`)
|
|
157
|
-
* - backslash separators (Windows)
|
|
158
|
-
* - `..` segments anywhere in the path
|
|
159
|
-
* - `.` segments anywhere except a leading bare `.`
|
|
160
|
-
* - paths whose length exceeds `SKILL_BUNDLE_LIMITS.maxPathLength` (PATH_MAX, 4096)
|
|
161
|
-
* - NUL bytes
|
|
162
|
-
*
|
|
163
|
-
* There is deliberately NO nesting-depth cap (review-2026-07-25 plan 04): traversal is
|
|
164
|
-
* already dead structurally via the segment rules above, so a depth limit refused
|
|
165
|
-
* legitimate layouts while protecting nothing.
|
|
166
|
-
*/
|
|
167
|
-
export function normaliseSkillBundlePath(input) {
|
|
168
|
-
if (typeof input !== "string") {
|
|
169
|
-
throw new SkillBundleValidationError("bundle entry path must be a string");
|
|
170
|
-
}
|
|
171
|
-
if (input.length === 0 || input.trim().length === 0) {
|
|
172
|
-
throw new SkillBundleValidationError("bundle entry path must be non-empty");
|
|
173
|
-
}
|
|
174
|
-
if (input.length > SKILL_BUNDLE_LIMITS.maxPathLength) {
|
|
175
|
-
throw new SkillBundleValidationError(`bundle entry path exceeds maxPathLength (${SKILL_BUNDLE_LIMITS.maxPathLength}): ${input}`);
|
|
176
|
-
}
|
|
177
|
-
if (input.includes("\0")) {
|
|
178
|
-
throw new SkillBundleValidationError(`bundle entry path contains NUL byte: ${JSON.stringify(input)}`);
|
|
179
|
-
}
|
|
180
|
-
if (input.includes("\\")) {
|
|
181
|
-
throw new SkillBundleValidationError(`bundle entry path uses backslash separator: ${input}`);
|
|
182
|
-
}
|
|
183
|
-
if (/^[A-Za-z]:[\\/]/.test(input)) {
|
|
184
|
-
throw new SkillBundleValidationError(`bundle entry path uses a drive letter: ${input}`);
|
|
185
|
-
}
|
|
186
|
-
if (input.startsWith("/")) {
|
|
187
|
-
throw new SkillBundleValidationError(`bundle entry path must be relative: ${input}`);
|
|
188
|
-
}
|
|
189
|
-
// Reject trailing slash so callers cannot disguise directory entries
|
|
190
|
-
// as files. The manifest is files-only.
|
|
191
|
-
if (input.endsWith("/")) {
|
|
192
|
-
throw new SkillBundleValidationError(`bundle entry path must not end with '/': ${input}`);
|
|
193
|
-
}
|
|
194
|
-
const segments = input.split("/");
|
|
195
|
-
for (const segment of segments) {
|
|
196
|
-
if (segment === "..") {
|
|
197
|
-
throw new SkillBundleValidationError(`bundle entry path contains '..' segment: ${input}`);
|
|
198
|
-
}
|
|
199
|
-
if (segment === "." || segment === "") {
|
|
200
|
-
throw new SkillBundleValidationError(`bundle entry path contains empty or '.' segment: ${input}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
// NO nesting-depth cap (review-2026-07-25 plan 04). Traversal is already dead
|
|
204
|
-
// STRUCTURALLY above — `..`, `.`, empty, absolute, drive-letter and backslash
|
|
205
|
-
// segments are each rejected outright — so a depth bound protected nothing and
|
|
206
|
-
// only refused legitimately deep trees.
|
|
207
|
-
return input;
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Validate one manifest entry: normalises the path, bounds the size,
|
|
211
|
-
* and sanitises the mode to one of {defaultFileMode, defaultDirMode}.
|
|
212
|
-
* The bundle is files-only, so any non-regular-file entry is rejected
|
|
213
|
-
* upstream by the caller (zip parser must skip symlinks, device files,
|
|
214
|
-
* etc. before reaching this function).
|
|
215
|
-
*/
|
|
216
|
-
export function parseSkillBundleEntry(input) {
|
|
217
|
-
try {
|
|
218
|
-
const path = normaliseSkillBundlePath(input.path);
|
|
219
|
-
if (!Number.isFinite(input.size) || !Number.isInteger(input.size) || input.size < 0) {
|
|
220
|
-
throw new SkillBundleValidationError(`bundle entry size must be a non-negative integer (${path})`);
|
|
221
|
-
}
|
|
222
|
-
if (input.size > SKILL_BUNDLE_LIMITS.maxDecompressedBytes) {
|
|
223
|
-
throw new SkillBundleValidationError(`bundle entry size exceeds maxDecompressedBytes (${SKILL_BUNDLE_LIMITS.maxDecompressedBytes}): ${path}`);
|
|
224
|
-
}
|
|
225
|
-
// Sanitise the stored mode. Executable bit is implied by runtime
|
|
226
|
-
// convention; we never persist arbitrary chmod from the user's FS.
|
|
227
|
-
const mode = (input.mode ?? SKILL_BUNDLE_LIMITS.defaultFileMode) & 0o777;
|
|
228
|
-
if (mode !== SKILL_BUNDLE_LIMITS.defaultFileMode && mode !== SKILL_BUNDLE_LIMITS.defaultDirMode) {
|
|
229
|
-
return { path, size: input.size, mode: SKILL_BUNDLE_LIMITS.defaultFileMode };
|
|
230
|
-
}
|
|
231
|
-
return { path, size: input.size, mode };
|
|
232
|
-
}
|
|
233
|
-
catch (error) {
|
|
234
|
-
rethrowContractParseError(error, "parseSkillBundleEntry");
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
/** @deprecated Use {@link parseSkillBundleEntry}; this compatibility wrapper is identical. */
|
|
238
|
-
export function validateSkillBundleEntry(input) {
|
|
239
|
-
return parseSkillBundleEntry(input);
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Validate a full **skill bundle** manifest. Enforces:
|
|
243
|
-
* - entries is a non-empty array
|
|
244
|
-
* - `SKILL.md` exists at the bundle root (this is what makes a
|
|
245
|
-
* bundle a skill rather than a plain workspace file)
|
|
246
|
-
* - file count <= maxFiles
|
|
247
|
-
* - total uncompressed size <= maxDecompressedBytes
|
|
248
|
-
* - per-entry validation (see `validateSkillBundleEntry`)
|
|
249
|
-
* - no duplicate paths
|
|
250
|
-
*
|
|
251
|
-
* In this public surface, **skill** means "Claude Skill" — bundles without
|
|
252
|
-
* `SKILL.md` are not skills and must go
|
|
253
|
-
* through workspace instructions or file uploads instead.
|
|
254
|
-
*
|
|
255
|
-
* Returns a canonical manifest with totals computed.
|
|
256
|
-
*/
|
|
257
|
-
export function parseSkillBundleManifest(input) {
|
|
258
|
-
try {
|
|
259
|
-
if (!Array.isArray(input) || input.length === 0) {
|
|
260
|
-
throw new SkillBundleValidationError("bundle manifest must be a non-empty array of entries");
|
|
261
|
-
}
|
|
262
|
-
if (input.length > SKILL_BUNDLE_LIMITS.maxFiles) {
|
|
263
|
-
throw new SkillBundleValidationError(`bundle exceeds maxFiles (${SKILL_BUNDLE_LIMITS.maxFiles}): got ${input.length}`);
|
|
264
|
-
}
|
|
265
|
-
const seen = new Set();
|
|
266
|
-
const entries = [];
|
|
267
|
-
let totalSize = 0;
|
|
268
|
-
let hasSkillMd = false;
|
|
269
|
-
for (const raw of input) {
|
|
270
|
-
const entry = parseSkillBundleEntry(raw);
|
|
271
|
-
if (seen.has(entry.path)) {
|
|
272
|
-
throw new SkillBundleValidationError(`bundle manifest contains duplicate path: ${entry.path}`);
|
|
273
|
-
}
|
|
274
|
-
seen.add(entry.path);
|
|
275
|
-
if (entry.path === "SKILL.md") {
|
|
276
|
-
hasSkillMd = true;
|
|
277
|
-
}
|
|
278
|
-
totalSize += entry.size;
|
|
279
|
-
if (totalSize > SKILL_BUNDLE_LIMITS.maxDecompressedBytes) {
|
|
280
|
-
throw new SkillBundleValidationError(`bundle total size exceeds maxDecompressedBytes (${SKILL_BUNDLE_LIMITS.maxDecompressedBytes})`);
|
|
281
|
-
}
|
|
282
|
-
entries.push(entry);
|
|
283
|
-
}
|
|
284
|
-
if (!hasSkillMd) {
|
|
285
|
-
throw new SkillBundleValidationError("skill bundle manifest must contain a 'SKILL.md' entry at the bundle root. " +
|
|
286
|
-
"If you want to upload an instructions file or generic agent context, use " +
|
|
287
|
-
"workspace instructions or File instead.");
|
|
288
|
-
}
|
|
289
|
-
return { entries, totalSize, fileCount: entries.length };
|
|
290
|
-
}
|
|
291
|
-
catch (error) {
|
|
292
|
-
rethrowContractParseError(error, "parseSkillBundleManifest");
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
/** @deprecated Use {@link parseSkillBundleManifest}; this compatibility wrapper is identical. */
|
|
296
|
-
export function validateSkillBundleManifest(input) {
|
|
297
|
-
return parseSkillBundleManifest(input);
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Returns true when the manifest carries a `SKILL.md` entry at the
|
|
301
|
-
* bundle root. The presence of this file is Anthropic's
|
|
302
|
-
* skill-auto-discovery signal — bundles that have it are treated as
|
|
303
|
-
* Claude skills and mounted accordingly; bundles that don't are still
|
|
304
|
-
* usable agent context (AGENTS.md, settings files, folders of helper
|
|
305
|
-
* data) but the agent won't pick them up via the skills mechanism.
|
|
306
|
-
*
|
|
307
|
-
* The check is intentionally a separate, callable predicate (rather
|
|
308
|
-
* than baked into `validateSkillBundleManifest`) so the storage and
|
|
309
|
-
* the attach layers can remain independent.
|
|
310
|
-
*/
|
|
311
|
-
export function hasSkillMdAtRoot(manifest) {
|
|
312
|
-
return manifest.entries.some((entry) => entry.path === "SKILL.md");
|
|
313
|
-
}
|
|
314
|
-
// ---------------------------------------------------------------------------
|
|
315
|
-
// McpServerRef (non-secret) + SessionConfigMcpServer (with optional headers)
|
|
316
|
-
// ---------------------------------------------------------------------------
|
|
317
|
-
/**
|
|
318
|
-
* Remote MCP transports Aex accepts. Both are over HTTP — `http`
|
|
319
|
-
* is the streamable-HTTP transport, `sse` is the event-stream
|
|
320
|
-
* transport. `stdio` is explicitly NOT a value here: local-process
|
|
321
|
-
* MCP is not implemented.
|
|
322
|
-
*/
|
|
323
|
-
export const REMOTE_MCP_TRANSPORTS = ["http", "sse"];
|
|
324
|
-
/**
|
|
325
|
-
* Canonical error string for any attempt to declare a stdio-shaped MCP
|
|
326
|
-
* server (`transport: "stdio"`, or a stdio-only field like `command` /
|
|
327
|
-
* `args` / `env`). Pinned in source so every surface — shared parser,
|
|
328
|
-
* SDK builder, CLI flag parser, dashboard form — surfaces the same
|
|
329
|
-
* message and a user can find it via grep.
|
|
330
|
-
*
|
|
331
|
-
* Declared in `schemas/mcp-server.ts` alongside the shape gate that raises it.
|
|
332
|
-
*/
|
|
333
|
-
export { REMOTE_MCP_STDIO_REJECTED_MESSAGE };
|
|
334
|
-
/** Declared in `schemas/mcp-server.ts`, next to the schema that enforces it. */
|
|
335
|
-
export { MCP_SERVER_NAME_PATTERN };
|
|
336
|
-
/**
|
|
337
|
-
* Parse the non-secret half of an MCP server declaration mounted at `path`.
|
|
338
|
-
*
|
|
339
|
-
* SSRF guard at the parser boundary (C4) — the platform MCP proxy relies on
|
|
340
|
-
* this validation; each branch of {@link denyReasonForMcpHost} maps to an SSRF
|
|
341
|
-
* regression test case.
|
|
342
|
-
*/
|
|
343
|
-
export function parseMcpServerRef(input, path) {
|
|
344
|
-
return withContractParseError("parseMcpServerRef", () => normalizeMcpServerRef(parseMcpServerRefWire(input, path, MCP_WIRE_POLICY)));
|
|
345
|
-
}
|
|
346
|
-
export { rejectStdioMcpShape };
|
|
347
|
-
/**
|
|
348
|
-
* The canonical private/unroutable address set — the union resolved once so the
|
|
349
|
-
* SDK, the CLI, and the platform admission path refuse the same bytes.
|
|
350
|
-
*/
|
|
351
|
-
const EGRESS_DENIED_RANGES = [
|
|
352
|
-
{ cidr: "0.0.0.0/8", family: 4, reason: "must not target unroutable IPv4 (0.0.0.0/8)" },
|
|
353
|
-
{ cidr: "10.0.0.0/8", family: 4, reason: "must not target RFC1918 IPv4 (10.0.0.0/8)" },
|
|
354
|
-
{ cidr: "127.0.0.0/8", family: 4, reason: "must not target loopback IPv4 (127.0.0.0/8)" },
|
|
355
|
-
{
|
|
356
|
-
cidr: "169.254.0.0/16",
|
|
357
|
-
family: 4,
|
|
358
|
-
reason: "must not target link-local IPv4 (169.254.0.0/16) — cloud metadata range"
|
|
359
|
-
},
|
|
360
|
-
{ cidr: "100.64.0.0/10", family: 4, reason: "must not target CGNAT IPv4 (100.64.0.0/10)" },
|
|
361
|
-
{ cidr: "198.18.0.0/15", family: 4, reason: "must not target benchmark IPv4 (198.18.0.0/15)" },
|
|
362
|
-
{ cidr: "172.16.0.0/12", family: 4, reason: "must not target RFC1918 IPv4 (172.16.0.0/12)" },
|
|
363
|
-
{ cidr: "192.168.0.0/16", family: 4, reason: "must not target RFC1918 IPv4 (192.168.0.0/16)" },
|
|
364
|
-
{ cidr: "224.0.0.0/3", family: 4, reason: "must not target multicast/reserved IPv4 (224.0.0.0/3)" },
|
|
365
|
-
{
|
|
366
|
-
cidr: "::/128",
|
|
367
|
-
family: 6,
|
|
368
|
-
reason: "must not target unspecified IPv6 (::)",
|
|
369
|
-
match: (host) => host === "::"
|
|
370
|
-
},
|
|
371
|
-
{
|
|
372
|
-
cidr: "::1/128",
|
|
373
|
-
family: 6,
|
|
374
|
-
reason: "must not target loopback IPv6 (::1)",
|
|
375
|
-
match: (host) => host === "::1" || host === "0:0:0:0:0:0:0:1"
|
|
376
|
-
},
|
|
377
|
-
{
|
|
378
|
-
cidr: "fe80::/10",
|
|
379
|
-
family: 6,
|
|
380
|
-
reason: "must not target link-local IPv6 (fe80::/10)",
|
|
381
|
-
match: (host) => /^fe[89ab][0-9a-f]?:/.test(host)
|
|
382
|
-
},
|
|
383
|
-
{
|
|
384
|
-
cidr: "fc00::/7",
|
|
385
|
-
family: 6,
|
|
386
|
-
reason: "must not target unique-local IPv6 (fc00::/7)",
|
|
387
|
-
match: (host) => /^f[cd][0-9a-f]{0,2}:/.test(host)
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
cidr: "64:ff9b::/96",
|
|
391
|
-
family: 6,
|
|
392
|
-
reason: "must not target IPv6 NAT64 prefix (64:ff9b::/96)",
|
|
393
|
-
match: (host) => host.startsWith("64:ff9b::")
|
|
394
|
-
}
|
|
395
|
-
];
|
|
396
|
-
// NOTE — `::ffff:0:0/96` is deliberately ABSENT from the table above. Go's
|
|
397
|
-
// `net.IPNet.Contains` degrades an IPv4-mapped `/96` to IPv4 `0.0.0.0/0`, so
|
|
398
|
-
// adding it to the egress proxy's deny_ranges would block ALL IPv4. It is also
|
|
399
|
-
// unnecessary: IPv4-mapped literals are folded to IPv4 and classified by the
|
|
400
|
-
// IPv4 rules above (see the `::ffff:` branch in the IP-literal classifier).
|
|
401
|
-
/** IPv4 dotted-quad → uint32, or null when `host` is not a dotted-quad. */
|
|
402
|
-
function ipv4ToUint32(host) {
|
|
403
|
-
if (!/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host))
|
|
404
|
-
return null;
|
|
405
|
-
const octets = host.split(".").map((o) => Number.parseInt(o, 10));
|
|
406
|
-
if (octets.some((o) => o > 255))
|
|
407
|
-
return null;
|
|
408
|
-
return ((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]) >>> 0;
|
|
409
|
-
}
|
|
410
|
-
/** Whether `value` (uint32) falls inside the IPv4 `cidr`. */
|
|
411
|
-
function ipv4InCidr(value, cidr) {
|
|
412
|
-
const [network, prefixText] = cidr.split("/");
|
|
413
|
-
const base = ipv4ToUint32(network);
|
|
414
|
-
if (base === null)
|
|
415
|
-
return false;
|
|
416
|
-
const prefix = Number.parseInt(prefixText, 10);
|
|
417
|
-
const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0;
|
|
418
|
-
return (value & mask) === (base & mask);
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* Reasons an IP-literal host should be refused. Returns null when the
|
|
422
|
-
* literal is a routable public address (or not an IP literal at all — name
|
|
423
|
-
* resolution is the caller's concern). This numeric-range deny-list is kept
|
|
424
|
-
* in parity across the public contract parser and platform shared parser so
|
|
425
|
-
* the MCP parser, the egress proxy handlers, and `submission.parseProxyBaseUrl`
|
|
426
|
-
* classify the same bytes.
|
|
427
|
-
*
|
|
428
|
-
* `host` is the already-bracket-stripped, lowercased hostname.
|
|
429
|
-
*
|
|
430
|
-
* NOTE — residual DNS-rebind gap: this denies IP *literals* only. A name
|
|
431
|
-
* that resolves to a private/metadata IP is NOT caught here (we don't
|
|
432
|
-
* resolve at parse time). Closing that requires resolve-then-pin at egress;
|
|
433
|
-
* that pinning is deferred. The host's outbound fetch still refuses RFC1918
|
|
434
|
-
* at connect, but not loopback/169.254/CGNAT/ULA — which is exactly why the
|
|
435
|
-
* literal checks below exist as defense in depth.
|
|
436
|
-
*/
|
|
437
|
-
function denyReasonForHostIp(host) {
|
|
438
|
-
// IPv4-mapped / IPv4-compatible IPv6 literals decode to an embedded IPv4 —
|
|
439
|
-
// classify that IPv4 so a mapped form can't smuggle a private target. Two
|
|
440
|
-
// shapes reach us: the dotted-quad form a caller may type (`::ffff:127.0.0.1`)
|
|
441
|
-
// and the hex form `new URL().hostname` normalises it to (`::ffff:7f00:1`),
|
|
442
|
-
// where the two trailing hextets ARE the four IPv4 octets.
|
|
443
|
-
const mappedDotted = /^::(?:ffff:)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(host);
|
|
444
|
-
if (mappedDotted) {
|
|
445
|
-
return denyReasonForV4(mappedDotted[1]);
|
|
446
|
-
}
|
|
447
|
-
const mappedHex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(host);
|
|
448
|
-
if (mappedHex) {
|
|
449
|
-
const hi = Number.parseInt(mappedHex[1], 16);
|
|
450
|
-
const lo = Number.parseInt(mappedHex[2], 16);
|
|
451
|
-
const dotted = `${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff}`;
|
|
452
|
-
return denyReasonForV4(dotted);
|
|
453
|
-
}
|
|
454
|
-
const v4 = denyReasonForV4(host);
|
|
455
|
-
if (v4)
|
|
456
|
-
return v4;
|
|
457
|
-
for (const range of EGRESS_DENIED_RANGES) {
|
|
458
|
-
if (range.family === 6 && range.match?.(host) === true)
|
|
459
|
-
return range.reason;
|
|
460
|
-
}
|
|
461
|
-
return null;
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* IPv4-literal deny-list, derived from {@link EGRESS_DENIED_RANGES}. Returns
|
|
465
|
-
* null when `host` is not a dotted-quad or
|
|
466
|
-
* is a routable public IPv4. Split out of {@link denyReasonForHostIp} so the
|
|
467
|
-
* IPv4-mapped IPv6 branch reuses the exact same ranges.
|
|
468
|
-
*/
|
|
469
|
-
function denyReasonForV4(host) {
|
|
470
|
-
const value = ipv4ToUint32(host);
|
|
471
|
-
if (value === null)
|
|
472
|
-
return null;
|
|
473
|
-
for (const range of EGRESS_DENIED_RANGES) {
|
|
474
|
-
if (range.family === 4 && ipv4InCidr(value, range.cidr))
|
|
475
|
-
return range.reason;
|
|
476
|
-
}
|
|
477
|
-
return null;
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* Reasons an MCP server URL should be refused at parse time. Returns null
|
|
481
|
-
* when the URL is acceptable. Hostnames are lowercased; numeric ranges
|
|
482
|
-
* are checked literally so the catch covers both names ("localhost") and
|
|
483
|
-
* IP literals ("127.0.0.1") symmetrically. The numeric-range checks
|
|
484
|
-
* delegate to {@link denyReasonForHostIp} (shared with the platform proxy).
|
|
485
|
-
*
|
|
486
|
-
* Surface tracked by server-side SSRF regression coverage.
|
|
487
|
-
*/
|
|
488
|
-
function denyReasonForMcpHost(parsed) {
|
|
489
|
-
// `new URL("https://[fe80::1]/").hostname` returns `[fe80::1]` WITH
|
|
490
|
-
// the brackets on Node 22; strip them so the IPv6 checks match either
|
|
491
|
-
// shape symmetrically.
|
|
492
|
-
const host = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
493
|
-
// Loopback name (covers `localhost` + `localhost.localdomain` etc.)
|
|
494
|
-
if (host === "localhost" || host.endsWith(".localhost")) {
|
|
495
|
-
return "must not target a loopback hostname";
|
|
496
|
-
}
|
|
497
|
-
const ipDenial = denyReasonForHostIp(host);
|
|
498
|
-
if (ipDenial)
|
|
499
|
-
return ipDenial;
|
|
500
|
-
// Port constraint: https must be on 443 (defense in depth — non-standard
|
|
501
|
-
// https ports often indicate internal services). http allowance keeps
|
|
502
|
-
// the existing local-dev pattern (e.g. host.docker.internal:8787) usable.
|
|
503
|
-
if (parsed.protocol === "https:" && parsed.port !== "" && parsed.port !== "443") {
|
|
504
|
-
return `must use port 443 for https (got ${parsed.port})`;
|
|
505
|
-
}
|
|
506
|
-
return null;
|
|
507
|
-
}
|
|
508
|
-
function parseRemoteMcpTransport(input, field) {
|
|
509
|
-
if (input === undefined) {
|
|
510
|
-
return undefined;
|
|
511
|
-
}
|
|
512
|
-
if (typeof input !== "string" || !REMOTE_MCP_TRANSPORTS.includes(input)) {
|
|
513
|
-
throw new Error(`${field} must be one of: ${REMOTE_MCP_TRANSPORTS.join(", ")} (got ${JSON.stringify(input)})`);
|
|
514
|
-
}
|
|
515
|
-
return input;
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* The two rules the MCP schemas defer back to this module for.
|
|
519
|
-
*
|
|
520
|
-
* Injected rather than imported the other way around because
|
|
521
|
-
* `scripts/cicd/check-contract-parity.mjs` locates the SSRF deny-list INSIDE
|
|
522
|
-
* this file — it slices from `denyReasonForHostIp` to `parseRemoteMcpTransport`
|
|
523
|
-
* and byte-compares that region against `platform/packages/shared/src/blueprint.ts`.
|
|
524
|
-
* Moving either function into `schemas/` would break the only check that keeps
|
|
525
|
-
* the two deny-lists identical.
|
|
526
|
-
*/
|
|
527
|
-
const MCP_WIRE_POLICY = {
|
|
528
|
-
denyReasonForHost: denyReasonForMcpHost,
|
|
529
|
-
parseTransport: parseRemoteMcpTransport
|
|
530
|
-
};
|
|
531
|
-
// ---------------------------------------------------------------------------
|
|
532
|
-
// Session request config parser (used by CLI to load `session.json`)
|
|
533
|
-
// ---------------------------------------------------------------------------
|
|
534
|
-
/**
|
|
535
|
-
* Parse a session request config from JSON. Defensive — used by the host CLI to
|
|
536
|
-
* load `--config session.json`. Throws with the JSON path that failed so
|
|
537
|
-
* a user can fix their file. Headers are preserved here and split out
|
|
538
|
-
* later by the SDK normalisation step.
|
|
539
|
-
*/
|
|
540
|
-
export function parseSessionRequestConfig(input) {
|
|
541
|
-
return withContractParseError("parseSessionRequestConfig", () => parseSessionRequestConfigWire(input, MCP_WIRE_POLICY));
|
|
542
|
-
}
|
|
543
|
-
export function normaliseSessionRequestConfig(config) {
|
|
544
|
-
const prompt = typeof config.prompt === "string" ? [config.prompt] : config.prompt;
|
|
545
|
-
const mcpServers = [];
|
|
546
|
-
const mcpServerSecrets = [];
|
|
547
|
-
for (const entry of config.mcpServers ?? []) {
|
|
548
|
-
mcpServers.push({ name: entry.name, url: entry.url });
|
|
549
|
-
if (entry.headers !== undefined) {
|
|
550
|
-
mcpServerSecrets.push({ name: entry.name, url: entry.url, headers: entry.headers });
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
return {
|
|
554
|
-
model: config.model,
|
|
555
|
-
...(config.system !== undefined ? { system: config.system } : {}),
|
|
556
|
-
prompt,
|
|
557
|
-
mcpServers,
|
|
558
|
-
...(config.environment !== undefined ? { environment: config.environment } : {}),
|
|
559
|
-
...(config.metadata !== undefined ? { metadata: config.metadata } : {}),
|
|
560
|
-
mcpServerSecrets
|
|
561
|
-
};
|
|
562
|
-
}
|