mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-31 14:16:39 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - When an execution has managed GitHub, Paperclip gives it token-free `git` and `gh` launchers. Each launcher call gets the run's credential from the broker with a per-run capability (`PAPERCLIP_GITHUB_BROKER_TOKEN`). > - The launchers run inside the agent's shell commands and read the capability from their environment. > - #17216 limits the environment of Codex shell commands to an allowlist, and it keeps every `PAPERCLIP_GITHUB_*` name out. > - So on legacy Codex through ACP, the launchers do not get the capability, report `capability_missing`, and continue without credentials. > - The native runner already passes the same names to its Codex provider (`GITHUB_CREDENTIAL_ENVIRONMENT_KEYS` in `packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs`). > - This pull request adds the broker capability to the legacy Codex shell allowlist when Paperclip set it for the run. > - The benefit is that managed `git` and `gh` work again for legacy Codex agents. ## Linked Issues or Issue Description Fixes #17599 Refs #16549, #17216 ## What Changed - `packages/adapter-utils/src/codex-shell-environment.ts`: the shell allowlist also includes `PAPERCLIP_GITHUB_BROKER_TOKEN` and `PAPERCLIP_GITHUB_BRIDGE_TOKEN` when they have a value in the run's environment. Only the names enter `shell_environment_policy.include_only`. The values stay out of the Codex config and the arguments. - `packages/adapter-utils/src/codex-shell-environment.test.ts`: a new test for the capability. The "runtime transports" test no longer lists the broker token, because the launchers need it in the shell. ## Verification - `pnpm --filter @paperclipai/adapter-utils exec vitest run src/codex-shell-environment.test.ts`: 5 passed. The new test fails without the change. - Before the change, on a deployment with a legacy Codex agent that has managed GitHub, `gh pr view` in a shell command fails with `Paperclip: GitHub capability_missing; continuing without managed credentials`, and the shell has the broker URL but not the broker capability. I will add the result after the change is deployed there. ## Risks - Codex shell commands can now read the run's broker capability. The broker accepts it only while the run is live (`captureRunIdentity` requires `status === "running"`), and it returns the credential that the managed `gh` command receives anyway. - Agents cannot set these names in their configuration. `stripForbiddenEnvBindings` removes them (`server/src/services/heartbeat/run-preparation.ts`) before the server adds the run's own values. - No change for runs without managed GitHub: Paperclip does not set the capability, so the allowlist does not add it. ## Model Used - Provider and model: Anthropic Claude Opus 5.5 (`claude-opus-5-5`), used through Claude Code. - Capabilities used: tool use, code execution (local tests and type checks), and repository search. - A human reviewed the diagnosis and the change before this pull request was opened. ## 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) - [ ] 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] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [ ] I will address all Greptile and reviewer comments before requesting merge
45 lines
2.7 KiB
TypeScript
45 lines
2.7 KiB
TypeScript
import { expect, it } from "vitest";
|
|
import { codexShellEnvironmentKeys } from "./codex-shell-environment.js";
|
|
|
|
it("admits assigned service secrets by name while retaining unassigned secret exclusions", () => {
|
|
const assigned = { SLACK_BOT_TOKEN: "slack-canary", SERVICE_API_KEY: "key-canary", APP_SECRET: "secret-canary" };
|
|
const names = codexShellEnvironmentKeys({ ...assigned, HOST_API_TOKEN: "host-canary" }, assigned, false);
|
|
expect(names).toEqual(expect.arrayContaining(Object.keys(assigned)));
|
|
expect(names).not.toContain("HOST_API_TOKEN");
|
|
for (const value of Object.values(assigned)) expect(JSON.stringify(names)).not.toContain(value);
|
|
});
|
|
|
|
it("keeps Codex authentication, observability and runtime transports out of the service-secret opt-in", () => {
|
|
const configured = {
|
|
SLACK_BOT_TOKEN: "assigned", OPENAI_API_KEY: "openai", CODEX_API_KEY: "codex",
|
|
PAPERCLIP_AI_PROVIDER_KEY: "managed", OTEL_EXPORTER_OTLP_HEADERS: "authorization=export",
|
|
PAPERCLIP_NATIVE_MCP_TOKEN: "mcp",
|
|
};
|
|
const names = codexShellEnvironmentKeys(configured, configured, true);
|
|
expect(names).toContain("SLACK_BOT_TOKEN");
|
|
for (const key of Object.keys(configured).filter(key => key !== "SLACK_BOT_TOKEN")) expect(names).not.toContain(key);
|
|
});
|
|
|
|
it("does not turn a configured glob into permission to inherit other secrets", () => {
|
|
const configured = { "*TOKEN*": "wildcard", "SERVICE[AB]_KEY": "pattern" };
|
|
const names = codexShellEnvironmentKeys({ ...configured, HOST_TOKEN: "host" }, configured, true);
|
|
expect(names).not.toContain("*TOKEN*");
|
|
expect(names).not.toContain("SERVICE[AB]_KEY");
|
|
expect(names).not.toContain("HOST_TOKEN");
|
|
});
|
|
|
|
it("removes the secret opt-in when a binding is removed or has not been resolved", () => {
|
|
const environment = { SLACK_BOT_TOKEN: "old-provider-environment" };
|
|
expect(codexShellEnvironmentKeys(environment, {}, false)).not.toContain("SLACK_BOT_TOKEN");
|
|
expect(codexShellEnvironmentKeys(environment, { SLACK_BOT_TOKEN: { type: "secret_ref" } }, false)).not.toContain("SLACK_BOT_TOKEN");
|
|
expect(codexShellEnvironmentKeys(environment, {}, true)).not.toContain("SLACK_BOT_TOKEN");
|
|
});
|
|
|
|
it("passes the run's GitHub broker capability so the gh and git launchers can fetch its credential", () => {
|
|
const environment = { PAPERCLIP_GITHUB_BROKER_URL: "https://paperclip.example", PAPERCLIP_GITHUB_BROKER_TOKEN: "capability", PAPERCLIP_GITHUB_BRIDGE_TOKEN: "" };
|
|
const names = codexShellEnvironmentKeys(environment, environment, true);
|
|
expect(names).toEqual(expect.arrayContaining(["PAPERCLIP_GITHUB_BROKER_URL", "PAPERCLIP_GITHUB_BROKER_TOKEN"]));
|
|
expect(names).not.toContain("PAPERCLIP_GITHUB_BRIDGE_TOKEN");
|
|
expect(JSON.stringify(names)).not.toContain("capability");
|
|
});
|