You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(dpapi): add secret store with import for dpapi (#1) - #245
import(), used only when the flag is on and an OS store exists: moves the token into the store, reads it back, then removes the clear-text copies.
A bootstrap file (openaev-agent-token.bootstrap) wins over the config. It is the way to replace a stored token.
A verified stored token wins over an older config token.
A store read error is not treated as a missing token: nothing is overwritten.
A pending marker (openaev-agent-token.pending) makes sure an unverified write is never trusted.
replace_file(): rewrites the config in one step and keeps its ACL / mode. On Windows, a backup is kept and put back if the swap fails halfway. Leftovers are recovered or deleted at the next start.
If the token line cannot be removed: retried, then a clear error (read-only config: an administrator must remove the line).
The token becomes optional in the config.
flowchart TD
S[Agent start] --> F{os_store?}
F -- off --> C[Read the token from the config<br/>on each request, as in #239]
F -- on --> I[Token from bootstrap file or config]
I --> W[Write to the store, read back]
W -- fails --> K[Keep clear text, retry next start]
W -- ok --> D[Remove config line, delete bootstrap file]
I added/update the relevant documentation (either on github or on notion)
Where necessary I refactored code to improve the overall quality
For bug fix -> I implemented a test that covers the bug
Further comments
Edge cases covered by tests: crash during the move or the config rewrite, failed or mismatching read-back, store read errors, older config token, leftover backup holding the token.
❌ Patch coverage is 76.83824% with 63 lines in your changes missing coverage. Please review.
✅ Project coverage is 49.95%. Comparing base (8c462f0) to head (857ff51).
❌ Your patch check has failed because the patch coverage (76.83%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.
Test import rollback when credential read-back fails or mismatches
src/secrets/import.rs:80
The import tests cover a failed set, but not a successful set followed by a failed or mismatching read-back. Add a test store for each case and assert that import returns an error while leaving both the config and bootstrap contents unchanged. These tests protect the check that prevents deletion of the only usable credential copy.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
An unresolved pending-token guard and security-sensitive Windows recovery behavior require correction and platform-specific human validation.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Return an error when pending token has no verification source
src/secrets/import.rs:94
If a pending marker remains but the bootstrap/config token is no longer available, this branch only logs and import() returns Ok(()), even though it rejected the stored token as unverified. Once an OS store is enabled, open() interprets that success as permission to use the store, so requests can use the unverified value. Return an error for this pending-without-source case and add a regression test with a readable stored token, a pending marker, and no clear-text token. No OS store is enabled in this PR, so this currently affects the migration helper rather than live requests.
Since #245, import() refuses to write the config token when the store
returns a read error, and only writes it when the token is missing.
DpapiStore reported a missing or empty file (the empty file the installer
creates) as a generic error, so older installs with the token only in
the config could never be migrated.
DpapiStore::get now uses missing() for a missing or empty file. A file
that cannot be decrypted is still a read error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #245, import() refuses to write the config token when the store
returns a read error, and only writes it when the token is missing.
DpapiStore reported a missing or empty file (the empty file the installer
creates) as a generic error, so older installs with the token only in
the config could never be migrated.
DpapiStore::get now uses missing() for a missing or empty file. A file
that cannot be decrypted is still a read error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed changes
Groundwork to keep the agent token in an OS secret store. No OS store is plugged in yet (#240 adds DPAPI), so the agent behaves like #239.
[secrets]os_store, off by default. Off: the token is read from the config on each request, as in feat(agent): read the token on each request instead of keeping it in memory (#1) #239. Nothing is written or deleted.import(), used only when the flag is on and an OS store exists: moves the token into the store, reads it back, then removes the clear-text copies.openaev-agent-token.bootstrap) wins over the config. It is the way to replace a stored token.openaev-agent-token.pending) makes sure an unverified write is never trusted.replace_file(): rewrites the config in one step and keeps its ACL / mode. On Windows, a backup is kept and put back if the swap fails halfway. Leftovers are recovered or deleted at the next start.flowchart TD S[Agent start] --> F{os_store?} F -- off --> C[Read the token from the config<br/>on each request, as in #239] F -- on --> I[Token from bootstrap file or config] I --> W[Write to the store, read back] W -- fails --> K[Keep clear text, retry next start] W -- ok --> D[Remove config line, delete bootstrap file]Testing Instructions
[secrets]/os_store = trueto the config and restart: nothing changes in this PR (no OS store yet), the agent stays online.openaev-agent-token.bootstrapnext to the exe and remove it from the config: the agent stays online.Automated:
cargo test --locked.Related issues
Checklist
Further comments
import()is only exercised by unit tests until feat(agent): encrypt the token with DPAPI on Windows (#1) #240.toml_edit,windows-sys.