Re-cut of the two security items in #1378. Both original PRs (#1118, #1156) share no ancestry with main after the secret-purge force-push, so neither could be rebased or cherry-picked — the work had to be reimplemented against current main.
Neither fix is present on main today.
1. Proxy credentials leak from logged errors
redact_proxy_credentials bails out unless the configured WEBSHARE_PROXY_URL appears in the text byte-for-byte:
if not url or url not in text:
return text
It is called from exception handlers that log subprocess and HTTP failures, so anything it skips is written to logs verbatim. Reproduced against current main with WEBSHARE_PROXY_URL=http://user:s3cr3t@proxy.internal:8080:
| Text |
Result |
connect to http://user:s3cr3t@PROXY.INTERNAL:8080 (host case-normalised by urllib3) |
leaked |
http://user:s3cr3t%40x@proxy.internal:8080 (percent-encoded, echoed by yt-dlp) |
leaked |
HTTPS_PROXY=http://bob:hunter2@corp.proxy:3128 (different variable entirely) |
leaked |
CalledProcessError: ['yt-dlp','--proxy','http://u:p4ss@h:1'] |
leaked |
http://user:pa@ss@proxy.internal:8080 (raw @ in password) |
leaked |
The last case is #1113: RFC 3986 requires @ in userinfo to be percent-encoded, but real proxy values carry a raw one, and matching to the first @ leaves the password tail behind.
The helper also had no test coverage at all.
Fix
A second, generic scheme://user[:password]@ pass that redacts credentials regardless of which variable they came from or how they were rendered. The exact-match pass runs first and is kept, because it preserves the host so operators can still tell which proxy was in play. The helper now accepts non-str input and never raises — it runs inside except blocks where raising would mask the original error.
Applied to both copies: the canonical src/youtube_extension/utils/proxy.py and the drifted shared/libs/youtube_proxy.py, whose three logger calls in the retry handler are the actual leak site.
2. ecdsa advisory with no patched release
python-jose[cryptography] was declared in pyproject.toml and requirements.txt but is never imported. The only occurrence is inside a string template in backend/code_generator.py — text written into projects the generator emits, which get their own requirements.txt pinning it (line 468). Parsing the tree confirms zero jose imports at AST level in src/, shared/, or scripts/.
Declaring it pulled in ecdsa, whose GHSA-wj6h-64fc-37mp has no patched version — so the advisory could not be resolved by upgrading, only by removing the path to it. python-jose is ecdsa's sole dependent.
Fix
Drop the declaration. uv lock removes ecdsa, python-jose, and rsa (rsa's only dependent was also python-jose; google-auth in this resolution needs cryptography and pyasn1-modules). Generated projects are unaffected.
Acceptance criteria
Not included
passlib is the same template-only phantom as python-jose and could be dropped on identical reasoning, but it carries no advisory. Flagged in both manifests rather than removed unilaterally.
Re-cut of the two security items in #1378. Both original PRs (#1118, #1156) share no ancestry with
mainafter the secret-purge force-push, so neither could be rebased or cherry-picked — the work had to be reimplemented against currentmain.Neither fix is present on
maintoday.1. Proxy credentials leak from logged errors
redact_proxy_credentialsbails out unless the configuredWEBSHARE_PROXY_URLappears in the text byte-for-byte:It is called from exception handlers that log subprocess and HTTP failures, so anything it skips is written to logs verbatim. Reproduced against current
mainwithWEBSHARE_PROXY_URL=http://user:s3cr3t@proxy.internal:8080:connect to http://user:s3cr3t@PROXY.INTERNAL:8080(host case-normalised by urllib3)http://user:s3cr3t%40x@proxy.internal:8080(percent-encoded, echoed by yt-dlp)HTTPS_PROXY=http://bob:hunter2@corp.proxy:3128(different variable entirely)CalledProcessError: ['yt-dlp','--proxy','http://u:p4ss@h:1']http://user:pa@ss@proxy.internal:8080(raw@in password)The last case is #1113: RFC 3986 requires
@in userinfo to be percent-encoded, but real proxy values carry a raw one, and matching to the first@leaves the password tail behind.The helper also had no test coverage at all.
Fix
A second, generic
scheme://user[:password]@pass that redacts credentials regardless of which variable they came from or how they were rendered. The exact-match pass runs first and is kept, because it preserves the host so operators can still tell which proxy was in play. The helper now accepts non-strinput and never raises — it runs insideexceptblocks where raising would mask the original error.Applied to both copies: the canonical
src/youtube_extension/utils/proxy.pyand the driftedshared/libs/youtube_proxy.py, whose three logger calls in the retry handler are the actual leak site.2.
ecdsaadvisory with no patched releasepython-jose[cryptography]was declared inpyproject.tomlandrequirements.txtbut is never imported. The only occurrence is inside a string template inbackend/code_generator.py— text written into projects the generator emits, which get their ownrequirements.txtpinning it (line 468). Parsing the tree confirms zerojoseimports at AST level insrc/,shared/, orscripts/.Declaring it pulled in
ecdsa, whose GHSA-wj6h-64fc-37mp has no patched version — so the advisory could not be resolved by upgrading, only by removing the path to it.python-joseisecdsa's sole dependent.Fix
Drop the declaration.
uv lockremovesecdsa,python-jose, andrsa(rsa's only dependent was alsopython-jose;google-authin this resolution needscryptographyandpyasn1-modules). Generated projects are unaffected.Acceptance criteria
@in a path, query, or fragment, and a barehost:port, all pass through untouchedecdsaabsent fromuv.lockcode_generatorstill import; full unit suite greenNot included
passlibis the same template-only phantom aspython-joseand could be dropped on identical reasoning, but it carries no advisory. Flagged in both manifests rather than removed unilaterally.