Skip to content

Comments

fix: redact credentials in plugin source URLs#2154

Draft
jpshackelford wants to merge 1 commit intomainfrom
fix/redact-plugin-url-credentials
Draft

fix: redact credentials in plugin source URLs#2154
jpshackelford wants to merge 1 commit intomainfrom
fix/redact-plugin-url-credentials

Conversation

@jpshackelford
Copy link
Contributor

@jpshackelford jpshackelford commented Feb 20, 2026

Summary

Addresses #2152 - Credentials in plugin source URLs were being exposed in logs, exception messages, and persisted state.

Problem

When fetching plugins from private repositories using authenticated URLs (e.g., https://oauth2:<token>@gitlab.com/org/private-marketplace), credentials were exposed in multiple places:

  1. Application logs - Clone URLs and git commands were logged with credentials visible
  2. Exception messages - Error messages included full URLs with credentials
  3. Persisted conversation state - Plugin source URLs with credentials were stored in StoredConversation and ResolvedPluginSource

Solution

Added redact_url_credentials() utility function and applied it to all places where URLs might be logged, included in errors, or persisted:

Changes

  1. git/utils.py: Added redact_url_credentials() function that replaces credentials in HTTPS URLs with ****

    • Handles https://user:pass@host, https://oauth2:token@host, https://x-token-auth:token@host
    • Preserves SSH URLs (no embedded credentials) and local paths unchanged
  2. git/utils.py: Added _redact_args_for_logging() helper and updated run_git_command() to redact credentials from git command logging and error messages

  3. git/cached_repo.py: Updated clone log message to use redacted URL

  4. plugin/fetch.py: Updated error messages and warnings to use redacted URLs

  5. plugin/types.py: Updated ResolvedPluginSource.from_plugin_source() to automatically redact credentials from the source URL before persistence

Example

# Before: credentials exposed in logs and persisted state
INFO - Cloning repository from https://oauth2:SECRET_TOKEN@gitlab.com/repo.git
# Error messages: "Failed to fetch plugin from https://oauth2:SECRET_TOKEN@gitlab.com/repo.git"
# Persisted: {"source": "https://oauth2:SECRET_TOKEN@gitlab.com/repo.git", ...}

# After: credentials redacted
INFO - Cloning repository from https://****@gitlab.com/repo.git
# Error messages: "Failed to fetch plugin from https://****@gitlab.com/repo.git"
# Persisted: {"source": "https://****@gitlab.com/repo.git", ...}

Testing

  • Added comprehensive unit tests in tests/sdk/git/test_url_redaction.py covering:

    • Various credential formats (oauth2, x-token-auth, username:password, token-only)
    • URLs without credentials (unchanged)
    • SSH URLs (unchanged)
    • Local paths (unchanged)
    • ResolvedPluginSource credential redaction on persistence
    • JSON serialization doesn't expose credentials
  • All existing tests pass (45 git tests, 213 plugin tests)

Security Notes

  • The redaction is safe because plugins are fetched and cached before creating ResolvedPluginSource for persistence
  • The resolved_ref (commit SHA) uniquely identifies the exact plugin version, allowing resume operations to use the cached copy
  • This aligns with security best practices by preventing credential leakage to log aggregators, error responses, and database backups

@jpshackelford can click here to continue refining the PR


Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.12-nodejs22 Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:2aecf2a-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-2aecf2a-python \
  ghcr.io/openhands/agent-server:2aecf2a-python

All tags pushed for this build

ghcr.io/openhands/agent-server:2aecf2a-golang-amd64
ghcr.io/openhands/agent-server:2aecf2a-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:2aecf2a-golang-arm64
ghcr.io/openhands/agent-server:2aecf2a-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:2aecf2a-java-amd64
ghcr.io/openhands/agent-server:2aecf2a-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:2aecf2a-java-arm64
ghcr.io/openhands/agent-server:2aecf2a-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:2aecf2a-python-amd64
ghcr.io/openhands/agent-server:2aecf2a-nikolaik_s_python-nodejs_tag_python3.12-nodejs22-amd64
ghcr.io/openhands/agent-server:2aecf2a-python-arm64
ghcr.io/openhands/agent-server:2aecf2a-nikolaik_s_python-nodejs_tag_python3.12-nodejs22-arm64
ghcr.io/openhands/agent-server:2aecf2a-golang
ghcr.io/openhands/agent-server:2aecf2a-java
ghcr.io/openhands/agent-server:2aecf2a-python

About Multi-Architecture Support

  • Each variant tag (e.g., 2aecf2a-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 2aecf2a-python-amd64) are also available if needed

Addresses issue #2152 - Credentials in plugin source URLs were being
exposed in logs, exception messages, and persisted state.

Changes:
- Add redact_url_credentials() function in git/utils.py
- Update git command logging to redact credentials from URLs
- Update plugin fetch error messages to use redacted URLs
- Update ResolvedPluginSource.from_plugin_source() to automatically
  redact credentials before persistence
- Add comprehensive tests for URL redaction

This ensures that sensitive credentials (tokens, passwords) in
authenticated git URLs (e.g., https://oauth2:TOKEN@gitlab.com/repo)
are replaced with '****' in:
- Application logs
- Exception messages
- Persisted conversation state (StoredConversation, ResolvedPluginSource)

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions
Copy link
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk/git
   cached_repo.py129496%391–392, 427–428
   utils.py1272679%132–134, 159–161, 195–196, 203–208, 213–214, 224–229, 239–241, 269
openhands-sdk/openhands/sdk/plugin
   fetch.py821186%156–157, 159, 167, 169–170, 172, 338, 340–341, 343
   types.py235697%65, 68, 280, 369, 785, 793
TOTAL18329556469% 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants