Security: redact paired API key from Insights log ring buffer#3
Draft
mimeding wants to merge 2 commits into
Draft
Security: redact paired API key from Insights log ring buffer#3mimeding wants to merge 2 commits into
mimeding wants to merge 2 commits into
Conversation
When a pairing request is approved, /pair mints a master-scoped osk-v1
API key (agentIndex: nil, expiration: .never for permanent pairings)
and returns it once to the connector. That secret was also being
stored verbatim in the Insights log ring buffer via logRequest's
responseBody field.
Insights logs are kept in memory and rendered in the app UI, so any
of the following leaked the full credential:
* a screen recording or shoulder-surf while the Insights tab is open
* a memory dump or crash report
* a future support-bundle/log-export feature
* the existing in-app InsightsView search box
Replace the apiKey field with a clearly-marked placeholder
('osk-v1-***REDACTED***') in the logged copy of the response only.
The wire body returned to the legitimate paired connector is
unchanged, so end-to-end pairing flow keeps working exactly as
before; this commit only stops the secret from being saved into the
diagnostic ring buffer that the user can browse after the fact.
Co-authored-by: Michael Meding <mimeding@users.noreply.github.com>
ModelManager.init kicks off an unstructured Task that calls loadOsaurusAIOrgModels(), which fetches the OsaurusAI organization listing from Hugging Face and feeds the result through applyOsaurusOrgFetch. The unit-test runner repeatedly constructs ModelManager() to drive applyOsaurusOrgFetch directly. The background launch-time fetch races with those test calls — whichever finishes last wins, and the merge result is non-deterministic. That's the root cause of the flaky ModelManagerSuggestedTests failures seen across many of the recent PR CI runs (applyOsaurusOrgFetch_dropsStaleAutoFetched OnReapply, applyOsaurusOrgFetch_addsNewEntriesAfterCurated, etc.). Gate the launch-time fetch on a small isRunningInTestEnvironment helper that checks for any of XCTestConfigurationFilePath, XCTestBundlePath, or XCTestSessionIdentifier in the process environment. Those variables are only present inside an xctest host process; production app launches still get the HF fetch exactly as before. This is a network call, so removing it under tests also has the side benefit of making the test suite work offline / on hermetic CI runners. Co-authored-by: Michael Meding <mimeding@users.noreply.github.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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Why this matters (business)
Pairing is the moment Osaurus hands a long-lived, master-scoped API key to a trusted device. That key grants the same level of access as the user's primary credential, which is exactly why the UI shows it only once and asks the user to copy it.
Today, the same secret is also being silently saved to the in-app Insights log ring buffer, where it can be read back at any time without any prompt. Screen recordings, screen-sharing during a support call, a future "Export Insights" support-bundle action, or any process that can dump app memory now contains a credential the user thought they only revealed to one peer.
This is a classic "secrets in logs" problem and the kind of audit finding that drags into security reviews. The fix here is cheap and behavior-preserving.
What's wrong (technical)
handlePairEndpointbuilds aPairResponseon success, writes it to the wire, and also passes the same JSON string to the Insights logger:// 5. Return the agent's address, the generated API key, and the permanence flag. let response = PairResponse(agentAddress: agentAddress, apiKey: fullKey, isPermanent: isPermanent) let json = (try? JSONEncoder().encode(response)).map { String(decoding: $0, as: UTF8.self) } ?? #"{"error":"Encoding failed"}"# hop { ... logSelf.logRequest( method: "POST", path: "/pair", ... responseBody: json, responseStatus: 200, ... ) }InsightsServicekeeps a 500-entry in-memory ring buffer that is bound to a SwiftUI list. So the fullosk-v1-...value is:The connector still receives the real key on the wire; this is the only code path that copies it elsewhere.
Fix
Build a second, redacted
PairResponseexclusively for the Insights call. TheapiKeyfield becomes the clearly-marked placeholder stringosk-v1-***REDACTED***. The wire response is byte-identical to before — pairing continues to work end-to-end.Out of scope (intentional, can be follow-ups):
Authorizationheaders./pair, agent-scoped key by default, replay-nonce store). Those are larger threat-model changes; this PR is purely the leak fix.Changes
Test Plan
POST /pairrow.responseBodycolumn contains the fullosk-v1-...key.responseBodycolumn shows"apiKey":"osk-v1-***REDACTED***"while the paired connector continues to authenticate successfully (proving the wire body is untouched).Checklist
CONTRIBUTING.md