Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion mcp_proxy_for_aws/sigv4_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
import httpx
import json
import logging
import subprocess

Check notice

Code scanning / Bandit

Consider possible security implications associated with the subprocess module. Note

Consider possible security implications associated with the subprocess module.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False positive. We dont pass user-controlled input to subprocess, and we use compat_shell_split() (no shell=True). Literally replicating what botocore itself already does, just adding stdin=subprocess.DEVNULL

https://github.com/boto/botocore/blob/a6352f4e23fd71461ca9d74d48307dda8d859d31/botocore/credentials.py#L1101

from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
from botocore.credentials import Credentials
from botocore.compat import compat_shell_split
from botocore.credentials import Credentials, ProcessProvider
from functools import partial
from httpx import __version__ as httpx_version
from mcp_proxy_for_aws import __version__
Expand All @@ -30,6 +32,58 @@

logger = logging.getLogger(__name__)


def _patch_credential_process_stdin():
"""Patch botocore ProcessProvider to pass stdin=subprocess.DEVNULL.

When mcp-proxy-for-aws runs in stdio transport mode, its stdin is the MCP
JSON-RPC pipe. Botocore's ProcessProvider spawns credential_process
subprocesses without specifying stdin, so the child inherits the MCP pipe.
On Windows this causes the subprocess to hang indefinitely because it holds
an open handle to the pipe, blocking Popen.communicate() from completing.
"""
from botocore.exceptions import CredentialRetrievalError

def _retrieve_credentials_using(self, credential_process):
process_list = compat_shell_split(credential_process)
p = self._popen(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the patch be simpler if we just override self._popen with a patched popen that has stdin=subprocess.DEVNULL by default?

process_list,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.DEVNULL,
)
stdout, stderr = p.communicate()
if p.returncode != 0:
raise CredentialRetrievalError(provider=self.METHOD, error_msg=stderr.decode('utf-8'))
parsed = json.loads(stdout.decode('utf-8'))
version = parsed.get('Version', '<Version key not provided>')
if version != 1:
raise CredentialRetrievalError(
provider=self.METHOD,
error_msg=(
f"Unsupported version '{version}' for credential process "
f'provider, supported versions: 1'
),
)
try:
return {
'access_key': parsed['AccessKeyId'],
'secret_key': parsed['SecretAccessKey'],
'token': parsed.get('SessionToken'),
'expiry_time': parsed.get('Expiration'),
'account_id': self._get_account_id(parsed),
}
except KeyError as e:
raise CredentialRetrievalError(
provider=self.METHOD,
error_msg=f'Missing required key in response: {e}',
)

ProcessProvider._retrieve_credentials_using = _retrieve_credentials_using


_patch_credential_process_stdin()

# Headers that should be redacted when logging to prevent credential exposure
SENSITIVE_HEADERS = frozenset({'authorization', 'x-amz-security-token', 'x-amz-date'})

Expand Down
Loading
Loading