Skip to content

Comments

fix: redact credentials from URLs in logs, exceptions, and persisted state#2196

Draft
jpshackelford wants to merge 1 commit intomainfrom
openhands/fix-credential-exposure-in-logs
Draft

fix: redact credentials from URLs in logs, exceptions, and persisted state#2196
jpshackelford wants to merge 1 commit intomainfrom
openhands/fix-credential-exposure-in-logs

Conversation

@jpshackelford
Copy link
Contributor

@jpshackelford jpshackelford commented Feb 24, 2026

Summary

Fixes #2152

This PR addresses a security issue where credentials embedded in plugin source URLs (e.g., https://oauth2:SECRET_TOKEN@gitlab.com/org/private-repo) were being exposed in:

  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 ResolvedPluginSource

Changes

  • New utility function: redact_url_credentials(url) in git/utils.py that replaces credentials in URLs with **** (e.g., https://****@gitlab.com/org/repo)
  • Updated run_git_command(): Redacts credentials from error logs, debug logs, and GitCommandError exception messages/command fields
  • Updated cached_repo.py: Redacts credentials in clone log messages
  • Updated plugin/fetch.py: Redacts credentials in PluginFetchError exceptions and warning logs
  • Updated plugin/types.py: ResolvedPluginSource.from_plugin_source() now redacts credentials before persistence (credentials are only needed at fetch time)
  • Public API export: redact_url_credentials is exported from openhands.sdk for cross-repo usage (related to Security: Credentials in plugin source URLs exposed via API responses OpenHands#12959)

Example

Before:

INFO - Cloning repository from https://oauth2:SECRET_TOKEN@gitlab.com/org/repo.git

After:

INFO - Cloning repository from https://****@gitlab.com/org/repo.git

Checklist

  • If the PR is changing/adding functionality, are there tests to reflect this?
  • If there is an example, have you run the example to make sure that it works?
  • If there are instructions on how to run the code, have you followed the instructions and made sure that it works?
  • If the feature is significant enough to require documentation, is there a PR open on the OpenHands/docs repository with the same branch name?
  • Is the github CI passing?

@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:f4d42de-python

Run

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

All tags pushed for this build

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

About Multi-Architecture Support

  • Each variant tag (e.g., f4d42de-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., f4d42de-python-amd64) are also available if needed

…state

This addresses security issue #2152 by preventing credential leakage:

- Add redact_url_credentials() utility function to sanitize URLs with
  embedded credentials (e.g., https://oauth2:token@host/path becomes
  https://****@host/path)
- Update git/utils.py:run_git_command() to redact credentials from:
  - Error log messages
  - Debug log messages
  - GitCommandError exception messages and command fields
- Update git/cached_repo.py to redact credentials in clone log messages
- Update plugin/fetch.py to redact credentials in:
  - PluginFetchError exception messages
  - Warning log messages
- Update plugin/types.py:ResolvedPluginSource.from_plugin_source() to
  redact credentials before persistence (credentials only needed at
  fetch time)
- Export redact_url_credentials from openhands.sdk public API for
  cross-repo usage (related: OpenHands/OpenHands#12959)
- Add comprehensive tests for credential redaction

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

API breakage checks (Griffe)

Result: Passed

Action log

@github-actions
Copy link
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-sdk/openhands/sdk
   __init__.py21290%69–70
openhands-sdk/openhands/sdk/git
   cached_repo.py129496%391–392, 427–428
   utils.py1262679%125–127, 152–154, 188–189, 196–201, 206–207, 217–222, 232–234, 262
openhands-sdk/openhands/sdk/plugin
   fetch.py79988%156–157, 159, 167–168, 172, 338–339, 343
   types.py235697%65, 68, 264, 353, 769, 777
TOTAL18814569069% 

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.

Security: Credentials in plugin source URLs exposed in logs, exceptions, and persisted state

2 participants