fix(sdk): redact plugin and extension source credentials at the serialization boundary (#3752)#3786
Open
haya-asif wants to merge 3 commits into
Open
Conversation
Author
|
This PR is ready for review and implements the fix tracked in #3752 — redacting plugin/extension The |
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.
HUMAN:
This PR fixes a credential leak where plugin/extension source URLs containing embedded tokens (e.g.https://oauth2:TOKEN@host/repo.git) were written verbatim to persisted state (.installed.json, conversation state) and logs. The fix redacts them at the model serialization boundary while keeping the raw value in memorynfor fetching. I reviewed the change and ran all test successfully.
AGENT:
Why
Closes #3752.
Credential-bearing plugin/extension
sourceURLs (e.g.https://oauth2:TOKEN@github.com/org/repo.git) reached persisted state(
.installed.json, conversation state) and logs through theplugin/extension source path. Redaction existed only as a manual call inside
ResolvedPluginSource.from_plugin_source, so any othermodel_dump()/model_dump_json()of a plugin source emitted raw credentials.This is the SDK-side sibling of OpenHands/OpenHands#12959 (fixed there by adding
a
@field_serializer('source')to that repo'sPluginSpec); the equivalentchoke point was never added to the SDK's own models. Per the #2196 review, the
durable fix is a single redaction boundary on the model (a serializer) plus
explicit redaction at the log sites that bypass serialization — not
per-call-site scrubbing.
Summary
@field_serializer("source")toPluginSource,ResolvedPluginSource,and
InstallationInfo, so credentials are redacted on everymodel_dump/model_dump_json(including nested and persisted dumps). The raw value iskept in memory so the plugin can still be fetched/cloned.
from_plugin_sourcenow stores thesource verbatim instead of pre-redacting it, so the serializer is the single
source of truth.
spec.sourcein thelocal_conversationplugin-load debug log(f-strings bypass serializers), matching the existing
plugin/loader.pysite.Maps to the acceptance criteria in #3752:
PluginSourceredactssourcevia@field_serializer(covers subclasses + nested dumps).ResolvedPluginSource.from_plugin_sourceconsolidated onto the serializer.InstallationInfo.sourceredacted before being persisted to.installed.json.local_conversationplugin-load log redactsspec.source.plugin/loader.pyalready redacts; theextensions/fetch.pyerror strings only ever emitgithub:/ local / unparseable forms (a credentialedhttps://…@…URL is classified asGITbefore reaching them), so no raw credentials leak there..installed.json, the plugin-load log), not just the redaction regex.Issue Number
Closes #3752.
How to Test
All commands run from the repo root.
1. Reproduce the leak before the fix (run on
main, before this change):2. Verify the fix (this branch) — credentials redacted on dump, raw kept in memory, non-credential forms unchanged, and the live
.installed.jsonwrite redacted:3. Tests — added live-path regression tests; updated two existing tests that asserted construction-time (in-memory) redaction to assert serialization-time redaction.
4. Lint + types
Type
Notes
Behavior change worth a reviewer's eye:
ResolvedPluginSource.sourceand thein-memory plugin specs now keep the raw URL (redaction moved to the
serialization boundary). Persisted state and logs are redacted exactly as
before; on resume the redacted source plus the resolved commit SHA re-fetch from
the local cache, unchanged. The two updated tests in
test_url_redaction.pyreflect this (serialization-time rather than construction-time redaction).
Out of scope (tracked separately): git/workspace command-layer redaction
(#3751) and
GitCommandError.command(#3689).