`sed` works one line at a time, so the allowlist never removed newlines inside `NVM_AUTH_HEADER`. A `Basic` credential built with GNU `base64`, which wraps its output at 76 columns, therefore reached the downloader split across several lines, and authentication failed (wget rejects such a header outright). Filter with `tr -cd` instead, using the same set of allowed characters, so CR and LF are deleted like any other disallowed character and a wrapped credential is joined back into one line Discovered by Anthropic's OSS Scanner, as bug ANT-2026-W1E9TAK3; the scanner correctly classified it as not a vulnerability.
62 lines
3.1 KiB
Bash
Executable File
62 lines
3.1 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
die () { echo "$@" ; exit 1; }
|
|
|
|
\. ../../../nvm.sh
|
|
|
|
set -ex
|
|
|
|
# Test 1: all standard base64 characters (RFC 4648) are preserved
|
|
STANDARD_B64="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
|
|
RESULT=$(nvm_sanitize_auth_header "${STANDARD_B64}")
|
|
[ "${RESULT}" = "${STANDARD_B64}" ] || die "FAIL: standard base64 chars were stripped. Got: '${RESULT}'"
|
|
|
|
# Test 2: all base64url characters (RFC 4648 §5) are preserved
|
|
B64URL="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_="
|
|
RESULT=$(nvm_sanitize_auth_header "${B64URL}")
|
|
[ "${RESULT}" = "${B64URL}" ] || die "FAIL: base64url chars were stripped. Got: '${RESULT}'"
|
|
|
|
# Test 3: a real JWT Bearer token (base64url-encoded header.payload.signature) is preserved
|
|
JWT_TOKEN="Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
|
|
RESULT=$(nvm_sanitize_auth_header "${JWT_TOKEN}")
|
|
[ "${RESULT}" = "${JWT_TOKEN}" ] || die "FAIL: JWT Bearer token chars were stripped. Got: '${RESULT}'"
|
|
|
|
# Test 4: Basic auth (base64-encoded user:pass) is preserved
|
|
BASIC_TOKEN="Basic dXNlcm5hbWU6cGFzc3dvcmQ="
|
|
RESULT=$(nvm_sanitize_auth_header "${BASIC_TOKEN}")
|
|
[ "${RESULT}" = "${BASIC_TOKEN}" ] || die "FAIL: Basic auth base64 token chars were stripped. Got: '${RESULT}'"
|
|
|
|
# Test 5: dangerous shell metacharacters are removed
|
|
DANGEROUS="Bearer token;\`evil\`-cmd \$(inject)"
|
|
RESULT=$(nvm_sanitize_auth_header "${DANGEROUS}")
|
|
case "${RESULT}" in
|
|
*";"*|*"\`"*|*'$'*|*"("*|*")"*)
|
|
die "FAIL: dangerous shell metacharacters survived sanitization. Got: '${RESULT}'"
|
|
;;
|
|
esac
|
|
|
|
# Test 6: every character of RFC 7235 §2.1 `token68` is preserved.
|
|
# token68 = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="
|
|
# This is a superset of both base64 and base64url; note the '~', which no
|
|
# base64 variant emits but which an opaque token may legitimately contain.
|
|
TOKEN68="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~+/="
|
|
RESULT=$(nvm_sanitize_auth_header "${TOKEN68}")
|
|
[ "${RESULT}" = "${TOKEN68}" ] || die "FAIL: token68 chars were stripped. Got: '${RESULT}'"
|
|
|
|
# Test 7: the RFC 6750 §2.1 example Bearer token, extended with the remaining
|
|
# valid `b64token` characters, survives intact.
|
|
B64TOKEN="Bearer mF_9.B5f-4.1JqM~+/="
|
|
RESULT=$(nvm_sanitize_auth_header "${B64TOKEN}")
|
|
[ "${RESULT}" = "${B64TOKEN}" ] || die "FAIL: b64token chars were stripped. Got: '${RESULT}'"
|
|
|
|
# Test 8: a `Basic` credential that `base64` wrapped at 76 columns is rejoined into one line
|
|
WRAPPED="Basic $(printf 'YS1yYXRoZXItbG9uZy11c2VybmFtZUBleGFtcGxlLmNvbTphbi1ldmVuLWxvbmdlci1wYXNzd29y\nZCE=')"
|
|
RESULT=$(nvm_sanitize_auth_header "${WRAPPED}")
|
|
[ "${RESULT}" = "Basic YS1yYXRoZXItbG9uZy11c2VybmFtZUBleGFtcGxlLmNvbTphbi1ldmVuLWxvbmdlci1wYXNzd29yZCE=" ] || die "FAIL: wrapped base64 credential was not rejoined. Got: '${RESULT}'"
|
|
|
|
# Test 9: CR, LF, and blank lines are all removed
|
|
RESULT=$(nvm_sanitize_auth_header "$(printf 'Bearer a\r\nb\n\nc')")
|
|
[ "${RESULT}" = "Bearer abc" ] || die "FAIL: line breaks survived sanitization. Got: '${RESULT}'"
|
|
|
|
echo "All nvm_sanitize_auth_header tests passed"
|