Skip to content

Bound untrusted file reads during detection; document repo-code trust model#7

Merged
suarezesteban merged 9 commits into
mainfrom
claude/practical-thompson-yy9nm1
Jun 10, 2026
Merged

Bound untrusted file reads during detection; document repo-code trust model#7
suarezesteban merged 9 commits into
mainfrom
claude/practical-thompson-yy9nm1

Conversation

@suarezesteban

Copy link
Copy Markdown
Collaborator

Two follow-ups from an in-depth security audit of the repo.

F1 — Cap untrusted file reads during the pre-trust scan (Low)

up.config.json is already size-bounded "because it is attacker-controllable (a cloned repo) and parsed before it is trusted", but the rest of the detection phase was not:

  • Framework detection read package.json, and 283 of the @vercel/frameworks detectors read other files (index.js, server.ts, Cargo.toml, requirements.txt, …) whole into memory via the filesystem detector to match content.
  • readPackageJson, countDeps, and the env-file reader each read their file with no size guard.

A cloned repo could bloat any of those files and OOM the local up process before any trust prompt — a local self-DoS, but an inconsistency with a guard the code already deemed necessary.

Fix: detection now reads through a size-bounded filesystem detector (an oversized file is treated as empty, so its detectors simply do not match — graceful degradation, never a whole-file buffer). readPackageJson, countDeps, and the env-file reader gained the same 4 MB cap. The env guard matters now that the .env.local prompt defaults to yes, so a hostile repo's .env.local is reachable. 4 MB is far above any real detection input and far below OOM.

Tests: an oversized package.json degrades to "custom" (proving it is not parsed); an oversized env file is rejected.

F2 — Document the repo-code trust model (SECURITY.md)

The audit's key "vector you might not notice": up . executes the project's own install/dev commands in the sandbox — i.e. in your Vercel account, with its resources and network. The sandbox isolates that code from your machine, not from your account. Added a short paragraph framing up . like npm install && npm run dev (run it only on repos you trust), and noting the mitigating split that only the dev command receives an injected env file — the install step never sees those values.

Verification

pnpm lint, pnpm typecheck, pnpm build pass. Tests 156/158 — the 2 failures are pre-existing and environment-only (collectFiles unreadable-path tests rely on chmod 0o000, a no-op as root; they fail identically on main in that environment).

https://claude.ai/code/session_01GVqD2pepFwpZG7dNp9T9jh


Generated by Claude Code

claude added 9 commits June 9, 2026 18:35
Three low-risk hardening fixes from a security review. None is exploitable
today; each removes a latent footgun should an input source change.

- ui.ts: validate the URL before openUrl hands it to a shell on Windows
  (`start` is a cmd builtin). Restrict to an https `*.vercel.run` origin with
  a shell-safe character set via a new exported `isOpenableUrl` predicate.
- ready.ts: pass the loop count and port to the waitForPort probe as bash
  positional args ($1/$2) instead of string-interpolating them, matching the
  positional-arg pattern used elsewhere.
- sync.ts: add `--` to the remote `rm -f` so no target can be read as an
  option (targets are always absolute, so this is belt-and-suspenders).

Adds unit tests for isOpenableUrl and updates the removeFiles assertion.

