Clean up extension credentials on uninstall - #1718
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a secret cleanup mechanism to ensure that secrets and their associated companion secrets (e.g., refresh tokens, scopes) are deleted when an extension is uninstalled, provided they are no longer referenced by other extensions. The implementation includes a SecretCleanupPlan to track dependencies and logic within the ExtensionManager to verify references across WASM tools, channels, and MCP servers before deletion. Comprehensive unit and E2E tests have been added to validate that unique secrets are removed and shared secrets are preserved until the final referencing extension is gone. I have no feedback to provide.
There was a problem hiding this comment.
Pull request overview
Adds uninstall-time secret cleanup for extensions (WASM tools, WASM channels, MCP servers) while preserving shared credentials until the last referencing extension is removed, and extends E2E coverage to validate the behavior against the libSQL secrets table.
Changes:
- Implement secret cleanup planning + best-effort deletion during
ExtensionManager::remove()for WASM tools/channels and MCP servers. - Add E2E scenarios and a dedicated isolated E2E server fixture to validate secret deletion/preservation flows.
- Document the new E2E scenario in the E2E test index.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/extensions/manager.rs |
Builds a per-extension secret cleanup plan, checks whether secrets are still referenced, and deletes unreferenced secrets on uninstall; adds unit tests for cleanup behavior. |
tests/e2e/conftest.py |
Adds a session-scoped isolated IronClaw instance fixture for uninstall-cleanup E2E scenarios. |
tests/e2e/scenarios/test_extension_uninstall_cleanup.py |
New E2E tests verifying uninstall secret cleanup for WASM tools/channels, shared Google OAuth secrets, and MCP servers. |
tests/e2e/CLAUDE.md |
Documents the new uninstall cleanup E2E scenario and fixture. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| if let Some(auth) = cap.auth { | ||
| plan.add_base_secret(&auth.secret_name); |
There was a problem hiding this comment.
In collect_secret_cleanup_plan for WasmTool, the loop over Self::tool_secret_names(&cap) already inserts auth.secret_name (see tool_secret_names). The subsequent plan.add_base_secret(&auth.secret_name) is redundant; consider removing it to keep the cleanup-plan logic single-sourced (while still adding the companion secrets).
| plan.add_base_secret(&auth.secret_name); |
zmanian
left a comment
There was a problem hiding this comment.
Review: Clean up extension credentials on uninstall
What was done well
- Correct ordering: The cleanup plan is collected BEFORE files are deleted, then the reference scan runs AFTER deletion, so the just-uninstalled extension's secrets correctly fall out of the "still referenced" set. This is the right approach.
- Shared secret safety: The
collect_referenced_secret_namesscan prevents premature deletion of secrets shared across extensions (e.g., Google OAuth token used by both gmail and google-drive). The testtest_remove_wasm_tool_keeps_shared_secrets_until_last_extensionexercises this directly. - Fail-safe on uncertainty: When capabilities files for other installed tools are missing or unreadable,
collect_referenced_secret_namesreturns an error, andcleanup_uninstalled_extension_secretsbails out entirely, keeping all secrets. This is the conservative choice -- tested bytest_remove_wasm_tool_keeps_secrets_when_other_tool_capabilities_missing. - Best-effort deletion:
delete_secret_best_effortlogs warnings rather than failing the uninstall if a secret deletion fails. Correct tradeoff -- the uninstall should succeed even if cleanup is partial. - No
.unwrap()or.expect()in production code. All instances are in tests. - Companion secrets covered: OAuth refresh tokens and scopes companions are properly tracked via
SecretCleanupPlan::companion_secrets, with dedicated helpersoauth_refresh_secret_nameandoauth_scopes_secret_name. - ChannelRelay: Correctly returns an empty
SecretCleanupPlansince relay secret cleanup (relay:<name>:oauth_state,relay:<name>:stream_token) is already handled inline in the staging branch'sChannelRelayremoval arm. - Thorough test coverage: 6 new unit tests plus 4 E2E scenarios covering WASM tool, WASM channel, shared Google OAuth, and MCP server cleanup flows.
Suggestions (nice to have)
-
SecretCleanupPlancompanion secrets for non-base secrets: Thecompanion_secretsHashMap is keyed by base secret name, and companions are only deleted when their base secret is being deleted (not referenced elsewhere). If a companion secret name somehow also appears as a base secret in a different extension, it would get double-checked -- not harmful, but worth noting. -
collect_referenced_secret_namesdoes not track companion secrets: The reference scan only collects base secret names (viatool_secret_names,channel_secret_names,mcp_server_secret_names). Companion secrets (refresh tokens, scopes) are not added to the referenced set. This means if Tool A and Tool B share an OAuth provider, and Tool A is removed, the companion secrets (refresh token, scopes) would only be protected by their base secret still being referenced. This is correct in practice since companions are only deleted when the base is not referenced, but adding companion secret names to the reference set would be a more explicit safety net. -
tracing::warn!structured fields: Incleanup_uninstalled_extension_secrets, theerrorfield is passed without%formatting (error,instead oferror = %error). This works becauseStringimplementsValue, but usingerror = %errorwould be consistent withdelete_secret_best_effortwhich useserror = %error.
Overall this is clean, well-tested, and handles the edge cases correctly. LGTM.
* Clean up extension credentials on uninstall * Address PR review feedback * Cover channel webhook secrets on uninstall * Harden tool secret cleanup detection
* Clean up extension credentials on uninstall * Address PR review feedback * Cover channel webhook secrets on uninstall * Harden tool secret cleanup detection
Summary
Testing