diff --git a/.agents/skills/paperclip-page/README.md b/.agents/skills/paperclip-page/README.md index cfe2d87882..47cf6d0f1e 100644 --- a/.agents/skills/paperclip-page/README.md +++ b/.agents/skills/paperclip-page/README.md @@ -493,12 +493,12 @@ Create secrets from environment variables so values do not land in shell history export PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID="$(jq -r '.AccessKey.AccessKeyId' /tmp/paperclip-page-uploader-key.json)" export PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY="$(jq -r '.AccessKey.SecretAccessKey' /tmp/paperclip-page-uploader-key.json)" -pnpm paperclipai secrets create \ +pnpm exec paperclipai secrets create \ --company-id \ --name paperclip-page-aws-access-key-id \ --value-env PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID -pnpm paperclipai secrets create \ +pnpm exec paperclipai secrets create \ --company-id \ --name paperclip-page-aws-secret-access-key \ --value-env PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY @@ -532,7 +532,7 @@ host `AWS_PROFILE` identity for the entire agent run: Create or update the company skill from this package: ```bash -pnpm paperclipai skills create \ +pnpm exec paperclipai skills create \ --company-id \ --name "Paperclip Page" \ --slug paperclip-page \ @@ -543,7 +543,7 @@ pnpm paperclipai skills create \ Attach it to an agent: ```bash -pnpm paperclipai skills agent sync \ +pnpm exec paperclipai skills agent sync \ --company-id \ --skill paperclip-page ``` diff --git a/doc/CLI.md b/doc/CLI.md index 5c75aeb4e8..c5eff04686 100644 --- a/doc/CLI.md +++ b/doc/CLI.md @@ -6,6 +6,56 @@ Paperclip CLI now supports both: - instance setup/diagnostics (`onboard`, `doctor`, `configure`, `env`, `allowed-hostname`, `env-lab`) - control-plane client operations (issues, approvals, agents, activity, dashboard) +## Security: safe invocation for content-bearing arguments + +Use `pnpm exec paperclipai` for any command whose argument can hold untrusted or +semi-trusted content. Untrusted content includes issue text, comment bodies, +Markdown, pasted snippets, and model output. `pnpm exec` runs the installed +binary directly. It passes the argument as an inert `argv` value. It does not +run a shell over the value. + +Do not use `pnpm paperclipai` for a content-bearing argument. The safe form and +the unsafe form differ only by the `exec` keyword, so read the command with +care. `pnpm paperclipai` is a `package.json` script. `pnpm` runs the argument +through `/bin/sh` first. The shell interprets these metacharacters before the +CLI starts: + +- command substitution: a backtick pair or `$( )` +- variable expansion: `$NAME` or `${NAME}` (this can leak a secret value into the persisted argument) +- other shell syntax: `; | & < >` + +A crafted value can run an arbitrary command as the invoking user. A crafted +value can also expand an environment variable into the stored argument. No +CLI-side check stops this, because the shell runs before `cli/src` starts. + +Safe forms: + +- `pnpm exec paperclipai ` — the documented default +- `npx paperclipai ` — also injection-safe; use it when no local + install is present. `npx` fetches the package from the npm registry when the + binary is not installed locally, so a mistyped name can start a network + fetch. `pnpm exec` never does this; it runs only the locally installed binary. +- `node /dist/index.js ` — trusted direct-binary call + +Unsafe forms for a content-bearing argument: + +- `pnpm paperclipai ` +- `pnpm run