CodeArtifact login: auth token leaked unredacted in CommandFailedError via subprocess stderr
- Dominant language
- Python
- Stars
- 17.3k
- Forks
- 4.6k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 21
Description
## 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**.
```python
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:
```python
# NuGetLogin._get_configure_command
return [
'nuget', 'sources', operation,
'-name', source_name,
'-source', nuget_index_url,
'-username', 'aws',
'-password', self.auth_token
]
```
```python
# 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)`:
```python
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).
Contributor guide
Research direction
Start in awscli/customizations/codeartifact/login.py at CommandFailedError and inspect the NuGetLogin, DotNetLogin, and PipLogin failure paths. Verify how CalledProcessError stderr and any stdout are decoded and appended. Done means the auth token is redacted from the complete error message before it can be displayed or logged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, python
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 85/100