[BREAKING] Python: Refine SecretString handling - #8127
[BREAKING] Python: Refine SecretString handling#8127Eduard van Valkenburg (eavanvalkenburg) wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The hashable wrapper remains mutable, and public OpenAI constructor annotations still reject supported SecretString values.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Refines Python secret handling by making SecretString a masked value wrapper and explicitly unwrapping credentials at provider SDK boundaries.
Changes:
- Adds masked formatting, concatenation, equality, and validation semantics.
- Updates provider credential annotations and SDK integrations.
- Expands tests across supported providers.
File summaries
| File | Description |
|---|---|
python/packages/core/agent_framework/_settings.py |
Implements the new wrapper and coercion. |
python/packages/core/tests/core/test_settings.py |
Tests masking and value semantics. |
python/packages/core/AGENTS.md |
Documents settings conventions. |
python/packages/openai/tests/openai/test_openai_shared.py |
Tests SDK-boundary unwrapping. |
python/packages/mistral/tests/mistral/test_mistral_embedding_client.py |
Tests embedding credentials. |
python/packages/mistral/tests/mistral/test_mistral_chat_client.py |
Tests chat credentials. |
python/packages/gemini/agent_framework_gemini/_chat_client.py |
Accepts wrapped API keys. |
python/packages/gemini/tests/test_gemini_client.py |
Tests Gemini key resolution. |
python/packages/bedrock/agent_framework_bedrock/_chat_client.py |
Updates credential annotations. |
python/packages/bedrock/agent_framework_bedrock/_embedding_client.py |
Updates credential annotations. |
python/packages/bedrock/tests/test_bedrock_client.py |
Tests session credential unwrapping. |
python/packages/azure-cosmos/agent_framework_azure_cosmos/_history_provider.py |
Unwraps Cosmos credentials. |
python/packages/azure-cosmos/agent_framework_azure_cosmos/_checkpoint_storage.py |
Unwraps checkpoint credentials. |
python/packages/azure-cosmos/tests/test_cosmos_history_provider.py |
Tests history credentials. |
python/packages/azure-cosmos/tests/test_cosmos_checkpoint_storage.py |
Tests checkpoint credentials. |
python/packages/azure-ai-search/agent_framework_azure_ai_search/_context_provider.py |
Supports wrapped search keys. |
python/packages/azure-ai-search/tests/test_aisearch_context_provider.py |
Tests search key boundaries. |
python/packages/anthropic/agent_framework_anthropic/_chat_client.py |
Accepts wrapped API keys. |
python/packages/anthropic/agent_framework_anthropic/_foundry_client.py |
Accepts wrapped Foundry keys. |
python/packages/anthropic/agent_framework_anthropic/_bedrock_client.py |
Accepts wrapped AWS credentials. |
python/packages/anthropic/tests/test_anthropic_client.py |
Tests Anthropic key handling. |
python/packages/anthropic/tests/test_anthropic_provider_clients.py |
Tests provider credential boundaries. |
Review details
- Files reviewed: 22/22 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
MAF Automated Review — Iteration 1
Result: Findings reported
Scope: full PR (1 commit(s)): 4ceefd0b6ff1
Model: gpt-5.6-sol-fast
Overview
The PR replaces the str subclass with a masking value wrapper and adds explicit unwrapping at most provider SDK boundaries, backed by broad settings and provider tests. The masking, coercion, and direct SDK-boundary guards are strong, but the provider migration is incomplete: Foundry embeddings reject wrapped credentials at runtime, while the public OpenAI client signatures reject them during static checking despite runtime support.
Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
2 verified findings remained after source verification (2 medium) across 1 file. Details are attached to the affected lines below.
Affected areas: python/packages/core/agent_framework/_settings.py
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
4ceefd0 to
5abc6f0
Compare
| def __setattr__(self, name: str, value: object) -> None: | ||
| """Reject mutation after construction.""" | ||
| raise AttributeError("SecretString is immutable.") |
There was a problem hiding this comment.
Could the immutable wrapper implement the copy protocol? Both copy.copy(SecretString(...)) and copy.deepcopy(...) now raise this AttributeError, so State.set() and SessionStore.set() also crash when their otherwise arbitrary state contains a secret. Since the value is immutable, could __copy__ and __deepcopy__ return self?
|
|
||
| class SecretString(str): | ||
| """A string subclass that masks its value in repr() to prevent accidental exposure. | ||
| class SecretString: |
There was a problem hiding this comment.
Before dropping str inheritance, should we migrate the remaining credential boundaries? Mem0ContextProvider(api_key=SecretString(...)) now crashes when the SDK calls encode(), while AnthropicVertexClient(access_token=...) and CopilotStudioAgent(token=...) pass the wrapper through and format the mask into authorization headers. Could those annotations accept SecretString and unwrap with get_secret_value() at the SDK boundary, as the migrated providers do?
| assert type(client.anthropic_client.api_key) is str | ||
| assert client.anthropic_client.api_key == anthropic_unit_test_env["ANTHROPIC_API_KEY"] |
There was a problem hiding this comment.
Could we narrow client.anthropic_client before accessing api_key here? Its declared union includes AsyncAnthropicVertex, which has no api_key, so Zuban, mypy, and pyright all reject these new assertions and the PR's typing check currently fails.
| assert settings["api_key"] is secret | ||
| assert settings["api_key"].get_secret_value() == "my-secret" |
There was a problem hiding this comment.
Would it make sense to bind and narrow settings["api_key"] before calling get_secret_value()? SecretSettings declares this entry as optional, and the identity assertion does not narrow a second indexed access, so pyright reports reportOptionalMemberAccess and keeps the typing gate red.
Motivation & Context
Make settings-value behavior more consistent across Python formatting and provider integrations.
Description & Review Guide
SecretStringbehavior and align provider credential handling and type annotations.SecretStringis now a value wrapper rather than astrsubclass. Callers needing a plain string should useget_secret_value(). Plain-string settings inputs remain supported.Related Issue
N/A.
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.