Scorer should honour files/deno.json imports so bare specifiers resolve
Opened by stack72 · 4/24/2026
Problem
The extension tarball analyzer (lib/infrastructure/extension-tarball-analyzer.ts) strips the root deno.json and writes a controlled one with nodeModulesDir: "auto" and no imports map. Bare specifier imports like import { z } from "zod" therefore cannot resolve at score time, even when the publisher ships the import map via additionalFiles: [deno.json].
This creates a catch-22 for extension authors. Deno's no-import-prefix lint rule fires by default whenever a deno.json has an imports map — which is every swamp repo's default state — so:
- Bare
"zod"+ repodeno.json: passesswamp extension fmt/swamp extension push, failsswamp extension qualityand the registry scorer. - Inline
"npm:zod@4": passes the scorer, failsswamp extension fmt/swamp extension pushbecause ofno-import-prefix.
The only escape today is disabling no-import-prefix in the repo's deno.json or scattering // deno-lint-ignore comments per import — documented as a workaround in swamp#1220, but the proper fix is server-side.
Proposed solution
Mirror the existing findReadme / findLicenseFile pattern (lines 598, 620 of the analyzer) for deno.json: check files/deno.json in the tarball and merge its imports map into the controlled deno.json the analyzer writes. The security argument for stripping the ROOT deno.json (line 394-409) doesn't apply to files/deno.json because it's a publisher-declared location, the merge can whitelist only imports, and each value can be validated against an allowlist.
Specifically:
- Between
stripHermeticityConfigs(logicalRoot)(line 123) andwriteControlledDenoJson(logicalRoot)(line 124), readfiles/deno.jsonand extract ONLY theimportsfield (and possiblyscopes). Ignoretasks,compilerOptions,lint,fmt,nodeModulesDir,unstable,workspace, everything else. - Validate each
importsvalue: must matchnpm:*,jsr:*,https://<allowlisted host>, or a relative path that doesn't escape the tarball. Drop anything else. - Cap cardinality (e.g. 50 entries) and per-string length. Size-cap the file itself.
- Merge the validated
importsinto the controlled{ nodeModulesDir: "auto" }config that gets written to the root.
Back-compat: tarballs without files/deno.json behave exactly as today.
Test coverage needed
- Happy path:
files/deno.jsonwith{ "imports": { "zod": "npm:zod@4" } }+ entrypoint using bare"zod"→ scores normally. - Path traversal: value
"../etc/passwd"→ dropped. - Disallowed scheme: value
"data:..."→ dropped. - Oversized file → ignored.
- Malformed JSON → ignored, analyzer still runs.
- Key explosion (10k entries) → truncated to cap.
- Irrelevant keys (
tasks,compilerOptions, etc.) → do NOT land in controlled config. - No
files/deno.json→ behaves as today.
Alternatives considered
- Leave as-is, document the catch-22. Done in swamp#1220 as the interim. Works but requires every author to disable
no-import-prefixor use inlinenpm:— both inferior to shipping their import map. - Honour root
deno.jsonin the tarball. Rejected by the existing security comment at line 394-409 — a rootdeno.jsoncan shadow the entire analyzer environment.files/is safer because the publisher explicitly opted in and we can whitelist fields. - Inline ONLY at bundle time, strip at publish. The swamp CLI would rewrite bare → inline before packaging. Rejected: invasive, changes publisher source semantics, and swamp#1218's
swamp extension qualityalready mirrors server behaviour so the problem shows up locally anyway.
Downstream manual updates
After this ships, the following pages in this repo need updates:
content/manual/reference/extension-manifest.md(~line 520): the "barefrom \"zod\"requires adeno.jsonimport map" row is now accurate ONLY ifdeno.jsonis also inadditionalFiles:. Split into bundle-time vs score-time resolution, or add the clause.content/manual/how-to/improve-extension-score.md: add a section noting bare specifiers require shippingdeno.jsonviaadditionalFiles:.content/manual/how-to/create-and-publish-extension.md(~line 21): keep inlinenpm:zod@4as the primary recommendation but add the bare +additionalFilesalternative.
Swamp-side cleanup (separate, in systeminit/swamp)
Once this ships, the gotcha block added by swamp#1220 in .claude/skills/swamp-extension-quality/SKILL.md becomes partly stale:
- The
no-import-prefixcatch-22 goes away. - "Adding
deno.jsontoadditionalFiles:does not help" → flips to "is the supported way". - Remove the forward-looking sentence about the scorer honouring
files/deno.json.
Origin
Surfaced during triage of swamp-club#161 and the corresponding interim fix in systeminit/swamp#1220. Issue 161's skill-level workaround is the band-aid; this is the proper fix.
Closed
No activity in this phase yet.