[VUL-506] fix(push): stop web CLI from reading server-side files via payload args#407
Conversation
push publish, batch-publish, and devices save accept a JSON payload that may also be a path to a local file (@file, a path prefix, or a bare path that exists), loaded via fs.readFileSync. In web CLI mode the filesystem is the shared terminal-server container rather than the user's machine, so loading a file there exposes server-side contents. Route all JSON-or-file payload inputs through a new AblyBaseCommand.resolveJsonInput() helper. It only reads from disk in local CLI mode; in web CLI mode the input is always treated as literal data and an @file reference is rejected with a clear message. The commands stay fully usable in the web CLI with inline JSON. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR fixes a server-side file-read vulnerability (VUL-506) in the web CLI. Three push commands ( Changes
Review Notes
|
There was a problem hiding this comment.
PR Review: fix(push) – stop web CLI from reading server-side files via payload args
Overview
This PR fixes VUL-506 by introducing resolveJsonInput in AblyBaseCommand, centralising the JSON-or-file logic so the web CLI restriction is enforced in one place rather than repeated (and potentially missed) per command. The security fix is correct and the approach is clean.
One real issue
Bare path inputs in web CLI mode give a misleading error
In web CLI mode, resolveJsonInput only explicitly rejects the @ prefix:
if (this.isWebCliMode) {
if (input.startsWith("@")) {
this.fail("Loading ... from a file is not supported in the web CLI...");
}
return input; // /path/to/file, ./file.json, etc. fall through silently
}A web CLI user who passes --payload ./push.json gets Invalid JSON for --payload: ./push.json from the downstream parseJsonObjectFlag, not the helpful "not supported in the web CLI" message. Secure? Yes — no file is ever read. Confusing? Yes — they have no idea why their path didn't work.
Since the path-prefix detection already exists in the local branch, the web CLI guard is easy to extend:
if (this.isWebCliMode) {
const looksLikePath =
input.startsWith("@") ||
input.startsWith("/") ||
input.startsWith("./") ||
input.startsWith("../");
if (looksLikePath) {
this.fail(
`Loading ${inputLabel} from a file is not supported in the web CLI. Pass the JSON inline instead.`,
flags,
component,
);
}
return input;
}Minor observation (not a blocker)
Expanded behaviour for push devices save --data
The original parseDeviceData only recognised @file. Via resolveJsonInput, --data now also accepts /absolute/path, ./relative/path, and bare paths that exist on disk in local CLI mode. That's arguably a nice improvement, but it isn't mentioned and has no test coverage. Not a problem, just worth noting in case it surprises anyone auditing the flag behaviour.
What's good
- The centralised helper is the right architectural call — future commands get the restriction for free, and the skill doc now points to it clearly.
- All three scenarios (local file load, web CLI bare path treated as JSON, web CLI
@filerejection) are covered by tests in all three affected commands. this.fail()returnsnever, so the@check in web CLI mode has no fall-through risk.BaseFlagscarries[key: string]: unknown, soRecord<string, unknown>fromparseDataFlagis compatible without a cast.
The path-prefix UX gap is worth fixing before merge, but the security property itself is solid.
Previously only an @file reference produced the "not supported in the web CLI" error; a bare path like ./push.json fell through and surfaced as a confusing "Invalid JSON" error from the downstream parser. Extend the web CLI guard in resolveJsonInput to reject any file reference (@file or a /, ./, ../ path prefix) with the clear message. Detection stays prefix-based so the helper never probes the server filesystem via existsSync. Also add test coverage for push devices save --data accepting a local path input (not just @file) in local CLI mode. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author's Note
Adds a new
resolveJsonInputfunction which blocks@fileparams in the web cli only. This allows us to still keep certain push commands in the web-cli e.g.push publishwhich is still useful functionality given a user could've configured their push env in the dashboard first. The alternative was a blanket block of any and all file uploading commands which would result in a worse web-cli experience than necessary.Summary
push publish,push batch-publish, andpush devices saveaccept a JSON payload that may also be a path to a local file (@file, a/·./·../path prefix, or a bare path that exists on disk), which is loaded viafs.readFileSync. In web CLI mode the filesystem is the shared terminal-server container rather than the user's machine, so loading a file there reads server-side contents.This mirrors the existing restriction already applied to
push config set-fcm/set-apns, extending it to the remaining payload-loading commands.Change
AblyBaseCommand.resolveJsonInput(input, label, flags, component)— a single helper for JSON-or-file inputs:@file/path inputs from disk (unchanged behaviour).@filereference is rejected with a clear message.push publish(--payload),push batch-publish(payload arg), andpush devices save(--data) through the helper instead of callingfs.readFileSyncon flag/arg values directly.The commands remain fully usable in the web CLI with inline JSON — only the server-side file-loading shortcut is removed there.
Tests
@filereferences are rejected, and that local-mode file loading still works.pnpm prepare,eslint(0 errors), and the affected unit tests pass.🤖 Generated with Claude Code