The credential-invisibility ladder: toward workloads whose runtime never holds a secret
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 9
- Forks
- 0
- Avg merge
- 3h 3m
- Merged PRs (30d)
- 509
Description
The tj-actions compromise scraped runner memory, and it worked because in GHA's model the credential is in the runner's memory — masking redacts logs after the fact, but the material itself sits in the process every attacker-controlled dependency shares. Flowstate already refuses the first half of that sin (references in history, resolution only inside the activity that needs the value, scrubbing for accidents, vetted binaries #146 for malice). This issue records the rest of the ladder: each rung further shrinks where the plaintext exists at all, which is the only defense memory-scraping respects. The goal state is workloads where the answer to "where does the credential live" is, increasingly often, "nowhere the workload can reach."
The ladder, bottom to top
Rung 0 — where we are: secret refs resolve in-activity; the task process holds plaintext for the duration of its use; scrubber + containment tests bound accidents; #151 keeps values on local transport; #146 (vetted binaries) bounds malice. Honest statement: a compromised task binary can read the credential it was handed. That's the rung everything above exists to shrink.
Rung 1 — short-lived, scoped credentials (WIF/OIDC), already begun: JIT federation landed (#115, examples/http-federated) — the durable secret is an identity, not a credential, and what the activity holds is minutes-lived and audience-scoped. Expansion path: more issuers/exchange shapes (cloud WIF, GitHub OIDC as a consumer — flowstate workloads exchanging their own workload identity for scoped upstream tokens), and making the exchange the documented default pattern for cloud access rather than an advanced feature. A scraped token that expires in minutes and names one audience is a categorically smaller loss than a PAT.
Rung 2 — credential-injecting egress proxy: the runtime never sees it at all. The pattern Codex and Claude Code are converging on, and the natural next flowstate tier: the workload's outbound HTTP goes through a host-side (or sidecar) proxy that holds the credential and injects Authorization/auth headers at the boundary. The task process — including any plugin binary — makes an unauthenticated request to an allowlisted upstream and never possesses the material. What makes flowstate unusually well-placed: egress policy already exists as a fail-closed, compiled surface, and the proxy is its natural enforcement point — policy and injection at the same choke point, one config. Design constraints when this lands: injection rules bind (upstream host, path pattern, secret ref) explicitly per workflow/tenant — no ambient injection (that would be GHA's sin with better plumbing); the proxy is where scrubbing becomes prevention (a response echoing the injected header can be caught before the task sees it); mTLS or socket auth between task and proxy so a neighbor process can't borrow the injection; and the Flowfile spelling stays explicit — a step declares it reaches github.com via the credentialed egress, so the file names the authority flow even though it never touches the value.
Rung 3 — OS-native and hardware-backed stores as providers: keychain (macOS), libsecret, TPM/enclave-backed keys as secrets providers, so even the host-side material is non-exportable where the platform allows — sign/decrypt happens in the enclave, the provider returns results, not keys, where the operation permits. Fits the existing provider interface; the design work is deciding which operations (signing especially — #163 synergy) can be operation-proxied rather than value-returned.
Rung 4 — compute/network/storage as one governed surface: the isolation runners (#156 ladder: subprocess → runc → gVisor → microVM) bound compute; the egress proxy bounds network; the content-addressed store (#149 scale comment) bounds storage. The enterprise pitch is that these are one coherent policy plane — a workload's file declares what it needs, the deployment's policy decides what it gets, and both developers and security engineers read the same explicit spellings. Nothing ambient, everything auditable, defaults closed.
Discipline
Each rung is named-not-scheduled (#166 rule) except rung 1 expansion, which has landed foundations and real demand shape. Rung 2 is the highest-leverage next design when a workload demands it — and it should be designed against the #163 git-credential case and the github plugin's auth modes first, since "push to GitHub without the workload ever holding the token" is the concrete, demonstrable win that proves the tier. Every rung keeps the standing rules: explicit over ambient, fail closed, both drivers honest, and the Flowfile always names the authority flow even when it never touches the material.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with examples/http-federated and the existing egress-policy surface, then review the referenced #163 git-credential case and GitHub plugin auth modes. Define one concrete rung-2 design scope, including its explicit Flowfile authority spelling and fail-closed behavior; done means the design is bounded enough to schedule and evaluate against those cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- authentication, backend, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100