@jimhoyd/urlcode 0.4.2 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +57 -14
  2. package/.claude/skills/urlcode-operations/SKILL.md +4 -0
  3. package/README.md +42 -18
  4. package/SECURITY.md +5 -3
  5. package/dist/BUILD-MANIFEST.json +30 -27
  6. package/dist/agent-context.js +82 -0
  7. package/dist/agents-guide.js +42 -42
  8. package/dist/authoring.js +12 -2
  9. package/dist/body-schema.js +159 -0
  10. package/dist/build-cloudflare.js +2 -0
  11. package/dist/capabilities.js +1 -1
  12. package/dist/cli.js +28 -12
  13. package/dist/config.js +74 -6
  14. package/dist/context.js +4 -6
  15. package/dist/errors.js +3 -1
  16. package/dist/examples.js +1 -1
  17. package/dist/extensions.js +60 -2
  18. package/dist/http-policy.js +19 -4
  19. package/dist/http-response.js +2 -2
  20. package/dist/init-with.js +71 -9
  21. package/dist/mcp.js +15 -2
  22. package/dist/pattern-guard.js +32 -0
  23. package/dist/policies/security.js +0 -0
  24. package/dist/project-tests.js +35 -11
  25. package/dist/readiness.js +184 -32
  26. package/dist/recipes.js +1 -1
  27. package/dist/router.js +17 -0
  28. package/dist/runtime.js +26 -3
  29. package/dist/scaffold.js +0 -0
  30. package/dist/server.js +26 -3
  31. package/dist/site.js +0 -0
  32. package/dist/tooling.js +2 -1
  33. package/dist/types/agent-context.d.ts +44 -0
  34. package/dist/types/authoring.d.ts +3 -1
  35. package/dist/types/body-schema.d.ts +58 -0
  36. package/dist/types/config.d.ts +10 -2
  37. package/dist/types/context.d.ts +1 -1
  38. package/dist/types/errors.d.ts +9 -1
  39. package/dist/types/examples.d.ts +1 -1
  40. package/dist/types/extensions.d.ts +47 -2
  41. package/dist/types/http-policy.d.ts +3 -0
  42. package/dist/types/init-with.d.ts +10 -1
  43. package/dist/types/pattern-guard.d.ts +10 -0
  44. package/dist/types/project-tests.d.ts +9 -0
  45. package/dist/types/readiness.d.ts +68 -0
  46. package/dist/types/recipes.d.ts +1 -1
  47. package/dist/types/runtime.d.ts +4 -0
  48. package/dist/types/server.d.ts +9 -1
  49. package/dist/types/tooling.d.ts +1 -0
  50. package/dist/types/types.d.ts +13 -0
  51. package/dist/types.js +10 -2
  52. package/dist/typescript-authoring.js +5 -3
  53. package/dist/verify-deployment.js +16 -10
  54. package/examples/body-validation/README.md +16 -0
  55. package/examples/body-validation/example.yaml +17 -0
  56. package/examples/body-validation/tests/requests.json +15 -0
  57. package/examples/body-validation/urlcode.yaml +34 -0
  58. package/examples/coverage-waiver/README.md +8 -0
  59. package/examples/coverage-waiver/example.yaml +16 -0
  60. package/examples/coverage-waiver/functions/notes.mjs +2 -0
  61. package/examples/coverage-waiver/tests/requests.json +3 -0
  62. package/examples/coverage-waiver/urlcode.yaml +10 -0
  63. package/examples/data-dir/README.md +39 -0
  64. package/examples/data-dir/data/welcome.txt +1 -0
  65. package/examples/data-dir/example.yaml +22 -0
  66. package/examples/data-dir/functions/note.mjs +18 -0
  67. package/examples/data-dir/tests/requests.json +6 -0
  68. package/examples/data-dir/urlcode.yaml +6 -0
  69. package/examples/lifecycle/README.md +15 -0
  70. package/examples/lifecycle/example.yaml +19 -0
  71. package/examples/lifecycle/functions/notes.mjs +29 -0
  72. package/examples/lifecycle/tests/requests.json +12 -0
  73. package/examples/lifecycle/urlcode.yaml +29 -0
  74. package/examples/not-found/README.md +10 -0
  75. package/examples/not-found/example.yaml +17 -0
  76. package/examples/not-found/public/404.html +3 -0
  77. package/examples/not-found/public/index.html +3 -0
  78. package/examples/not-found/tests/requests.json +7 -0
  79. package/examples/not-found/urlcode.yaml +7 -0
  80. package/examples/shared-blocks/README.md +11 -0
  81. package/examples/shared-blocks/example.yaml +17 -0
  82. package/examples/shared-blocks/tests/requests.json +7 -0
  83. package/examples/shared-blocks/urlcode.yaml +37 -0
  84. package/llms-full.txt +364 -49
  85. package/llms.txt +44 -6
  86. package/package.json +15 -26
  87. package/recipes/static-page/README.md +9 -0
  88. package/recipes/static-page/public/index.html +11 -0
  89. package/recipes/static-page/recipe.yaml +21 -0
  90. package/recipes/static-page/tests/requests.json +22 -0
  91. package/recipes/static-page/urlcode.yaml +7 -0
  92. package/recipes/static-plus-api/README.md +6 -0
  93. package/recipes/static-plus-api/urlcode.yaml +4 -0
  94. package/recipes/store-crud/README.md +53 -0
  95. package/recipes/store-crud/recipe.yaml +31 -0
  96. package/recipes/store-crud/tests/requests.json +18 -0
  97. package/recipes/store-crud/urlcode.yaml +18 -0
  98. package/schemas/urlcode.schema.json +125 -60
  99. package/skills/urlcode/SKILL.md +47 -27
  100. package/starters/default/AGENTS.md +43 -43
  101. package/starters/page/README.md +14 -0
  102. package/starters/page/public/index.html +12 -0
  103. package/starters/page/tests/requests.json +17 -0
  104. package/starters/page/urlcode.yaml +6 -0
  105. package/.claude-plugin/marketplace.json +0 -18
  106. package/CONTRIBUTING.md +0 -148
  107. package/ROADMAP.md +0 -70
  108. package/docs/AI-AUTHORING.md +0 -339
  109. package/docs/ASSETS.md +0 -107
  110. package/docs/AUTH-BACKUP.md +0 -32
  111. package/docs/AWS.md +0 -86
  112. package/docs/BEST-PRACTICES.md +0 -276
  113. package/docs/BULK.md +0 -79
  114. package/docs/CAPABILITIES.md +0 -192
  115. package/docs/CAPACITY.md +0 -305
  116. package/docs/CI-FOLLOWUP-2026-09-19.md +0 -97
  117. package/docs/CI-RELEASE-AUDIT-2026-09-19.md +0 -322
  118. package/docs/CI.md +0 -147
  119. package/docs/CLOUDFLARE.md +0 -109
  120. package/docs/CODEBASE-AUDIT-2026-09-20.md +0 -284
  121. package/docs/COMPLIANCE.md +0 -239
  122. package/docs/COMPOSING-A-SITE.md +0 -287
  123. package/docs/CONDITIONS.md +0 -74
  124. package/docs/CONTAINER-PROMOTION.md +0 -74
  125. package/docs/DEPLOYMENT-CHECKS.md +0 -108
  126. package/docs/DEVELOPMENT-PIPELINE.md +0 -304
  127. package/docs/EGRESS.md +0 -125
  128. package/docs/EXTENSIONS.md +0 -403
  129. package/docs/FRAMEWORK.md +0 -217
  130. package/docs/FUNCTION-SECURITY.md +0 -251
  131. package/docs/HTTP.md +0 -129
  132. package/docs/INSTALL.md +0 -128
  133. package/docs/INTERCHANGE.md +0 -134
  134. package/docs/LOAD-TESTING.md +0 -91
  135. package/docs/LOCAL-DEVELOPMENT.md +0 -102
  136. package/docs/MIDDLEWARE-EXAMPLES.md +0 -75
  137. package/docs/MIDDLEWARE.md +0 -102
  138. package/docs/MONITORING.md +0 -115
  139. package/docs/OBSERVABILITY.md +0 -222
  140. package/docs/OPEN-DECISIONS.md +0 -149
  141. package/docs/OPERATIONAL-PROOF.md +0 -41
  142. package/docs/OPERATIONS.md +0 -201
  143. package/docs/ORGANIZATION.md +0 -135
  144. package/docs/PERFORMANCE.md +0 -72
  145. package/docs/PLUGINS.md +0 -271
  146. package/docs/POLICIES.md +0 -211
  147. package/docs/PRERENDER.md +0 -245
  148. package/docs/PROJECT-DIRECTION.md +0 -118
  149. package/docs/PROVIDER-VERIFICATION.md +0 -84
  150. package/docs/READINESS.md +0 -153
  151. package/docs/README.md +0 -93
  152. package/docs/RECIPES.md +0 -99
  153. package/docs/RELEASE-0.4.0-alpha.3.md +0 -50
  154. package/docs/RELEASE-0.4.1.md +0 -73
  155. package/docs/RELEASE-0.4.2.md +0 -30
  156. package/docs/RELEASE-READINESS.md +0 -146
  157. package/docs/RELEASE-SECURITY.md +0 -100
  158. package/docs/RESILIENCE.md +0 -161
  159. package/docs/ROUTING.md +0 -92
  160. package/docs/SANDBOX-REVIEW.md +0 -72
  161. package/docs/SCAFFOLDING.md +0 -70
  162. package/docs/SECURITY-AUDIT.md +0 -164
  163. package/docs/SITE.md +0 -150
  164. package/docs/SPECIFICATION.md +0 -359
  165. package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +0 -288
  166. package/docs/SPIKE-BUSINESS-SUITE.md +0 -1029
  167. package/docs/SPIKE-CORE-LAYERING.md +0 -368
  168. package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +0 -207
  169. package/docs/STANDARDS.md +0 -311
  170. package/docs/STARTERS.md +0 -83
  171. package/docs/STATIC.md +0 -105
  172. package/docs/TOOLING.md +0 -298
  173. package/docs/TUNNELS.md +0 -72
  174. package/docs/TYPESCRIPT-AUTHORING.md +0 -87
  175. package/docs/TYPESCRIPT.md +0 -123
  176. package/docs/VERCEL.md +0 -114
  177. package/docs/VERSION-ALIGNMENT.md +0 -88
  178. package/docs/YAML-GUIDE.md +0 -57
  179. package/docs/YAML-REFERENCE.md +0 -449
  180. package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +0 -68
  181. package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +0 -102
  182. package/docs/archive/2026-09-19/NEXT-PHASE-PLAN.md +0 -108
  183. package/docs/archive/2026-09-19/NEXT-STEPS.md +0 -646
  184. package/docs/archive/2026-09-19/OPEN-DECISIONS.md +0 -277
  185. package/docs/archive/2026-09-19/RELEASE-SECURITY.md +0 -186
  186. package/docs/archive/2026-09-19/ROADMAP.md +0 -387
  187. package/docs/archive/2026-09-19/SPIKE-EXTENSION-MODEL.md +0 -430
  188. package/docs/archive/2026-09-19/SPIKE-EXTENSIONS.md +0 -492
  189. package/docs/archive/2026-09-19/SPIKE-LAMBDA-COMPILE.md +0 -365
  190. package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +0 -778
  191. package/docs/archive/2026-09-19/USABILITY-REVIEW.md +0 -139
  192. package/docs/archive/2026-09-20/OPEN-DECISIONS-COMPLETED.md +0 -116
  193. package/docs/archive/README.md +0 -29
  194. package/docs/policies/agents.md +0 -182
  195. package/docs/policies/cache.md +0 -152
  196. package/docs/policies/compression.md +0 -169
  197. package/docs/policies/contract.md +0 -52
  198. package/docs/policies/hardened.md +0 -56
  199. package/docs/policies/interoperability.md +0 -169
  200. package/docs/policies/operations.md +0 -45
  201. package/docs/policies/security.md +0 -161
  202. package/docs/policies/throttle.md +0 -103
  203. package/docs/yaml/assets.md +0 -36
  204. package/docs/yaml/conditions.md +0 -20
  205. package/docs/yaml/functions.md +0 -168
  206. package/docs/yaml/middleware.md +0 -31
  207. package/docs/yaml/organization.md +0 -74
  208. package/docs/yaml/policies.md +0 -37
  209. package/docs/yaml/redirects.md +0 -64
  210. package/docs/yaml/responses.md +0 -57
  211. package/docs/yaml/site.md +0 -24
  212. package/packaging/claude-plugin/.claude-plugin/plugin.json +0 -19
  213. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +0 -130
  214. 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).
@@ -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.