## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Paperclip provides CLI commands and guidance for operators and agents > - The `pnpm paperclipai` script can pass argument values through a shell > - Shell re-parsing can execute command substitutions inside quoted values > - This pull request routes guidance through inert-argv `npx paperclipai` commands and adds regression coverage > - The benefit is safer operator guidance across documentation and runtime hints ## Linked Issues or Issue Description This pull request fixes a command-injection-class defect in Paperclip CLI guidance. **What happened?** The `pnpm paperclipai <sub> --flag "$VALUE"` form can re-parse argument values through a shell. A command substitution inside a quoted value can execute on the host. **Expected behavior** Paperclip guidance must pass CLI values as inert argument values. Host-derived values must not appear in copyable commands. **Steps to reproduce** 1. Run a Paperclip guidance command that uses the `pnpm paperclipai` script. 2. Provide a quoted value that contains a command substitution. 3. Observe that the shell can evaluate the substitution before the CLI starts. 4. Compare the result with the `npx paperclipai` form. **Paperclip version or commit** `5670984b75d109950c968542a0111ebb6967f4da` **Deployment mode** All deployment modes that show or use the affected CLI guidance. **Installation method** Built from source and installed CLI guidance. **Agent adapter(s) involved** Not adapter-specific (core bug). **Database mode** Not database-related. **Access context** Both. **Additional context** The earlier merged PR [#11343](https://github.com/paperclipai/paperclip/pull/11343) used the unsafe `pnpm exec paperclipai` form. This fresh PR replaces that guidance with the safe `npx paperclipai` form. ## What Changed - Standardize documentation and runtime hints on `npx paperclipai`. - Remove the broken `pnpm exec paperclipai` guidance. - Use a static `<host>` placeholder in private-hostname guidance. - Add regression tests for unsafe forms, continued lines, static hosts, and offline guidance. ## Verification - `git diff --check origin/master...origin/fix/paperclipai-cli-npx-safe-invocation` passes. - The branch adds `server/src/__tests__/cli-invocation-safety.test.ts` and updates private-hostname tests. - CI must run the new tests, typecheck, lint, and build checks. - Local Vitest execution was not available because this worktree has no installed Vitest binary. ## Risks - The change affects operator and agent documentation text. - The runtime hints now show `<host>` instead of a request-derived host value. - No database schema or migration changes exist. - CI will detect any missed unsafe invocation or type error. ## Model Used OpenAI GPT-5, exact model ID `gpt-5`, with tool use and code-review assistance. The model used repository inspection, Git operations, and PR preparation. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] CI ran the test suites and they pass; local test execution was unavailable in this worktree - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I addressed all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
4.0 KiB
Manual smoke test — @paperclipai/plugin-kubernetes
Manual sanity check that the plugin works end-to-end against a real paperclip-server instance and a real Kubernetes cluster (kind for local dev). Future work may automate this in CI.
Prerequisites
- A running kind cluster:
kind create cluster --name paperclip kubectl --context kind-paperclip get nodesreturns a node inReadystate.
Steps
1. Build the plugin
cd packages/plugins/sandbox-providers/kubernetes
pnpm install --ignore-workspace
pnpm build
Expected: dist/ populated with compiled .js and .d.ts files. No errors.
2. Start paperclip-server in dev mode
In a separate terminal:
cd /path/to/paperclip
export PAPERCLIP_HOME=/tmp/paperclip-smoke
export PAPERCLIP_INSTANCE_ID=smoke
export PAPERCLIP_DEPLOYMENT_MODE=local_trusted
pnpm --filter @paperclipai/server dev
Wait for Server listening on 127.0.0.1:3100.
3. Install the plugin via the CLI
npx paperclipai plugin install \
--local /path/to/paperclip/packages/plugins/sandbox-providers/kubernetes \
--api-base http://127.0.0.1:3100
Expected: ✓ Installed paperclip.kubernetes-sandbox-provider v0.1.0 (ready).
4. Create a company and a kubernetes sandbox environment
CO_ID=$(curl -s -X POST -H "Content-Type: application/json" \
-d '{"name":"SmokeCo"}' \
http://127.0.0.1:3100/api/companies | jq -r '.id')
KUBECONFIG_CONTENT=$(cat ~/.kube/config | jq -Rs .)
curl -s -X POST -H "Content-Type: application/json" \
-d "{
\"name\": \"k8s-sandbox\",
\"driver\": \"sandbox\",
\"config\": {
\"provider\": \"kubernetes\",
\"kubeconfig\": $KUBECONFIG_CONTENT,
\"companySlug\": \"smoke\",
\"adapterType\": \"claude_local\",
\"imageAllowList\": [\"ghcr.io/paperclipai/agent-runtime-claude:v1\"]
}
}" \
http://127.0.0.1:3100/api/companies/$CO_ID/environments | jq
Expected: HTTP 201 with the new environment row.
5. Probe the environment
ENV_ID=$(curl -s http://127.0.0.1:3100/api/companies/$CO_ID/environments | jq -r '.[0].id')
curl -s -X POST -d '{}' -H "Content-Type: application/json" \
http://127.0.0.1:3100/api/environments/$ENV_ID/probe | jq
Expected: {"ok": true, ...} with a summary mentioning the tenant namespace
(paperclip-smoke). On first probe the namespace may not yet exist —
the plugin treats a 404 on listNamespacedPod as a successful reachability
check.
6. Trigger an agent run
Use the UI or the API to dispatch a run against the k8s-sandbox environment.
The plugin's onEnvironmentAcquireLease will:
ensureTenant— provision thepaperclip-smokenamespace, SA, Role, RoleBinding, ResourceQuota, LimitRange, NetworkPoliciesbuildJobManifest— render the security-hardened Job manifestcreateJob— submit tobatch/v1createPerRunSecret— owned by the Job for cascade-delete
7. Verify the tenant resources
kubectl --context kind-paperclip get namespace paperclip-smoke
kubectl --context kind-paperclip get all,networkpolicy,resourcequota,limitrange,sa,role,rolebinding -n paperclip-smoke
Expected:
- Namespace
paperclip-smokeexists with PSS labels (pod-security.kubernetes.io/enforce=restricted) - ServiceAccount
paperclip-tenant-sa - Role
paperclip-tenant-role, RoleBindingpaperclip-tenant-rb - ResourceQuota
paperclip-quota, LimitRangepaperclip-limits - NetworkPolicies
paperclip-deny-all+paperclip-egress-allow - Job
pc-{ulid}and its child Pod - Secret
pc-{ulid}-envwithownerReferencespointing at the Job
8. Tear down
kubectl --context kind-paperclip delete namespace paperclip-smoke
kill %1 # paperclip-server
9. Document the result
In the PR description (or appended to this file as a dated section), record:
- Date + git SHA
kubectl versionserver version- Output of
kubectl get all -n paperclip-smokeafter step 6 - Probe response from step 5
- Time-to-acquire-lease (target: <30s on kind for a cold tenant)