-
Notifications
You must be signed in to change notification settings - Fork 48
fix: prevent credential_process hang on Windows in stdio transport mode #293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,9 +18,11 @@ | |
| import httpx | ||
| import json | ||
| import logging | ||
| import subprocess | ||
Check noticeCode scanning / Bandit Consider possible security implications associated with the subprocess module. Note
Consider possible security implications associated with the subprocess module.
|
||
| 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__ | ||
|
|
@@ -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( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would the patch be simpler if we just override |
||
| 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'}) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
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