Skip to content

CodeArtifact login: auth token leaked unredacted in CommandFailedError via subprocess stderr #10575

Description

@Adityaj0

Summary

awscli/customizations/codeartifact/login.py's CommandFailedError is meant to scrub the CodeArtifact authorization token out of error messages before they are shown to the user / logged, but it only redacts the token from str(called_process_error) — the decoded stderr from the failed subprocess is appended unredacted.

class CommandFailedError(Exception):
    def __init__(self, called_process_error, auth_token):
        msg = str(called_process_error).replace(auth_token, '******')
        if called_process_error.stderr is not None:
            msg += (
                f' Stderr from command:\n'
                f'{called_process_error.stderr.decode(get_stderr_encoding())}'
            )
        Exception.__init__(self, msg)

str(subprocess.CalledProcessError(...)) only ever includes the cmd list and return code — it never includes stderr content (verified directly against CPython's subprocess module). So the .replace(auth_token, '******') call can only ever redact the token if it appears inside the invoked cmd list; it can never redact anything appearing in stderr, which is concatenated onto the message afterward with no scrubbing at all.

Why this is exploitable

For NuGetLogin/DotNetLogin (login.py), the auth token is passed as a literal CLI argument to the underlying tool:

# NuGetLogin._get_configure_command
return [
    'nuget', 'sources', operation,
    '-name', source_name,
    '-source', nuget_index_url,
    '-username', 'aws',
    '-password', self.auth_token
]
# DotNetLogin._get_configure_command
command += [
    '--username', 'aws',
    '--password', self.auth_token
]

Both nuget.exe and dotnet nuget are known to echo the full failing command line (including all passed arguments) into stderr/output when a sources add/nuget add source operation fails — e.g. an invalid/duplicate source name, a malformed URL, or a permissions error. PipLogin similarly embeds the token directly in a URL (https://aws:{auth_token}@{netloc}...) passed to pip config, and pip/curl-style tools commonly print the failing URL (credentials included) on error.

Impact

Running aws codeartifact login --tool nuget|dotnet|pip (etc.) in a scenario where the underlying tool invocation fails causes CommandFailedError to be raised with the live, unexpired CodeArtifact repository authorization token embedded in its message — printed to the user's terminal and captured by anything logging CLI output (CI logs, terminal scrollback capture, support tickets/bug reports). This defeats the explicit intent of the .replace(auth_token, '******') redaction in the same function.

Suggested fix

Redact auth_token from the decoded stderr (and stdout, if present) before appending it to the message, not just from str(called_process_error):

class CommandFailedError(Exception):
    def __init__(self, called_process_error, auth_token):
        msg = str(called_process_error).replace(auth_token, '******')
        if called_process_error.stderr is not None:
            stderr = called_process_error.stderr.decode(get_stderr_encoding())
            stderr = stderr.replace(auth_token, '******')
            msg += f' Stderr from command:\n{stderr}'
        Exception.__init__(self, msg)

Environment

Reviewed against the current aws-cli source (vendored botocore confirmed unmodified/upstream; this bug is in aws-cli-specific customization code, not botocore).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions