@ambitresearch/paperclip-plugin-coder-sandbox
v0.2.0
Published
Paperclip sandbox provider plugin for Coder-backed workspaces
Readme
Paperclip Plugin Coder Sandbox
Paperclip Plugin Coder Sandbox is an Ambit Research Labs TypeScript plugin that provides a sandbox provider for Paperclip workflows backed by Coder workspaces.
Status
- Current release track:
0.2.0 - Repository:
ambitresearch/paperclip-plugin-coder-sandbox - Planned npm package:
@ambitresearch/paperclip-plugin-coder-sandbox(not yet published)
Prerequisites
- Node.js 22 or newer
- npm 10.9.8 (declared in
packageManager)
Installation
npm ci
npm run check
npm packThe package is not published yet. The resulting .tgz is the reviewable installation artifact for
Paperclip's plugin manager; do not treat a local pack as a release.
Development Commands
npm run build
npm test
npm run typecheck
npm run check:secrets
npm run check:version
npm run checknpm run check runs local repository safety checks plus test/type/build/audit gates.
Operator Setup
Coder prerequisites
The Paperclip host needs network access to the Coder deployment and a Coder CLI at coderBin. The
configured Coder template must:
- create an unprivileged workspace with no Docker socket;
- give that workspace user the dedicated
/home/coderhome:remoteCwdmust be a direct child of that home, and no unrelated workload may share that home or user; - run only trusted template and runner processes as the workspace user before initial realization;
- treat workspace-user-owned entries in
/tmp,/var/tmp, and/dev/shmas disposable; - define Coder's idle stop behavior (the default plugin setting requests eight hours);
- expose an authenticated Coder app named
paperclip-runnerfrom themainagent for the defaultapp-proxytransport; - accept any explicit entries in
workspaceParametersduring create and start.
The runner app must accept POST /run with { script, stdin, timeoutMs, env, envMode }, require
envMode: "replace", and execute script as /bin/bash --noprofile --norc -c <script> with env
replacing its process environment rather than merging into it. It returns
{ exitCode, signal, timedOut, stdout, stderr }. Coder's app proxy authenticates the request with
the coder_session_token cookie and strips that cookie before forwarding to the runner. rpty and
ssh are supported fallbacks through the local Coder CLI. The legacy unauthenticated
direct-runner transport is disabled.
CODER_HEADER and CODER_HEADER_COMMAND apply only to direct Coder control-plane requests. They
are intentionally not forwarded through the app proxy to workspace runners. On Windows,
CODER_HEADER_COMMAND is rejected because descendant process cleanup cannot be guaranteed; use
CODER_HEADER instead.
workerImage, agentWorkspaceImages, and adapterWorkspaceImages are compatibility metadata.
They do not guess a Coder template parameter name. To select an image, configure the parameter your
template actually declares, for example "workspaceParameters": { "container_image":
"example/agent-runtime:1.2.3" }.
Credentials
- Keep secrets in local environment configuration that is not committed.
- Resolve
apiTokenand the compatibilitygitAuthTokenthrough Paperclip secrets; a UUID-looking unresolvedapiTokenproduces a validation warning. IfgitAuthTokenis used, scope it to one repository and rotate it frequently. - Prefer host-side GitHub App credentials:
GITHUB_APP_ID,GITHUB_INSTALLATION_ID(orGITHUB_APP_INSTALLATION_ID), and eitherGITHUB_APP_PRIVATE_KEYorGITHUB_APP_PRIVATE_KEY_FILE. Keep the private key on the Paperclip/plugin host. The plugin never forwards App IDs or private-key material to Coder or agent commands. - For an exact credential-free
https://github.com/owner/repositoryURL, the plugin mints a short-lived installation token restricted to that repository. Only the transient token reaches the authenticatedapp-proxyrunner; command output and errors redact both the token and its Git Basic-header representation. - Agent-propagated GitHub App credentials can authenticate the initial private clone only when the
Paperclip host forwards the resolved per-run adapter environment to
environmentRealizeWorkspace. Hosts whose realization contract omits that environment cannot authenticate the initial clone; later command execution deliberately strips GitHub credentials rather than retaining them. Provider-configuredagentWorkspaceEnvremains a compatibility path, not an Agent Identities source-of-truth integration. - GitHub authentication is realization-only: the initial private clone/fetch is authenticated during workspace realization, and subsequent agent command execution runs without those credentials.
- Do not put credentials, signed query parameters, or fragments in repository URLs.
- Do not commit generated artifacts or recovery/provenance records.
- Run
npm run check:secretsbefore pushing.
Minimal environment-driver configuration
{
"driverKey": "coder",
"coderUrl": "https://coder.example.com",
"apiToken": "paperclip-secret-reference",
"gitAuthToken": "paperclip-secret-reference",
"template": "docker",
"workspaceParameters": {},
"remoteCwd": "/home/coder/workspace",
"commandTransport": "app-proxy",
"runnerAppSlug": "paperclip-runner",
"reuseLease": false,
"stopAfterHours": 8
}Version 0.1.0 is deliberately cold-only. Every acquisition generates a unique workspace and lease;
workspaceName, when set, is only a prefix. reuseLease: true is rejected, and the manifest reports
that reusable leases are unsupported. A newly created workspace is deleted if readiness or marker
claim fails.
Release and destroy authorize the persisted lease locally, take its workspace lock, and ask the
Coder control plane to delete the persisted immutable workspace UUID directly. They do not start the
workspace, wait for its runner, or execute cleanup code inside it. A repeated cleanup succeeds when
Coder's authenticated API confirms that the same UUID's latest build is deleted. Legacy
stopOnRelease and deleteOnDestroy values are parsed for compatibility but do not change this
behavior. Legacy reusable lease records that contain the immutable workspace UUID and complete
ownership metadata can be released or destroyed, but cannot be resumed, realized, or used to
execute commands. Older records without those fields fail closed and require manual workspace
deletion.
The environment-driver form exposes these transport settings:
commandTransportdefaults toapp-proxy, which authenticates through Coder'scoder_session_tokencookie; Coder strips it before forwarding.rptyandsshuse the local Coder CLI.runnerAppSlugselects the Coder workspace app used byapp-proxyand defaults topaperclip-runner.directRunnerUrlis retained only to parse legacy configurations; direct execution is refused.
Benchmarking
The benchmark harness runs operator-supplied probes and emits raw redacted JSON samples. Each
successful probe must exit zero and print non-empty evidence; missing commands, exit 127, empty
output, and unconfigured probes are UNVERIFIED, never PASS.
The report schema retains the historical warmAcquisition probe for comparison. It is not evidence
that reusable workspaces are supported by 0.1.0.
Create a local command file that is not committed:
{
"warmAcquisition": "./ops/probe-warm-acquire.sh",
"coldAcquisition": "./ops/probe-cold-acquire.sh",
"coldFirstCommand": "./ops/probe-cold-first-command.sh",
"steadyCommand": "./ops/probe-steady-command.sh",
"cleanup": "./ops/probe-cleanup.sh"
}Run the frozen SLO suite (100 samples per metric by default):
npm run benchmark -- \
--config ./benchmark-commands.json \
--samples 100 \
--output ./benchmark-results.jsonThe output file is forced to mode 0600. Exit status is 0 for PASS, 1 for FAIL, and 2
for UNVERIFIED.
| Metric | Frozen target | | --- | --- | | Warm acquisition | p50 <= 1.5s, p95 <= 3s, success >= 98% | | Cold acquisition | p50 <= 20s, p95 <= 45s, success >= 98% | | Cold first command | p50 <= 15s, success >= 99% | | Steady command | p50 <= 1s, p95 <= 2s, success >= 99% | | Cleanup | p50 <= 5s, p95 <= 15s |
The report recommends the Kubernetes fallback after two consecutive cold-acquisition failures or a cold-acquisition p95 breach. It records the decision only; it does not mutate provider settings.
Architecture (Public-Level)
The plugin is organized into focused modules:
src/manifest.tsandsrc/plugin.tsdefine plugin integration points.src/worker.tsandsrc/process.tscoordinate workspace actions.src/config.ts,src/types.ts, andsrc/index.tsprovide shared configuration/types and exports.
Build output is generated into dist/ from TypeScript sources in src/.
Commands use Coder's authenticated app proxy by default; RPTY and SSH remain opt-in transports.
Security Note
If you discover a security issue, follow SECURITY.md for reporting guidance through GitHub Security Advisories.