https://claude.ai/code/session_01GVqD2pepFwpZG7dNp9T9jh
Beta feedback showed raw HTTP 403 dumps with no guidance ("No clue why i
got this or what to even do"). v0.1.0-beta.3 calmed the `up .` auth paths;
this extends the same treatment to the paths it missed:

- `up stop` had none of the calm handling: an expired/invalid token dumped
  the same raw 403 the feedback flagged. Route auth failures to the
  sign-in note, and turn the very common "nothing to stop" case (sandbox
  expired, or stop ran from a different directory than `up .`) into a
  short explanation pointing at `up ls` instead of a raw 404/410 body.
- The sign-in note assumed the Vercel CLI is installed; `vercel login` is
  a dead end for newcomers without it. Add a dim hint with the install
  command and the VERCEL_TOKEN alternative.
- The dev-server timeout error never named the port it was waiting on. A
  dev script that binds a different port (hardcoded -p, PORT ignored) hit
  a 90s wait then an unexplained timeout. Name the port and point at
  --port.

https://claude.ai/code/session_01GVqD2pepFwpZG7dNp9T9jh
Beta feedback: a user declined the .env.local prompt and their app (which
needed those keys) ran broken, with a nag line on every later run and the
flag required on each retry. A local dotenv almost always exists because
the app needs it, so lean into that:

- Default the .env.local prompt to yes; the common path is one Enter.
- `--env-file` now records the choice, so passing it once re-enables
  injection on future runs (it also overrides an earlier "no").
- A recorded "no" stays quiet instead of printing the --env-file hint on
  every run; the hint shows only while no decision exists.
- Drop the "!" on the env line of the shared-command trust panel; env
  injection is the expected case, not an alarm (sensitive-config keeps
  its warning since that genuinely persists in snapshots).

https://claude.ai/code/session_01GVqD2pepFwpZG7dNp9T9jh
`up` waited on the framework's default port even when the project's dev
script bound a different one (e.g. `next dev -p 4000`, `vite --port=8000`,
`PORT=4000 node server.js`). Since `pm run dev` does not forward a port
flag and a hardcoded port ignores the injected PORT env var, the server
came up on its own port while the supervisor polled the default, ending in
a 90s timeout for a project that needed no flags.

Parse an explicit port out of the dev script (--port/-p/PORT=) and use it
as the profile port so the supervisor waits where the server actually
binds. Conservative matching: word-boundary guarded so `--prefix`,
`npm-run-all -p`, and `run-p` are not misread, and out-of-range values are
ignored. Falls back to the framework default when no port is present.

https://claude.ai/code/session_01GVqD2pepFwpZG7dNp9T9jh
The message fallback /not[_ ]?found/i also matched ENOTFOUND, so `up stop`
while offline would report "Nothing to stop" instead of the network error,
leaving the user believing a still-running sandbox (and its public URL)
was stopped. Match the API status (404/410) only, like the SDK does;
anything else surfaces as a real error.

https://claude.ai/code/session_01GVqD2pepFwpZG7dNp9T9jh
Ships the changes from #4 and #5: hardened command construction, calmer
auth/stop/timeout error paths, low-friction env-file injection, and
honoring an explicit port in the project's dev script.

- Add the v0.1.0-beta.4 release artifacts (sha256-checksummed bundle).
- Point install.sh's default version at v0.1.0-beta.4.
- Print the next step after a successful install when no PATH change is
  needed, so the happy path ends with what to run instead of silence.

https://claude.ai/code/session_01GVqD2pepFwpZG7dNp9T9jh
Security-review follow-ups (F1, F2).

F1 — cap untrusted file reads during the pre-trust scan. up.config.json is
already bounded, but framework detection read package.json, and 283 of the
@vercel/frameworks detectors read other files (index.js, server.ts,
Cargo.toml, ...) whole into memory via the filesystem detector. A cloned
repo could bloat any of them and OOM the local `up` process before any
trust prompt. Detection now reads through a size-bounded filesystem
detector (oversized file -> treated as empty), and readPackageJson,
countDeps, and the env-file reader gained the same 4 MB guard. The env
guard matters now that the .env.local prompt defaults to yes.

F2 — SECURITY.md now states that `up .` runs the project's own install/dev
commands in your Vercel account (the sandbox isolates them from your
machine, not your account), and notes that only the dev command receives
the injected env file, never the install step.

https://claude.ai/code/session_01GVqD2pepFwpZG7dNp9T9jh
# Conflicts:
#	packages/cli/test/detect.test.ts
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
upcli Ready Ready Preview, Comment Jun 10, 2026 7:56pm
upcli-downloads Ready Ready Preview, Comment Jun 10, 2026 7:56pm

@suarezesteban suarezesteban merged commit 4d42e6d into main Jun 10, 2026
9 checks passed
suarezesteban pushed a commit that referenced this pull request Jun 10, 2026
suarezesteban pushed a commit that referenced this pull request Jun 10, 2026
Ships the bounded-read hardening (#7) on top of everything in beta.4.

- Add the v0.1.0-beta.5 release artifacts (sha256-checksummed bundle).
- Point install.sh's default version at v0.1.0-beta.5.

https://claude.ai/code/session_01GVqD2pepFwpZG7dNp9T9jh
@suarezesteban suarezesteban mentioned this pull request Jun 10, 2026
suarezesteban added a commit that referenced this pull request Jun 10, 2026
Ships the bounded-read hardening (#7) on top of everything in beta.4.

- Add the v0.1.0-beta.5 release artifacts (sha256-checksummed bundle).
- Point install.sh's default version at v0.1.0-beta.5.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants