T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/setup-wizard.py:14
- Finding
- Credentials Exposed Through Echoing Interactive Prompts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup-wizard.py`, lines 14-16, with credential-prompt invocations at lines 46 and 59 **Vulnerability Type**: Sensitive credential exposure through terminal output **Risk Level**: Medium ### Vulnerable Code ```python def _prompt(label: str, default: str = "") -> str: suffix = f" [{default}]" if default else "" value = input(f"{label}{suffix}: ").strip() return value or default ``` The vulnerable helper is used directly for both supported credential types: ```python token = _prompt("PIPEDRIVE_API_TOKEN", os.environ.get("PIPEDRIVE_API_TOKEN", "")) ``` ```python access_token = _prompt("PIPEDRIVE_ACCESS_TOKEN", os.environ.get("PIPEDRIVE_ACCESS_TOKEN", "")) ``` ### Technical Analysis The `_prompt` function uses the ordinary `input()` function for secrets. This causes newly entered API tokens and OAuth access tokens to remain visible while the user types them. More critically, when a credential is already present in the environment, it is passed as `default` and interpolated into the prompt through: ```python suffix = f" [{default}]" if default else "" ``` Consequently, the complete credential is printed to the terminal before the user enters anything. This behavior conflicts with the rule in `SKILL.md` stating that raw tokens must never be printed or echoed. Although the setup wizard legitimately needs a token to validate the Pipedrive connection, displaying that token is unnecessary and exceeds minimum credential-handling requirements. ### Attack Path 1. A valid credential is stored in `PIPEDRIVE_API_TOKEN` or `PIPEDRIVE_ACCESS_TOKEN`. 2. The user or an automation environment runs `scripts/setup-wizard.py` as documented. 3. The corresponding environment value is passed to `_prompt` as its default. 4. `_prompt` embeds the complete token in the visible terminal prompt. 5. The credential is captured through terminal scrollback, session recording, CI logs, screenshots, screen sharing ...[truncated 900 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Replace `input()` with `getpass.getpass()` whenever a credential must be entered: ```python import getpass def _prompt_secret(label: str, existing: str = "") -> str: if existing: reuse = input(f"{label} is already configured. Reuse it? [Y/n]: ").strip().lower() if reuse in {"", "y", "yes"}: return existing return getpass.getpass(f"{label}: ").strip() ``` 2. Never display an existing credential as a prompt default. Indicate only whether it is configured. 3. Avoid logging, printing, or returning raw credentials in success and error messages. 4. Where practical, avoid interactive token entry entirely and require secrets to be supplied through a protected secret manager or environment variable. 5. Add tests that populate the credential environment variables and verify that captured stdout and stderr do not contain their values. 6. Document terminal-recording and process-environment risks for users operating in shared or automated environments. ]]>
