Skip to content

archive: do not leak URL credentials in user visible messages - #706

Open
mahaase wants to merge 3 commits into
BobBuildTool:masterfrom
mahaase:fix/archive-url-credentials-leak
Open

archive: do not leak URL credentials in user visible messages#706
mahaase wants to merge 3 commits into
BobBuildTool:masterfrom
mahaase:fix/archive-url-credentials-leak

Conversation

@mahaase

@mahaase mahaase commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Problem

The HTTP basic authentication credentials of the http archive backend are
part of the URL, as documented. All three places that turn that URL back into
a string for display use the raw netloc, which still carries the
user:password@ part:

leaks via visible
getArchiveName() (archive.py:877) _namedErrorString() (archive.py:401) always, no flag needed
_remoteName() (archive.py:902) details of the DOWNLOAD/UPLOAD/MAP-SRC/CACHE-BID/CACHE-FPR/MAP-FPRNT status lines with -v
getArchiveUri() (archive.py:966) bob archive (cmds/archive.py:32) always

The first one is the bad one. _namedErrorString() is used for every error
message, including the perfectly ordinary "artifact not found" that happens for
each package on a cache miss. So a plain bob build on a cold cache prints the
password once per package:

>> aarch64::linux/devel::sandbox/core::coreutils
   DOWNLOAD  work/core/coreutils/.../workspace .. https://user:s3cr3t@server.test/artifacts: artifact not found

These messages routinely end up in build logs, CI consoles and pasted-into-an-issue
snippets.

The optional name archive setting is not a workaround: _remoteName() and
getArchiveUri() never consult it.

Note this is a display-only issue. WebDav._getURL() already strips the
credentials before the URL goes on the wire; they are sent in the
Authorization header (webdav.py:47-55).

Fix

Factor the stripping that WebDav._getURL() already does into a
stripUserInfo() helper and use it in the three places above as well.

It takes and returns a parsed URL and preserves the result type, so both the
urlsplit() result used in webdav.py and the urlparse() result used in
archive.py work.

Drive-by: cut at the last @, not the first

The existing implementation used netloc.split('@')[1]. urlparse() delimits
the user info at the last @, so the two disagree when the password
contains an unencoded @:

>>> urlparse('https://u:p@ss@host:8443/x').netloc
'u:p@ss@host:8443'
>>> _.split('@')[1]        # before: bogus host, and part of the password survives
'ss'
>>> _.rsplit('@', 1)[1]    # after
'host:8443'

Percent encoding is documented as required, so this was latent rather than a
live bug, but there is no reason to get it wrong.

Tests

  • TestHttpBasicAuthArchive.testNoCredentialsInMessages — asserts all three
    methods drop the password and the @, and still contain the host.
  • TestStripUserInfo — no credentials (identity), removal, @ in the
    password, IPv6 literal, urlsplit() type preservation.

Reverting archive.py alone makes the first test fail on all three subtests;
test_archive + test_webdav are green with the fix (71 tests).

Docs

One sentence in the basic auth section of configuration.rst stating that the
credentials are stripped from displayed URLs.

The HTTP basic authentication credentials are part of the archive URL. All
three places that turn that URL back into a string for display used the raw
netloc, which still carries the "user:password@" part:

  * getArchiveName() feeds _namedErrorString(), so the credentials were
    printed on *every* error message -- including the perfectly ordinary
    "artifact not found" that occurs for each package on a cache miss. No
    verbosity flag needed.
  * _remoteName() is the "details" of the DOWNLOAD/UPLOAD/MAP-SRC/CACHE-BID/
    CACHE-FPR/MAP-FPRNT status lines, shown with -v.
  * getArchiveUri() is printed by "bob archive".

These messages routinely end up in build logs and CI consoles.

WebDav._getURL() already stripped the credentials before putting the URL on
the wire, so this only ever affected the display strings. Factor that logic
out into stripUserInfo() and use it in the three spots above as well.

While at it, cut at the last '@' instead of the first. urlparse() delimits
the user info at the last '@', so for a password containing an unencoded '@'
the previous split('@')[1] produced a bogus host -- and revealed part of the
password on top.

The optional 'name' archive setting was no workaround: _remoteName() and
getArchiveUri() do not consult it.
The http backend takes the basic authentication credentials as part of the
URL. State that they are not echoed back in the status lines, so it is clear
that putting them there does not put them into every build log.
testNoCredentialsInMessages checks the three user visible strings derived
from the archive URL: the password and the '@' delimiter must be gone while
the host must survive. Reverting archive.py alone makes all three subtests
fail.

TestStripUserInfo covers the helper itself: URL without credentials (returned
unchanged), plain removal, a password containing an unencoded '@', an IPv6
literal host, and preservation of the urlsplit() result type.
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.25%. Comparing base (7b00892) to head (7bc796d).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #706      +/-   ##
==========================================
+ Coverage   89.23%   89.25%   +0.01%     
==========================================
  Files          50       50              
  Lines       16450    16459       +9     
==========================================
+ Hits        14679    14690      +11     
+ Misses       1771     1769       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mahaase mahaase changed the title archive: do not leak URL credentials in user visible messages WIP archive: do not leak URL credentials in user visible messages Aug 19, 2026
@mahaase mahaase changed the title WIP archive: do not leak URL credentials in user visible messages archive: do not leak URL credentials in user visible messages Aug 19, 2026
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.

1 participant