fix(guards): refuse a .npmrc beside a portal manifest, like bunfig.toml

Bun reads .npmrc too, so a registry line beside a borrowed portal's
manifest fetches every dependency from a server the portal's author
picked - the same door bunfig.toml opens, which the installer and guard
check 3 already refuse. Both now reject either file. The #718 integration
test, which pointed Bun at its offline registry through a .npmrc, passes
the registry through NPM_CONFIG_REGISTRY instead; it still proves the
hook stays off, because the hooked package only exists on that server.

Follows GHSA-wq7j-x5jq-pjrw; this case is ours, not the report's. Guard
tests for the workspaces and bunfig.toml refusals, which the previous
commit added without them, come along.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Mads LorentzenandClaude Fable 5.1 authored and Mads Lorentzen committed 2026-10-27 16:14:35 +01:00
1 parent 0bcfd75e60
commit 83c788d779
9 files changed
+67 -28

No files matched your search

+1 -1
View File
@@ -31,7 +31,7 @@ Every grant the template ships, where it lives, how long it lasts, and what chec
| Hooks (`.claude/settings.json` `hooks`, skill and command frontmatter `hooks`, !`command` lines) | None. The template ships no hooks: a hook runs with no prompt the moment its event fires, which is the vector the Shai-Hulud worm used in August 2026. A !`command` line in a skill or command runs when it loads, the same way. | - | Any of them fails CI |
| Every other `settings.json` key | None. `permissions` (with `allow`, `ask`, `deny`) and `hooks` are the whole file: `env` would reach every pre-approved `python tools/*.py` run, `apiKeyHelper` and a `statusLine` command run a program with no prompt, and `permissions.defaultMode` or `additionalDirectories` would widen what the lists above scope. | - | Any other top-level or `permissions` key fails CI (`ALLOWED_SETTINGS_KEYS`, `ALLOWED_PERMISSIONS_KEYS`) |
| Compiling (`lualatex`, `xelatex`, a custom template's command) | Not pre-approved: a `lualatex:*` grant would also approve `-shell-escape`. In default mode each compile prompts. Answering "don't ask again" would persist exactly that `lualatex` rule to your `settings.local.json`, so answer per call. | - | - |
| Portal CLI manifests (`.agents/**/package.json`) | No lifecycle scripts (`preinstall`, `postinstall`, ...), no `trustedDependencies`, no `workspaces` (whose packages bring their own scripts) and no `bunfig.toml` beside the manifest (which reconfigures the install), so `bun install` runs no code; the installer passes `--ignore-scripts` as well | - | Fails CI; `tools/install_portal_clis.py` also skips a manifest that carries one |
| Portal CLI manifests (`.agents/**/package.json`) | No lifecycle scripts (`preinstall`, `postinstall`, ...), no `trustedDependencies`, no `workspaces` (whose packages bring their own scripts) and no `bunfig.toml` or `.npmrc` beside the manifest (either can point every dependency at another registry), so `bun install` runs no code from a source the template did not review; the installer passes `--ignore-scripts` as well | - | Fails CI; `tools/install_portal_clis.py` also skips a manifest that carries one |
| Personal-data ignore rules (`.gitignore`) | Every rule in `REQUIRED_IGNORE_RULES` present, read as git reads them (leading whitespace is part of the pattern); no negation outside `ALLOWED_IGNORE_NEGATIONS`, in the root file or in any subdirectory `.gitignore`, which outranks it | - | Fails CI |
| The browser-header retry (`tools/fetch_page.py`) | A GET with a fixed argv, http(s) only, 45 s / 5 redirects / 10 MB, `robots.txt` enforced before each redirect target with no bypass flag, output refused inside the repo | - | Pinned by `tests/test_fetch_page.py`; the grant names the script, not `curl` |