Skip to content
Experiment Report
Awaiting weekly review
The Credential File Disappeared. Did Access Disappear Too?
Open-source Engineering · Experiments

The Credential File Disappeared. Did Access Disappear Too?

A pinned-source experiment shows why fewer credential files do not necessarily mean fewer secrets available to the receiving process.

2026-09-10中文 →

The Credential File Disappeared. Did Access Disappear Too?

A credential fix reduced file-materialization callbacks from four to zero. That looks reassuring—until the receiving process is inspected. In our controlled experiment, the same four synthetic credential blobs appeared as environment variables.

There was no mysterious bypass. The values had changed delivery routes. The question is what “credential isolation” promises: a restriction on where files are created, or a restriction on which values a process can obtain.

A useful fix with a narrower contract

OpenHands Agent SDK can launch different agent programs through ACP, a protocol connecting an agent client to an execution program. Those programs use different authentication mechanisms, including environment variables and credential files.

PR #4927 narrows file-secret handling by provider ownership. The default configuration combines multiple providers' file rules; the proposed change avoids materializing unrelated providers' files when starting one particular program. That is a useful correction to credential representation and directory configuration.

File rules, however, are not a secret allow-list. The startup path also exports registry secrets as ordinary environment variables, excluding those handled as files. Narrowing the file set narrows this exclusion set too. A value removed from one route can consequently reappear on the other.

This does not make the patch a failed implementation of its stated goal. It means reviewers should not add a stronger promise—“all other providers' secrets become inaccessible”—that the change does not establish.

Observe the recipient, not just the writer

We pinned the PR's base and head revisions and executed its original filtering methods, environment-conflict method, and contiguous environment-construction slice. Both revisions used registry metadata extracted from the head to isolate the handling change. Provider identity was fixture-supplied; actual command detection was not tested. All inputs were synthetic strings.

File materialization was a recording fixture: the file-side numbers below count callbacks, not authentication files actually written to disk. The assembled environment was passed to a real Python child process, which returned variable names only. It did not launch a model or attempt authentication.

Revision and recipientFile-side callbacksFour credential-blob variables visible to child
Base, Claude40
Proposed head, Claude04
Base, Codex40
Proposed head, Codex13
Proposed head, unrecognized provider40

Both rounds produced the same results. The four blobs correspond to registered file credentials for Codex, Gemini, Kimi and Pi. Codex retained its own file route while three other blobs became ordinary environment variables. An unrecognized provider retained every file specification. Those branches cannot all be summarized as “only this provider's credentials remain.”

File-side and environment-side observations

Figure 1. The figure uses our local seam-test observations. It establishes delivery of synthetic values through the tested construction path, not their use by a real CLI or disclosure over a network. Source: saved rounds 1 and 2 of our pinned-source experiments.

An empty list can have an authentication exception

Draft PR #4931 addresses a different layer: profile-level restrictions on secret names received by a conversation. Filtering the registry can narrow both downstream delivery routes.

We placed that PR's original filtering functions before the earlier environment slice. This is explicitly a laboratory composition of two separate PRs, not a merged SDK release or a server integration test.

Given the same six synthetic inputs, the Claude ACP profile produced:

secret_refsNames retained in the registry
nullAll six supplied names
[]The provider's ANTHROPIC_API_KEY
[PROD_DB_URL]The database variable and provider authentication variable

The empty list removed business secrets while retaining an explicit authentication exception. The program still needs a way to authenticate. As a separate control, we executed only the ordinary OpenHands profile's empty-list filter and obtained an empty set; that control did not pass through ACP startup.

An interface therefore needs to explain what its empty list means. “No selected business secrets” and “this process receives no secrets at all” are materially different expectations.

The registry is not every input channel

The tested startup slice also merges the host environment. A separate synthetic marker placed on that route remained visible to the child in every ACP scenario, including the empty-list case.

This is not a production leak report. It shows that a registry filter cannot constrain an input that bypasses the registry. The draft explicitly leaves host-environment inheritance outside its scope.

A stronger acceptance test begins by defining the business secrets and authentication materials authorized for a provider and run, then observes the actual environment and file capabilities at the receiving process boundary. Representation changes should follow that selection. Unknown-provider behavior needs its own explicit policy as well.

Our experiment did not exercise the complete SDK, a real ACP handshake, authentication, or server request filtering. Its conclusion is narrower: a disappearing credential file is insufficient evidence of disappearing access. Trace every delivery route for the same secret and inspect the set the recipient actually receives.

中文 · Methods, pinned revisions and raw results

Last updated: