feat(runner): stage managed Codex credentials (#12398)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Codex can authenticate through an API key or an `auth.json`
document.
> - Forwarding managed credentials in process arguments, durable session
options, or general environment state would leak them across the runner
boundary.
> - The ACPX sandbox therefore needs a private Codex home, bounded
staging, crash-safe cleanup intent, and cross-process ownership that
fails closed without relying on writable marker files.
> - This pull request establishes that credential boundary before later
PRs connect the Codex ACP process.

## Linked Issues or Issue Description

**Agent or provider**

The qualified Codex ACPX profile.

**Why this adapter is useful**

Managed installations may already have a Codex login document, while
service deployments may provide an API key or inline JSON. The runner
needs all three modes without copying ambient home state, accepting
ambiguous sources, persisting secret values in ordinary runner state,
following links, or allowing concurrent processes to mutate the same
credential home.

**How the agent is invoked**

A later runtime-host pull request acquires this credential lease before
opening Codex and closes it during shutdown and failed-start paths. API
keys remain launch-only environment values. JSON credentials are
atomically staged as `auth.json` inside the isolated Codex home. This
pull request does not spawn Codex, add a dependency, register an
adapter, or change execution selection.

**Additional context**

This is the lowest unmerged PR in the runner series and targets `master`
directly. It deliberately requires one explicit credential source and
never reads the Paperclip server user's ambient home by default.

## What Changed

- Add one-use managed Codex credential leases for API-key, inline-JSON,
and explicit managed-file modes.
- Reject missing and ambiguous source combinations.
- Require absolute external managed-file paths, private ownership and
permissions on POSIX, bounded documents, no-follow opens, and stable
file identity during reads.
- Stage JSON through a deterministic private no-follow staging path,
scrub stale staging state on admission, sync the file, rename
atomically, and sync the parent directory.
- Persist a bounded cleanup intent and remove staged or
provider-generated authentication files without following links.
- Fence each canonical credential home with a markerless 2-of-3
exclusive loopback quorum derived from the current UID and canonical
home. One unrelated occupied candidate is tolerated; two occupied
candidates fail closed before credential mutation.
- Bound stuck parent filesystem operations and cleanup helpers
process-wide so an unresponsive filesystem cannot create unbounded work.
- Zero temporary credential buffers and make cleanup retryable without
allowing a stale cleanup generation to affect a successor.
- Add focused tests for credential modes, permissions, malformed and
ambiguous sources, symlink rejection, deterministic staging, quorum
contention and crash release, stuck-operation bounds, and idempotent
cleanup.

## Verification

- Prettier and `git diff --check` passed for the exact pushed head.
- Greptile reviewed the exact head at 5/5 with no blocking finding;
independent static audits found no remaining correctness, security,
test-contract, or resource-bound blocker.
- GitHub Actions passed on the exact head
`29e75afb7b8529b9d070070e0c1462e795d115bb`, including Build, Typecheck,
the complete test matrix, e2e, verify, and security checks. One
unrelated hosted-runner `npm ENOENT` shard passed on its GitHub rerun.
- The diff contains 2 files and does not change `pnpm-lock.yaml`,
workflows, dependencies, public package exports, server selection, or UI
behavior.

## Risks

The primary risks are credential disclosure, concurrent ownership, and
cleanup that outlives its authority. Secret values never enter returned
identity or durable runner data. Source and destination operations are
bounded, no-follow, privately permissioned, and crash-durable. The
markerless kernel quorum prevents concurrent home mutation without
trusting a replaceable marker, while process-global operation/helper
caps prevent unresponsive filesystem work from growing without bound.
Cleanup is generation-scoped so a stale retry cannot remove a
successor's credential.

## Model Used

OpenAI Codex with GPT-5 and repository tool use.

## 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 linked an existing public item or described the
issue in this PR
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal task
identifier
- [ ] I have run the affected tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have documented the credential and cleanup boundary
- [x] All applicable GitHub Actions are green
- [x] Greptile is 5/5 with every actionable comment resolved
- [x] I will address all review findings before requesting merge
This commit is contained in:
Dotta 2026-08-30 17:51:33 -05:00 committed by GitHub
parent 001428a2d6
commit dba1a2d4f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 2927 additions and 0 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff