@jimhoyd/urlcode 0.4.2 → 0.4.6
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/.claude/skills/urlcode-authoring/SKILL.md +57 -14
- package/.claude/skills/urlcode-operations/SKILL.md +4 -0
- package/README.md +19 -15
- package/SECURITY.md +5 -3
- package/dist/BUILD-MANIFEST.json +30 -27
- package/dist/agent-context.js +82 -0
- package/dist/agents-guide.js +42 -42
- package/dist/authoring.js +12 -2
- package/dist/body-schema.js +159 -0
- package/dist/build-cloudflare.js +2 -0
- package/dist/capabilities.js +1 -1
- package/dist/cli.js +28 -12
- package/dist/config.js +74 -6
- package/dist/context.js +4 -6
- package/dist/errors.js +3 -1
- package/dist/examples.js +1 -1
- package/dist/extensions.js +60 -2
- package/dist/http-policy.js +19 -4
- package/dist/http-response.js +2 -2
- package/dist/init-with.js +71 -9
- package/dist/mcp.js +15 -2
- package/dist/pattern-guard.js +32 -0
- package/dist/policies/security.js +0 -0
- package/dist/project-tests.js +35 -11
- package/dist/readiness.js +184 -32
- package/dist/recipes.js +1 -1
- package/dist/router.js +17 -0
- package/dist/runtime.js +26 -3
- package/dist/scaffold.js +0 -0
- package/dist/server.js +26 -3
- package/dist/site.js +0 -0
- package/dist/tooling.js +2 -1
- package/dist/types/agent-context.d.ts +44 -0
- package/dist/types/authoring.d.ts +3 -1
- package/dist/types/body-schema.d.ts +58 -0
- package/dist/types/config.d.ts +10 -2
- package/dist/types/context.d.ts +1 -1
- package/dist/types/errors.d.ts +9 -1
- package/dist/types/examples.d.ts +1 -1
- package/dist/types/extensions.d.ts +47 -2
- package/dist/types/http-policy.d.ts +3 -0
- package/dist/types/init-with.d.ts +10 -1
- package/dist/types/pattern-guard.d.ts +10 -0
- package/dist/types/project-tests.d.ts +9 -0
- package/dist/types/readiness.d.ts +68 -0
- package/dist/types/recipes.d.ts +1 -1
- package/dist/types/runtime.d.ts +4 -0
- package/dist/types/server.d.ts +9 -1
- package/dist/types/tooling.d.ts +1 -0
- package/dist/types/types.d.ts +13 -0
- package/dist/types.js +10 -2
- package/dist/typescript-authoring.js +5 -3
- package/dist/verify-deployment.js +16 -10
- package/examples/body-validation/README.md +16 -0
- package/examples/body-validation/example.yaml +17 -0
- package/examples/body-validation/tests/requests.json +15 -0
- package/examples/body-validation/urlcode.yaml +34 -0
- package/examples/coverage-waiver/README.md +8 -0
- package/examples/coverage-waiver/example.yaml +16 -0
- package/examples/coverage-waiver/functions/notes.mjs +2 -0
- package/examples/coverage-waiver/tests/requests.json +3 -0
- package/examples/coverage-waiver/urlcode.yaml +10 -0
- package/examples/data-dir/README.md +39 -0
- package/examples/data-dir/data/welcome.txt +1 -0
- package/examples/data-dir/example.yaml +22 -0
- package/examples/data-dir/functions/note.mjs +18 -0
- package/examples/data-dir/tests/requests.json +6 -0
- package/examples/data-dir/urlcode.yaml +6 -0
- package/examples/lifecycle/README.md +15 -0
- package/examples/lifecycle/example.yaml +19 -0
- package/examples/lifecycle/functions/notes.mjs +29 -0
- package/examples/lifecycle/tests/requests.json +12 -0
- package/examples/lifecycle/urlcode.yaml +29 -0
- package/examples/not-found/README.md +10 -0
- package/examples/not-found/example.yaml +17 -0
- package/examples/not-found/public/404.html +3 -0
- package/examples/not-found/public/index.html +3 -0
- package/examples/not-found/tests/requests.json +7 -0
- package/examples/not-found/urlcode.yaml +7 -0
- package/examples/shared-blocks/README.md +11 -0
- package/examples/shared-blocks/example.yaml +17 -0
- package/examples/shared-blocks/tests/requests.json +7 -0
- package/examples/shared-blocks/urlcode.yaml +37 -0
- package/llms-full.txt +364 -49
- package/llms.txt +43 -5
- package/package.json +15 -22
- package/recipes/static-page/README.md +9 -0
- package/recipes/static-page/public/index.html +11 -0
- package/recipes/static-page/recipe.yaml +21 -0
- package/recipes/static-page/tests/requests.json +22 -0
- package/recipes/static-page/urlcode.yaml +7 -0
- package/recipes/static-plus-api/README.md +6 -0
- package/recipes/static-plus-api/urlcode.yaml +4 -0
- package/recipes/store-crud/README.md +53 -0
- package/recipes/store-crud/recipe.yaml +31 -0
- package/recipes/store-crud/tests/requests.json +18 -0
- package/recipes/store-crud/urlcode.yaml +18 -0
- package/schemas/urlcode.schema.json +125 -60
- package/skills/urlcode/SKILL.md +47 -27
- package/starters/default/AGENTS.md +43 -43
- package/starters/page/README.md +14 -0
- package/starters/page/public/index.html +12 -0
- package/starters/page/tests/requests.json +17 -0
- package/starters/page/urlcode.yaml +6 -0
- package/.claude-plugin/marketplace.json +0 -18
- package/CONTRIBUTING.md +0 -148
- package/ROADMAP.md +0 -70
- package/docs/AI-AUTHORING.md +0 -339
- package/docs/ASSETS.md +0 -107
- package/docs/AUTH-BACKUP.md +0 -32
- package/docs/AWS.md +0 -86
- package/docs/BEST-PRACTICES.md +0 -276
- package/docs/BULK.md +0 -79
- package/docs/CAPABILITIES.md +0 -192
- package/docs/CAPACITY.md +0 -305
- package/docs/CI-FOLLOWUP-2026-09-19.md +0 -97
- package/docs/CI-RELEASE-AUDIT-2026-09-19.md +0 -322
- package/docs/CI.md +0 -147
- package/docs/CLOUDFLARE.md +0 -109
- package/docs/CODEBASE-AUDIT-2026-09-20.md +0 -284
- package/docs/COMPLIANCE.md +0 -239
- package/docs/COMPOSING-A-SITE.md +0 -287
- package/docs/CONDITIONS.md +0 -74
- package/docs/CONTAINER-PROMOTION.md +0 -74
- package/docs/DEPLOYMENT-CHECKS.md +0 -108
- package/docs/DEVELOPMENT-PIPELINE.md +0 -304
- package/docs/EGRESS.md +0 -125
- package/docs/EXTENSIONS.md +0 -403
- package/docs/FRAMEWORK.md +0 -217
- package/docs/FUNCTION-SECURITY.md +0 -251
- package/docs/HTTP.md +0 -129
- package/docs/INSTALL.md +0 -128
- package/docs/INTERCHANGE.md +0 -134
- package/docs/LOAD-TESTING.md +0 -91
- package/docs/LOCAL-DEVELOPMENT.md +0 -102
- package/docs/MIDDLEWARE-EXAMPLES.md +0 -75
- package/docs/MIDDLEWARE.md +0 -102
- package/docs/MONITORING.md +0 -115
- package/docs/OBSERVABILITY.md +0 -222
- package/docs/OPEN-DECISIONS.md +0 -149
- package/docs/OPERATIONAL-PROOF.md +0 -41
- package/docs/OPERATIONS.md +0 -201
- package/docs/ORGANIZATION.md +0 -135
- package/docs/PERFORMANCE.md +0 -72
- package/docs/PLUGINS.md +0 -271
- package/docs/POLICIES.md +0 -211
- package/docs/PRERENDER.md +0 -245
- package/docs/PROJECT-DIRECTION.md +0 -118
- package/docs/PROVIDER-VERIFICATION.md +0 -84
- package/docs/READINESS.md +0 -153
- package/docs/README.md +0 -93
- package/docs/RECIPES.md +0 -99
- package/docs/RELEASE-0.4.0-alpha.3.md +0 -50
- package/docs/RELEASE-0.4.1.md +0 -73
- package/docs/RELEASE-0.4.2.md +0 -30
- package/docs/RELEASE-READINESS.md +0 -146
- package/docs/RELEASE-SECURITY.md +0 -100
- package/docs/RESILIENCE.md +0 -161
- package/docs/ROUTING.md +0 -92
- package/docs/SANDBOX-REVIEW.md +0 -72
- package/docs/SCAFFOLDING.md +0 -70
- package/docs/SECURITY-AUDIT.md +0 -164
- package/docs/SITE.md +0 -150
- package/docs/SPECIFICATION.md +0 -359
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +0 -288
- package/docs/SPIKE-BUSINESS-SUITE.md +0 -1029
- package/docs/SPIKE-CORE-LAYERING.md +0 -368
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +0 -207
- package/docs/STANDARDS.md +0 -311
- package/docs/STARTERS.md +0 -83
- package/docs/STATIC.md +0 -105
- package/docs/TOOLING.md +0 -298
- package/docs/TUNNELS.md +0 -72
- package/docs/TYPESCRIPT-AUTHORING.md +0 -87
- package/docs/TYPESCRIPT.md +0 -123
- package/docs/VERCEL.md +0 -114
- package/docs/VERSION-ALIGNMENT.md +0 -88
- package/docs/YAML-GUIDE.md +0 -57
- package/docs/YAML-REFERENCE.md +0 -449
- package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +0 -68
- package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +0 -102
- package/docs/archive/2026-09-19/NEXT-PHASE-PLAN.md +0 -108
- package/docs/archive/2026-09-19/NEXT-STEPS.md +0 -646
- package/docs/archive/2026-09-19/OPEN-DECISIONS.md +0 -277
- package/docs/archive/2026-09-19/RELEASE-SECURITY.md +0 -186
- package/docs/archive/2026-09-19/ROADMAP.md +0 -387
- package/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md +0 -430
- package/docs/archive/2026-09-19/SPIKE-EXTENSIONS.md +0 -492
- package/docs/archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md +0 -365
- package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +0 -778
- package/docs/archive/2026-09-19/USABILITY-REVIEW.md +0 -139
- package/docs/archive/2026-09-20/OPEN-DECISIONS-COMPLETED.md +0 -116
- package/docs/archive/README.md +0 -29
- package/docs/policies/agents.md +0 -182
- package/docs/policies/cache.md +0 -152
- package/docs/policies/compression.md +0 -169
- package/docs/policies/contract.md +0 -52
- package/docs/policies/hardened.md +0 -56
- package/docs/policies/interoperability.md +0 -169
- package/docs/policies/operations.md +0 -45
- package/docs/policies/security.md +0 -161
- package/docs/policies/throttle.md +0 -103
- package/docs/yaml/assets.md +0 -36
- package/docs/yaml/conditions.md +0 -20
- package/docs/yaml/functions.md +0 -168
- package/docs/yaml/middleware.md +0 -31
- package/docs/yaml/organization.md +0 -74
- package/docs/yaml/policies.md +0 -37
- package/docs/yaml/redirects.md +0 -64
- package/docs/yaml/responses.md +0 -57
- package/docs/yaml/site.md +0 -24
- package/packaging/claude-plugin/.claude-plugin/plugin.json +0 -19
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +0 -130
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +0 -108
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
# Function execution: trusted by default, sandboxed opt-in
|
|
2
|
-
|
|
3
|
-
`function` and `middleware` routes run **trusted and unsandboxed by default**:
|
|
4
|
-
in the host process, with full Node, filesystem and network access, exactly
|
|
5
|
-
like any other project code (docs/SPIKE-DEFAULT-TRUST-MODEL.md). This is a
|
|
6
|
-
deliberate, maintainer-decided reversal of alpha.2's blanket sandbox — see
|
|
7
|
-
that spike document for the full rationale. It is a call the project makes,
|
|
8
|
-
not a property the runtime can verify: URLCode cannot know whether your code
|
|
9
|
-
is safe to trust, only whether you asked for isolation.
|
|
10
|
-
|
|
11
|
-
Declare `sandbox: true` on a route when its code specifically warrants
|
|
12
|
-
isolation: it processes input from a source the project doesn't fully trust
|
|
13
|
-
(a third-party webhook payload, for example), it is a contribution nobody on
|
|
14
|
-
the team has reviewed, or it handles a secret sensitive enough that a bug in
|
|
15
|
-
that one route should not be able to reach the rest of the process or the
|
|
16
|
-
filesystem. A sandboxed route runs in QuickJS inside WebAssembly, in a
|
|
17
|
-
separate worker thread, with none of the host access described below — its
|
|
18
|
-
guarantees are unchanged from every earlier release and are described in
|
|
19
|
-
full in the rest of this document. Absence of `sandbox` (or `sandbox: false`)
|
|
20
|
-
means trusted; there is no separate `unsafe`/`trusted` field to opt back into
|
|
21
|
-
the old sandboxed-by-default behavior — set `sandbox: true` per route instead.
|
|
22
|
-
|
|
23
|
-
**Either way, binding grants are unaffected.** Trusting a route's code by
|
|
24
|
-
default does not grant it any `env`/`secrets` it was not explicitly declared
|
|
25
|
-
in YAML and approved by an operator policy pinned to the project revision
|
|
26
|
-
(see "Granting selected bindings" below). A trusted function only *can* do
|
|
27
|
-
more with Node once it runs — it does not receive anything more than a
|
|
28
|
-
sandboxed one would.
|
|
29
|
-
|
|
30
|
-
This is a claim about `context`/`context.secrets` injection, not an
|
|
31
|
-
access-control guarantee on trusted code. The binding grant governs only what
|
|
32
|
-
URLCode hands a route through `context`; it does not restrict what trusted
|
|
33
|
-
(non-`sandbox`) code can independently do, because that code has full Node
|
|
34
|
-
access by design. A trusted function can read `process.env`, open files or
|
|
35
|
-
make network calls on its own regardless of what its route was or was not
|
|
36
|
-
granted — withholding a binding grant limits what URLCode gives the code
|
|
37
|
-
through `context`, not what the code itself, running with full Node access,
|
|
38
|
-
can go and get. A sandboxed route has no such independent access: the guest
|
|
39
|
-
API is all it has, so its binding grant *is* effectively its whole reach into
|
|
40
|
-
the environment. Trusted code's reach is not bounded that way; treat the
|
|
41
|
-
grant as scoping `context`, not as scoping the process.
|
|
42
|
-
|
|
43
|
-
## Migrating to the trusted default
|
|
44
|
-
|
|
45
|
-
If you are upgrading a project from a release before this change shipped:
|
|
46
|
-
**every existing `function` and `middleware` route silently changes execution
|
|
47
|
-
mode**, from sandboxed to trusted, unless it already has (or you add)
|
|
48
|
-
`sandbox: true`. This is a real behavior change on upgrade, not a
|
|
49
|
-
documentation update — a route that used to run with no filesystem or network
|
|
50
|
-
access will, after the upgrade, run with full Node access unless you opt it
|
|
51
|
-
back into the sandbox.
|
|
52
|
-
|
|
53
|
-
Before upgrading:
|
|
54
|
-
|
|
55
|
-
- List every `function` and `middleware` route in the project.
|
|
56
|
-
- For each one, decide whether you fully trust that code to run in-process
|
|
57
|
-
with full Node/filesystem/network access — the same trust you would extend
|
|
58
|
-
to any other code you deploy to that server.
|
|
59
|
-
- Add `sandbox: true` explicitly to any route whose code you do not fully
|
|
60
|
-
trust, that processes input from a source you don't control, or that handles
|
|
61
|
-
a secret binding you want isolated — before you upgrade, not after.
|
|
62
|
-
- Routes you do want running trusted need no change; that is now the default.
|
|
63
|
-
|
|
64
|
-
The change moves the sandbox from an unconditional guarantee to an explicit,
|
|
65
|
-
per-route choice, mainly for performance: the previous blanket sandbox capped
|
|
66
|
-
concurrency at two workers with no queue shared across every function route on
|
|
67
|
-
the server, which does not scale to real concurrent traffic. It also brings
|
|
68
|
-
first-party code in line with how the rest of the Node ecosystem treats
|
|
69
|
-
deployed application code. The sandbox itself is unchanged for routes that opt
|
|
70
|
-
into it; only the default for routes that declare neither option has changed.
|
|
71
|
-
|
|
72
|
-
## What "sandboxed" (`sandbox: true`) still guarantees
|
|
73
|
-
|
|
74
|
-
- Function sources are parsed/snapshotted without importing them into Node.
|
|
75
|
-
- Code runs in QuickJS inside WebAssembly, with no host JS functions/objects
|
|
76
|
-
exposed to the guest. Request/response/context use a JSON/string boundary.
|
|
77
|
-
- No `process`, `require`, Node built-ins, filesystem, shell, sockets, fetch,
|
|
78
|
-
WebSocket, workers, native extensions or ambient environment is available.
|
|
79
|
-
- Module resolution is restricted to the route's declared middleware and function relative JavaScript
|
|
80
|
-
dependency graphs inside the project. Symlink escapes, remote/bare imports and
|
|
81
|
-
dynamic imports in source fail. Runtime-created imports cannot broaden access.
|
|
82
|
-
- A fresh guest heap/module state per invocation prevents state crossing requests.
|
|
83
|
-
- 32 MiB guest heap, 512 KiB stack, source/input/output/header limits, bounded
|
|
84
|
-
concurrency, guest interruption and an independent worker termination deadline.
|
|
85
|
-
- External bindings are denied by default. Project YAML cannot self-authorize.
|
|
86
|
-
Operator grants are exact-name, route-scoped and pinned to configuration/source.
|
|
87
|
-
|
|
88
|
-
The guest API is intentionally narrower than Node or full Fetch; see the
|
|
89
|
-
[implemented contract](SPECIFICATION.md). A function moving from trusted to
|
|
90
|
-
`sandbox: true` that uses Node/network or binary/stream APIs must be rewritten
|
|
91
|
-
for the supported guest profile, or stay trusted. Redirects need none of this
|
|
92
|
-
machinery either way.
|
|
93
|
-
|
|
94
|
-
This engine — worker spawning, the module-allowlist walk, the two-layer
|
|
95
|
-
deadline, `maxBytes` and response-shape validation — is one implementation
|
|
96
|
-
shared by route dispatch and by `@jimhoyd/urlcode/sandbox`'s `SandboxPool`.
|
|
97
|
-
That public HTTP-shaped primitive remains available to extension authors, but
|
|
98
|
-
project extension hooks use arbitrary typed values and contract v1 runs them
|
|
99
|
-
trusted in-process; it rejects `sandbox: true` rather than claiming HTTP sandbox
|
|
100
|
-
semantics apply to them. See [extensions](EXTENSIONS.md#project-level-lifecycle-hooks).
|
|
101
|
-
|
|
102
|
-
## What the trusted default can and can't do
|
|
103
|
-
|
|
104
|
-
A trusted route (no `sandbox`, or `sandbox: false`) has none of the guest
|
|
105
|
-
restrictions above:
|
|
106
|
-
|
|
107
|
-
- Full Node built-ins, `process`, the filesystem, `fetch`, sockets, workers
|
|
108
|
-
and npm packages are available, exactly as in any other Node module.
|
|
109
|
-
- Module resolution is ordinary Node ESM resolution: bare specifiers, dynamic
|
|
110
|
-
`import()` and node_modules all work. There is no dependency-graph allowlist
|
|
111
|
-
and no per-module/total source-size budget (function-sources.ts's
|
|
112
|
-
`MODULE_LIMIT`/`MODULE_BYTE_LIMIT`/`TOTAL_BYTE_LIMIT` apply only to what a
|
|
113
|
-
sandboxed snapshot bundles).
|
|
114
|
-
- Node's own module cache is shared across invocations and across the whole
|
|
115
|
-
process; there is no fresh heap per call. Module-level state persists
|
|
116
|
-
between requests exactly like an ordinary long-running Node server, so a
|
|
117
|
-
trusted function that mutates shared/global state affects later requests
|
|
118
|
-
the way hand-written server code would.
|
|
119
|
-
- There is no worker-thread deadline that force-terminates a stuck call. A
|
|
120
|
-
trusted invocation races a configurable timeout, but that race can only
|
|
121
|
-
reject the *call*; it cannot preempt code that blocks the event loop
|
|
122
|
-
synchronously. See [capacity](CAPACITY.md) for what this means for one slow
|
|
123
|
-
or hung trusted route's effect on the rest of the process.
|
|
124
|
-
- A snapshot reload re-imports a trusted route's own entry file fresh (each
|
|
125
|
-
reload gets its own cache-busted module registration), so editing the
|
|
126
|
-
`source` file a route declares and reloading picks up the change, the same
|
|
127
|
-
as the sandboxed pool rebuilding from scratch. A file that entry file
|
|
128
|
-
merely *imports* is not similarly busted: Node's own module cache is
|
|
129
|
-
keyed by the resolved URL of that import statement, which this runtime
|
|
130
|
-
does not rewrite, so an edited dependency two files deep from the route
|
|
131
|
-
keeps serving its old content until the process restarts. Restructure a
|
|
132
|
-
route so the code you expect to hot-reload is the declared entry file
|
|
133
|
-
itself, or restart rather than reload after editing a trusted route's
|
|
134
|
-
dependencies. A `sandbox: true` route has no such gap: reload always
|
|
135
|
-
rebuilds its whole snapshot, dependencies included.
|
|
136
|
-
|
|
137
|
-
What does **not** change with trust: `args` are still exactly the validated
|
|
138
|
-
values the route declares (never raw request input), and `env`/`secrets` are
|
|
139
|
-
still exactly what the route's YAML requests and an operator policy grants,
|
|
140
|
-
pinned to the project revision — trust changes where code runs, not what
|
|
141
|
-
it is handed *through `context`*. It does not change what the code can go get
|
|
142
|
-
on its own once it is running; see "binding grants are unaffected" above for
|
|
143
|
-
that distinction.
|
|
144
|
-
|
|
145
|
-
## Trusted code, not trusted requests
|
|
146
|
-
|
|
147
|
-
"Trusted" describes the code's authorship — first-party project code you
|
|
148
|
-
reviewed and deployed — not the requests it handles. Every request, in either
|
|
149
|
-
mode, still carries client-controlled path, query, header and body data that is
|
|
150
|
-
exactly as adversarial as it always was. Running trusted means that code
|
|
151
|
-
executes with full Node access if it mishandles that input; it does not mean
|
|
152
|
-
the input itself became safe to trust. Declare `parameters` and `request.body`
|
|
153
|
-
validation in YAML, check `args` and any other request data again inside
|
|
154
|
-
function/middleware code, and implement your own authentication and
|
|
155
|
-
authorization — no route, sandboxed or trusted, adds automatic auth.
|
|
156
|
-
`sandbox: true` narrows what a bug or an unreviewed dependency in the *code*
|
|
157
|
-
can do with that same request data; it is not a substitute for validating or
|
|
158
|
-
authenticating the request itself.
|
|
159
|
-
|
|
160
|
-
## Granting selected bindings
|
|
161
|
-
|
|
162
|
-
An application may request a named binding in YAML, but only an operator can
|
|
163
|
-
approve it. Inspect what the app requests without executing any module:
|
|
164
|
-
|
|
165
|
-
```sh
|
|
166
|
-
urlcode permissions --project /srv/my-links
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
This prints a proposed JSON shape with `version: 1`, `projectSha256` and `routes`.
|
|
170
|
-
It grants nothing. Review the code/configuration and keep only necessary bindings.
|
|
171
|
-
Save the policy **outside the application checkout**, in an operator-controlled
|
|
172
|
-
file; never let application authors or deployment artifacts overwrite it.
|
|
173
|
-
|
|
174
|
-
```json
|
|
175
|
-
{
|
|
176
|
-
"version": 1,
|
|
177
|
-
"projectSha256": "REPLACE_WITH_THE_REVIEWED_PROJECT_DIGEST",
|
|
178
|
-
"routes": {
|
|
179
|
-
"/customer/{id}": {
|
|
180
|
-
"env": ["API_MODE"],
|
|
181
|
-
"secrets": ["customer_api_key"]
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
The placeholder deliberately does not validate. Use the actual digest produced
|
|
188
|
-
by inspection. Then, with values securely injected into the process:
|
|
189
|
-
|
|
190
|
-
```sh
|
|
191
|
-
urlcode validate --project /srv/my-links --policy /etc/urlcode/my-links-policy.json
|
|
192
|
-
urlcode serve --project /srv/my-links --policy /etc/urlcode/my-links-policy.json
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
`dev`, `test` and `validate --local` use the same policy rules even for `.env.local`.
|
|
196
|
-
The JavaScript API accepts an equivalent operator-supplied `permissions` object.
|
|
197
|
-
Every config change invalidates the grant, and so does a module change within
|
|
198
|
-
what the approval digest actually hashes: for a sandboxed route, its
|
|
199
|
-
middleware/function sources and their full dependency graph; for a trusted
|
|
200
|
-
route, only its own entry-file source (see the next paragraph — a trusted
|
|
201
|
-
route's transitive dependencies are explicitly **not** part of that digest).
|
|
202
|
-
Inspect/review the new revision before updating the operator file. Policies
|
|
203
|
-
are read at startup, not hot-reloaded. A failed development candidate leaves
|
|
204
|
-
the previous approved snapshot running.
|
|
205
|
-
|
|
206
|
-
Granting a secret deliberately makes it available to every middleware and function
|
|
207
|
-
in that route, trusted or sandboxed alike. A sandboxed route's middleware
|
|
208
|
-
sources and their full dependency graph are included in the approval digest,
|
|
209
|
-
as before; a trusted route's own entry-file source is included too, so
|
|
210
|
-
changing that file's content invalidates the grant, but a change to a helper
|
|
211
|
-
module it merely imports does not by itself (see function-sources.ts's
|
|
212
|
-
`collectTrustedSources`) — a known, documented gap versus the sandboxed path's
|
|
213
|
-
full dependency-graph hashing: a trusted route's grant scope is entry-file-only,
|
|
214
|
-
not transitive. Either way, code can include any granted data
|
|
215
|
-
in its HTTP response: neither the sandbox nor the trusted default promises
|
|
216
|
-
secrecy from code that was explicitly authorized to read a value. Minimize
|
|
217
|
-
grants, use scoped/short-lived credentials and revoke/restart when needed.
|
|
218
|
-
Other routes get none of that context.
|
|
219
|
-
|
|
220
|
-
## Next capability work
|
|
221
|
-
|
|
222
|
-
Outbound requests need a host-owned broker with explicit destination/method
|
|
223
|
-
allowlists, private/metadata/loopback-address restrictions, DNS/rebinding defenses,
|
|
224
|
-
redirect revalidation, deadlines and byte/concurrency limits. Application YAML
|
|
225
|
-
must not grant those permissions. Persistent state needs similarly scoped access.
|
|
226
|
-
Until such brokers are implemented and tested, these capabilities are unavailable
|
|
227
|
-
to a *sandboxed* route. Provider adapters must preserve a `sandbox: true`
|
|
228
|
-
route's isolation or reject deployment; they cannot silently downgrade a
|
|
229
|
-
route that explicitly asked for the sandbox into unrestricted Node execution.
|
|
230
|
-
(A trusted route, by contrast, already has unrestricted Node execution by
|
|
231
|
-
design on the self-hosted target — see "What the trusted default can and
|
|
232
|
-
can't do" above; non-Node targets refuse `function`/`middleware` entirely,
|
|
233
|
-
trusted or sandboxed, since neither execution mode exists there.)
|
|
234
|
-
|
|
235
|
-
## Verification and remaining risk
|
|
236
|
-
|
|
237
|
-
Tests attempt constructor/eval escapes, Node/filesystem/shell/network imports,
|
|
238
|
-
runtime-created imports, cross-request prototype/state pollution, oversized
|
|
239
|
-
allocations, loops, unauthorized secret requests and stale/repo-local policies.
|
|
240
|
-
These are regression tests, not a proof of complete security.
|
|
241
|
-
|
|
242
|
-
The URLCode host, parser, QuickJS/WASM engine, native runtime and dependencies
|
|
243
|
-
remain trusted computing components that need patching and review. Guest heap
|
|
244
|
-
limits do not cap all host/WASM RSS; use OS/container memory/CPU/PID limits as an
|
|
245
|
-
additional layer. Native engine bugs or resource exhaustion remain residual risks.
|
|
246
|
-
For a public arbitrary-code/multi-tenant service, require independent security
|
|
247
|
-
review plus process/VM-level isolation and operational controls before launch.
|
|
248
|
-
Do not advertise this release as an audited hostile multi-tenant hosting platform.
|
|
249
|
-
|
|
250
|
-
Implementation references: [QuickJS/WASM project](https://github.com/justjake/quickjs-emscripten)
|
|
251
|
-
and its [runtime isolation/limits API](https://github.com/justjake/quickjs-emscripten/blob/main/doc/quickjs-emscripten/classes/QuickJSRuntime.md).
|
package/docs/HTTP.md
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
# HTTP request and response configuration
|
|
2
|
-
|
|
3
|
-
Implemented in alpha.4. This is a documented HTTP subset, not a promise that every
|
|
4
|
-
HTTP feature is configurable. It builds on [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html).
|
|
5
|
-
|
|
6
|
-
```yaml
|
|
7
|
-
version: "1"
|
|
8
|
-
routes:
|
|
9
|
-
/echo:
|
|
10
|
-
methods: [POST]
|
|
11
|
-
request:
|
|
12
|
-
body:
|
|
13
|
-
required: true
|
|
14
|
-
maxBytes: 16384
|
|
15
|
-
contentTypes: [application/json]
|
|
16
|
-
format: json
|
|
17
|
-
function:
|
|
18
|
-
source: functions/echo.mjs
|
|
19
|
-
response:
|
|
20
|
-
headers:
|
|
21
|
-
Cache-Control: no-store
|
|
22
|
-
X-App: my-links
|
|
23
|
-
/go:
|
|
24
|
-
redirect:
|
|
25
|
-
url: https://example.com
|
|
26
|
-
status: 302
|
|
27
|
-
response:
|
|
28
|
-
headers:
|
|
29
|
-
Cache-Control: public, max-age=60
|
|
30
|
-
/status:
|
|
31
|
-
respond:
|
|
32
|
-
status: 200
|
|
33
|
-
json: {ok: true}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
`functions/echo.mjs`:
|
|
37
|
-
|
|
38
|
-
```js
|
|
39
|
-
export default async function echo(request) {
|
|
40
|
-
return Response.json(await request.json());
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## Requests
|
|
45
|
-
|
|
46
|
-
Keep ordinary routes short: omit `methods` to accept GET and HEAD. Set
|
|
47
|
-
`methods: [POST]` for a POST-only handler, or `methods: [GET, HEAD, POST]` for all
|
|
48
|
-
three. Use uppercase method names. Explicit lists replace the defaults; GET does
|
|
49
|
-
not implicitly add HEAD when a list is supplied. The schema advertises the same
|
|
50
|
-
default as the runtime. No declaration is needed for the default 302 redirect
|
|
51
|
-
status or default `Cache-Control: no-store` on functions/redirects.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
| Field | Behavior |
|
|
55
|
-
|---|---|
|
|
56
|
-
| `methods` | Allowed methods, default GET/HEAD; exact lists, 405 plus Allow on mismatch |
|
|
57
|
-
| `parameters` | Required/defaulted/typed path, query and header inputs; see the specification |
|
|
58
|
-
| `request.body.required` | Reject an empty body with 400; default false |
|
|
59
|
-
| `request.body.maxBytes` | 0–1048576; tighter per-route budget, enforced while reading fixed/chunked bodies; 413 on overflow |
|
|
60
|
-
| `request.body.contentTypes` | Exact lowercase MIME essences for nonempty bodies; parameters ignored; mismatch/missing type returns 415 |
|
|
61
|
-
| `request.body.format` | `text`: validate UTF-8; `json`: validate UTF-8, JSON media type and JSON syntax; malformed input returns 400 |
|
|
62
|
-
|
|
63
|
-
The operator request limit remains an upper bound; YAML cannot raise it. A route
|
|
64
|
-
without body policy keeps the existing server limit. A configured body policy
|
|
65
|
-
rejects nonidentity Content-Encoding for nonempty bodies; no automatic decompression.
|
|
66
|
-
Empty optional bodies skip media/format checks. Inputs are validated before the
|
|
67
|
-
handler; the original body remains available through function `request.text()` or
|
|
68
|
-
`request.json()`. No YAML body interpolation or automatic argument binding.
|
|
69
|
-
Request header inputs use `parameters` with `in: header`; this is validation,
|
|
70
|
-
not arbitrary modification or forwarding of the incoming request.
|
|
71
|
-
|
|
72
|
-
## Responses
|
|
73
|
-
|
|
74
|
-
`response.headers` maps HTTP names to literal strings. Names are case insensitive;
|
|
75
|
-
duplicate spellings and invalid names/control characters fail activation. YAML
|
|
76
|
-
values replace the same handler headers, including all prior Set-Cookie values.
|
|
77
|
-
Only `Set-Cookie` accepts a list, producing separate header lines:
|
|
78
|
-
|
|
79
|
-
```yaml
|
|
80
|
-
response:
|
|
81
|
-
headers:
|
|
82
|
-
Cache-Control: no-store
|
|
83
|
-
Set-Cookie:
|
|
84
|
-
- "theme=light; Path=/; SameSite=Lax; Secure"
|
|
85
|
-
- "notice=seen; Path=/; HttpOnly; SameSite=Lax; Secure"
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Use functions for dynamic cookies; never commit session credentials or secret
|
|
89
|
-
values into header literals. Header configuration applies to handler responses,
|
|
90
|
-
including declared error statuses, but not runtime validation/errors (400, 404,
|
|
91
|
-
405, 413, 415, 500, etc.). Defaults remain `no-store`, `nosniff` and a request ID.
|
|
92
|
-
Header policy is bounded to 64 keys/16 KiB; merged function headers remain bounded.
|
|
93
|
-
|
|
94
|
-
Framing, hop-by-hop headers, Location, Allow, range/cache validators,
|
|
95
|
-
Content-Encoding, X-Request-ID and X-Content-Type-Options are reserved to the
|
|
96
|
-
runtime/handler. Configure redirect URLs/status on `redirect`; asset content type,
|
|
97
|
-
cache and disposition on its own handler. Asset metadata cannot be overridden by
|
|
98
|
-
`response.headers`. On functions/declared responses, Content-Type may be configured;
|
|
99
|
-
JSON declarations require a JSON type. No response header secret interpolation.
|
|
100
|
-
|
|
101
|
-
`respond` is an additional native handler (exactly one handler per route):
|
|
102
|
-
|
|
103
|
-
- `status`: 200–599, default 200; 206 and 304 are reserved for native asset semantics.
|
|
104
|
-
- `text`: literal UTF-8 body, default content type text/plain.
|
|
105
|
-
- `json`: any JSON-compatible YAML value, serialized with application/json.
|
|
106
|
-
- Omit both for an empty body; declaring both fails. Body limit is 1 MiB.
|
|
107
|
-
- Status 204/205 cannot declare a nonempty body. HEAD always suppresses the body.
|
|
108
|
-
|
|
109
|
-
Functions still return their own Response/status/body. YAML header policy does
|
|
110
|
-
not replace function status/body. Asset handlers retain conditional/HEAD/range
|
|
111
|
-
behavior described in [assets](ASSETS.md). Use OPTIONS explicitly if you need a
|
|
112
|
-
declared response; merely adding a header does not implement CORS preflight.
|
|
113
|
-
|
|
114
|
-
## Still outside this contract
|
|
115
|
-
|
|
116
|
-
Automatic CORS/preflight policy, cookie parsing/signing, authentication, body JSON
|
|
117
|
-
Schema validation, multipart/file uploads, streaming, content negotiation,
|
|
118
|
-
WebSocket upgrades and proxies are not implemented. Do not advertise these as
|
|
119
|
-
supported just because raw headers can be declared. Compression negotiation,
|
|
120
|
-
security-header profiles, per-client throttling, User-Agent policy and HTTP
|
|
121
|
-
caching strategies exist only as optional, off-by-default
|
|
122
|
-
[policies](POLICIES.md); a project that declares none keeps the identity-only
|
|
123
|
-
behavior described here, and YAML `response.headers` beat any header a policy
|
|
124
|
-
would add. Future features need their own portable semantics and tests; unknown
|
|
125
|
-
YAML fields fail.
|
|
126
|
-
|
|
127
|
-
Middleware runs after route/method/input/body validation and before YAML response
|
|
128
|
-
header overrides. See [middleware](MIDDLEWARE.md) for ordering and native body
|
|
129
|
-
preservation rules.
|
package/docs/INSTALL.md
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
# Installing URLCode
|
|
2
|
-
|
|
3
|
-
URLCode is a Node CLI. Every channel below installs the same published tarball;
|
|
4
|
-
pick whichever fits how you already manage tools. All of them require
|
|
5
|
-
**Node.js 22.13 or newer**. The tarball ships plain JavaScript built from the
|
|
6
|
-
TypeScript source (`dist/`, with declarations; see [TypeScript](TYPESCRIPT.md)),
|
|
7
|
-
so the installed `urlcode` command runs `dist/cli.js` and needs no build tool.
|
|
8
|
-
|
|
9
|
-
## npm
|
|
10
|
-
|
|
11
|
-
The stable release target is `0.4.1`. Use these commands after publication;
|
|
12
|
-
`npm run release:status` in a checkout reports live registry availability. An
|
|
13
|
-
unversioned npm install selects the current `latest` channel, while `@alpha`
|
|
14
|
-
explicitly selects the separate prerelease channel.
|
|
15
|
-
|
|
16
|
-
```sh
|
|
17
|
-
npm install --global @jimhoyd/urlcode@0.4.1
|
|
18
|
-
urlcode --help
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Project-local, which is what an application repository should normally pin.
|
|
22
|
-
Which dependency list it belongs in depends on how the project uses URLCode:
|
|
23
|
-
|
|
24
|
-
```sh
|
|
25
|
-
# Using URLCode as a tool: validate, test and build in CI, never imported by
|
|
26
|
-
# the code that serves requests.
|
|
27
|
-
npm install --save-dev --save-exact @jimhoyd/urlcode@0.4.1
|
|
28
|
-
npx urlcode validate
|
|
29
|
-
|
|
30
|
-
# Embedding the runtime (see TYPESCRIPT.md): the application imports
|
|
31
|
-
# @jimhoyd/urlcode at startup, so it must survive `npm ci --omit=dev`.
|
|
32
|
-
npm install --save --save-exact @jimhoyd/urlcode@0.4.1
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
A devDependency is absent from a production install, so an application that
|
|
36
|
-
imports `createRuntime`, `startServer`, `prerenderPages` or any other
|
|
37
|
-
[embedding entry point](TYPESCRIPT.md) fails at startup on a missing module if it
|
|
38
|
-
is installed with `--save-dev`. An application should also pin an **exact**
|
|
39
|
-
version rather than a range: the compiled Cloudflare artifact format is tied to
|
|
40
|
-
the runtime version that reads it.
|
|
41
|
-
|
|
42
|
-
## Homebrew
|
|
43
|
-
|
|
44
|
-
```sh
|
|
45
|
-
brew tap jimhoyd-com/urlcode
|
|
46
|
-
brew trust jimhoyd-com/urlcode
|
|
47
|
-
brew install urlcode
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Homebrew refuses to load a formula from a third-party tap until you trust it,
|
|
51
|
-
so without the middle line the install stops with `Refusing to load formula …
|
|
52
|
-
from untrusted tap`. Trusting a tap means agreeing to run code from this
|
|
53
|
-
repository, the same as with the install script; `brew trust --formula
|
|
54
|
-
jimhoyd-com/urlcode/urlcode` limits it to this one formula.
|
|
55
|
-
|
|
56
|
-
The tap's formula is generated from the published tarball for each release and
|
|
57
|
-
attached to the GitHub release as `urlcode.rb`. Homebrew verifies the tarball's
|
|
58
|
-
SHA-256 against the formula before installing.
|
|
59
|
-
|
|
60
|
-
Verified on macOS (arm64) against the 0.3.0 tap: fetch verified, installed into
|
|
61
|
-
`/opt/homebrew/Cellar/urlcode/0.3.0`.
|
|
62
|
-
|
|
63
|
-
## Install script
|
|
64
|
-
|
|
65
|
-
```sh
|
|
66
|
-
curl -fsSL https://raw.githubusercontent.com/jimhoyd-com/urlcode/main/install.sh | sh
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
It downloads the release tarball, verifies its SHA-256 against the release's
|
|
70
|
-
`SHA256SUMS`, and installs with npm. Options:
|
|
71
|
-
|
|
72
|
-
```sh
|
|
73
|
-
curl -fsSL .../install.sh | sh -s -- --version 0.4.1 --prefix "$HOME/.local"
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
`--prefix` avoids needing privileges for a global npm directory; add
|
|
77
|
-
`$PREFIX/bin` to `PATH`. Piping a script into a shell means trusting the source
|
|
78
|
-
for that moment: to inspect first, download it, read it, then run it.
|
|
79
|
-
|
|
80
|
-
## Container
|
|
81
|
-
|
|
82
|
-
No image is published yet: the release job's GHCR step is gated behind the
|
|
83
|
-
`PUBLISH_CONTAINER` repository variable and has not run, so there is nothing at
|
|
84
|
-
`ghcr.io/jimhoyd-com/urlcode` to pull. After the release tag exists, build it from that checkout:
|
|
85
|
-
|
|
86
|
-
```sh
|
|
87
|
-
git clone --branch v0.4.1 https://github.com/jimhoyd-com/urlcode.git
|
|
88
|
-
docker build -t urlcode:0.4.1 urlcode
|
|
89
|
-
docker run --rm -p 127.0.0.1:3000:3000 -v "$PWD:/project:ro" urlcode:0.4.1 \
|
|
90
|
-
serve --project /project --host 0.0.0.0
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
The image runs the same built runtime, `node /opt/urlcode/dist/cli.js`, as its
|
|
94
|
-
entry point. Pin the digest rather than a tag for a deployment, and give the
|
|
95
|
-
container its own resource limits. See [operations](OPERATIONS.md).
|
|
96
|
-
|
|
97
|
-
## From source
|
|
98
|
-
|
|
99
|
-
```sh
|
|
100
|
-
git clone https://github.com/jimhoyd-com/urlcode.git
|
|
101
|
-
cd urlcode
|
|
102
|
-
make dev
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
A clone runs the TypeScript source directly (`node src/cli.ts`, Node 22.18+),
|
|
106
|
-
with no build step; see [local development](LOCAL-DEVELOPMENT.md).
|
|
107
|
-
|
|
108
|
-
Three Node versions appear around the project, and they are not a contradiction:
|
|
109
|
-
the installed package runs on Node 22.13 or newer (`engines`), running the
|
|
110
|
-
TypeScript source from a clone needs 22.18 or newer because it relies on Node's
|
|
111
|
-
built-in type stripping, the release workflow's npm trusted publishing needs
|
|
112
|
-
22.14 or newer, and the container image pins Node 26. Only the first number
|
|
113
|
-
constrains a deployment of the published tarball.
|
|
114
|
-
|
|
115
|
-
## Verify what you installed
|
|
116
|
-
|
|
117
|
-
Releases carry Sigstore provenance signed by the release workflow. Before
|
|
118
|
-
trusting a downloaded artifact:
|
|
119
|
-
|
|
120
|
-
```sh
|
|
121
|
-
gh attestation verify jimhoyd-urlcode-0.3.0.tgz --repo jimhoyd-com/urlcode \
|
|
122
|
-
--signer-workflow jimhoyd-com/urlcode/.github/workflows/release.yml
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
A signature establishes where an artifact came from. It is not a statement that
|
|
126
|
-
the release is safe for your workload, and it is not a production-readiness
|
|
127
|
-
claim; see [release readiness](RELEASE-READINESS.md) and
|
|
128
|
-
[release security](RELEASE-SECURITY.md).
|
package/docs/INTERCHANGE.md
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
# Redirect interchange and bulk authoring
|
|
2
|
-
|
|
3
|
-
`importRoutes` and `exportRoutes` return a conversion report without writing files,
|
|
4
|
-
activating a runtime, reading bindings, loading function sources or running code.
|
|
5
|
-
The CLI can preview the report before creating a new output file. Failed reports
|
|
6
|
-
never contain a partial document or output; duplicate paths never overwrite a row.
|
|
7
|
-
|
|
8
|
-
```js
|
|
9
|
-
import { importRoutes, exportRoutes } from '@jimhoyd/urlcode';
|
|
10
|
-
const report = await importRoutes({
|
|
11
|
-
format: 'csv', source: 'migration.csv',
|
|
12
|
-
text: 'path,url,status\n/old,https://example.test/new,301\n'
|
|
13
|
-
});
|
|
14
|
-
if (!report.ok) throw new Error(JSON.stringify(report.diagnostics));
|
|
15
|
-
// report.output is URLCode project YAML; report.document is the validated project.
|
|
16
|
-
const csv = await exportRoutes({ format: 'csv', document: report.document });
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Each report has `ok`, `lossless`, `routeCount` and `diagnostics`. Diagnostics name
|
|
20
|
-
an input source, physical CSV/text row or JSON/YAML array index where available,
|
|
21
|
-
a path where appropriate, a code, severity and explanation. Destinations and
|
|
22
|
-
credentials are not echoed in validation errors. Output is sorted by literal
|
|
23
|
-
path, independent of input order. Import does not merge an existing project;
|
|
24
|
-
normal project loading rejects collisions across includes when output is added.
|
|
25
|
-
|
|
26
|
-
## CLI usage
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
# Bulk row formats can be inferred from the input extension or named explicitly.
|
|
30
|
-
urlcode import csv migration.csv --out routes.yaml --dry-run
|
|
31
|
-
urlcode import csv migration.csv --out routes.yaml
|
|
32
|
-
urlcode import migration.json --report json --dry-run
|
|
33
|
-
|
|
34
|
-
# Provider migrations require explicit acknowledgment of the reported differences.
|
|
35
|
-
urlcode import netlify _redirects --out imported.yaml --dry-run
|
|
36
|
-
urlcode import netlify _redirects --out imported.yaml --accept-provider-differences
|
|
37
|
-
urlcode export --target netlify --project ./project --out _redirects --accept-provider-differences
|
|
38
|
-
urlcode export --target cloudflare --project ./project --dry-run
|
|
39
|
-
urlcode export --target vercel --project ./project --dry-run
|
|
40
|
-
urlcode export --target netlify-toml --project ./project --dry-run
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
General import syntax is `urlcode import [format] FILE`; `--format` explicitly
|
|
44
|
-
overrides the positional format or extension. Exports use `--target` for a
|
|
45
|
-
provider format, or `--format csv|json|yaml` for row data. `--out FILE` creates a
|
|
46
|
-
new file with mode 0600 and never overwrites one. `--dry-run` validates and
|
|
47
|
-
returns a report without creating the requested output. `--report json` always
|
|
48
|
-
emits the report. Failures exit nonzero and leave no converted output.
|
|
49
|
-
|
|
50
|
-
Reports include source/row diagnostics and the successful candidate output.
|
|
51
|
-
Acknowledged provider conversions always emit a report with their warnings,
|
|
52
|
-
even when `--out` is provided; raw provider output is never silently printed as
|
|
53
|
-
if it were lossless. Project export flattens validated includes, but refuses
|
|
54
|
-
other unsupported project behavior instead of dropping it. For a large input
|
|
55
|
-
that needs a complete sharded project, use [bulk import](BULK.md).
|
|
56
|
-
|
|
57
|
-
## Supported forms
|
|
58
|
-
|
|
59
|
-
| Format | Input/output subset |
|
|
60
|
-
| --- | --- |
|
|
61
|
-
| `csv` | Header exactly `path,url,status`; optional empty status defaults to 302; quoted fields and escaped quotes |
|
|
62
|
-
| `json` | Array of `{path,url,status?}`; status must be a number |
|
|
63
|
-
| `yaml` | The same row array in strict URLCode YAML syntax; not a full project file |
|
|
64
|
-
| `netlify` | `_redirects`: literal source, absolute HTTP(S) destination, optional status (default 301) |
|
|
65
|
-
| `cloudflare` | Pages `_redirects`: same columns, default 302; 2,000 static rules, 1,000 characters per rule |
|
|
66
|
-
| `vercel` | `vercel.json` containing only `redirects`; literal `source`, absolute `destination`, and either boolean `permanent` (308/307) or `statusCode` |
|
|
67
|
-
| `netlify-toml` | Only `[[redirects]]` blocks with `from`, `to`, optional numeric `status` (default 301); unescaped double-quoted strings |
|
|
68
|
-
|
|
69
|
-
All statuses are restricted to 301, 302, 303, 307 and 308. Literal ASCII paths
|
|
70
|
-
and absolute HTTP(S) destinations are required. The existing schema and semantic
|
|
71
|
-
compiler validate resulting routes. No provider pattern compiler is introduced.
|
|
72
|
-
|
|
73
|
-
The TOML subset deliberately rejects general TOML constructs, build settings,
|
|
74
|
-
escapes, inline comments, nested conditions, force flags and other tables. A full
|
|
75
|
-
`netlify.toml` must be separated into a redirects-only input by the operator. This
|
|
76
|
-
avoids silently discarding build settings or reinterpreting unsupported syntax.
|
|
77
|
-
|
|
78
|
-
Functions, assets, middleware, conditions, parameters, query maps/allowlists,
|
|
79
|
-
headers, policies, includes and any other extra route/project fields cannot be
|
|
80
|
-
exported by this subset. Relative destinations, wildcards, provider placeholder
|
|
81
|
-
syntax, rewrites and duplicate paths fail. Use a runtime adapter for richer
|
|
82
|
-
behavior. CSV/JSON/YAML round-trips are lossless within the declared simple subset.
|
|
83
|
-
|
|
84
|
-
## Provider semantics require explicit acknowledgment
|
|
85
|
-
|
|
86
|
-
Provider conversions fail by default. `acceptProviderDifferences: true` (CLI
|
|
87
|
-
`--accept-provider-differences`) permits a **non-lossless migration candidate**;
|
|
88
|
-
it does not suppress invalid rules, discard unsupported fields or claim exact
|
|
89
|
-
behavior. Every such successful report retains a warning and `lossless: false`.
|
|
90
|
-
|
|
91
|
-
URLCode drops incoming query parameters and defaults to GET/HEAD. Native
|
|
92
|
-
redirect systems can apply to other methods and have different normalization
|
|
93
|
-
and request-query behavior. Netlify automatically forwards queries for common
|
|
94
|
-
redirect statuses and can give existing files precedence. Cloudflare Pages
|
|
95
|
-
redirects override assets, but Pages Functions can bypass `_redirects`.
|
|
96
|
-
Review behavior for requests with queries, non-GET methods, slash/URL normalization
|
|
97
|
-
and conflicting assets/functions before deploying. The acknowledged subset is
|
|
98
|
-
literal GET/HEAD requests without these conflicts. No provider deployment was
|
|
99
|
-
performed as part of interchange unit tests.
|
|
100
|
-
|
|
101
|
-
Provider references checked 2026-09-17:
|
|
102
|
-
|
|
103
|
-
- [Netlify redirect options](https://docs.netlify.com/manage/routing/redirects/redirect-options/)
|
|
104
|
-
- [Netlify TOML configuration](https://docs.netlify.com/build/configure-builds/file-based-configuration/)
|
|
105
|
-
- [Cloudflare Pages redirects](https://developers.cloudflare.com/pages/configuration/redirects/)
|
|
106
|
-
- [Vercel redirect configuration](https://vercel.com/docs/project-configuration/vercel-json)
|
|
107
|
-
|
|
108
|
-
## Resource limits
|
|
109
|
-
|
|
110
|
-
Imports reject more than 32 MiB of input or 100,000 rows. Diagnostic collection
|
|
111
|
-
stops after 100 invalid rows; normal route compilation retains its deadline and
|
|
112
|
-
validation rules. Large output can still exceed the runtime YAML worker's heap
|
|
113
|
-
or deadline: conversion success does not promise activation of a single 100k
|
|
114
|
-
route YAML file. Split large migration inputs into independently reviewed files
|
|
115
|
-
and use explicit `includes`; aggregate project limits continue to apply.
|
|
116
|
-
|
|
117
|
-
Reports also expose bounded classification counts:
|
|
118
|
-
|
|
119
|
-
- `convertedRoutes`: routes actually returned in successful output; zero when
|
|
120
|
-
any error prevents output.
|
|
121
|
-
- `nativeEquivalentRoutes`: returned routes with no conversion warnings; always
|
|
122
|
-
zero for acknowledged provider conversions.
|
|
123
|
-
- `runtimeRequiredRoutes`: route diagnostics identifying behavior outside the
|
|
124
|
-
simple export subset.
|
|
125
|
-
- `unsupportedRows`: known invalid/duplicate input-row diagnostics.
|
|
126
|
-
- `providerDifferenceRoutes`: candidate routes subject to the provider semantic
|
|
127
|
-
warning; these overlap converted routes after acknowledgment.
|
|
128
|
-
- `fullyScanned`: false on parser/global failures or truncated diagnostics. Counts
|
|
129
|
-
then describe only examined input, never an inferred total.
|
|
130
|
-
|
|
131
|
-
These counts are not a partition of arbitrary malformed input. `routeCount`
|
|
132
|
-
retains its original meaning: routes in the candidate table, or rows
|
|
133
|
-
parsed before an early global failure. A failed report contains no candidate
|
|
134
|
-
output even if some rows were convertible.
|