T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/x402.py:391
- Finding
- Wallet Private Key Exposed Through Command-Line Arguments, Plaintext Storage, and Terminal Output<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:160-164`; `scripts/x402.py:89-103`; `scripts/x402.py:329-333`; `scripts/x402.py:391-408` **Vulnerability Type**: Plaintext sensitive-data exposure **Risk Level**: High ### Vulnerable Code The documentation directs users to pass a wallet private key as a command-line argument: ```bash # Set wallet private key (stored securely) neckr0ik-x402-payments config set wallet.private_key <key> # Or use environment variable export X402_PRIVATE_KEY=<key> ``` The configuration value is accepted as an ordinary command-line argument: ```python # config command config_parser = subparsers.add_parser('config', help='Configure x402') config_subparsers = config_parser.add_subparsers(dest='config_command') config_set = config_subparsers.add_parser('set', help='Set configuration') config_set.add_argument('key', help='Configuration key (e.g., wallet.address)') config_set.add_argument('value', help='Configuration value') ``` Configuration data is written as unencrypted JSON without explicitly establishing owner-only file permissions: ```python def _load_config(self) -> dict: """Load configuration.""" if self.config_file.exists(): return json.loads(self.config_file.read_text()) return {"chain": "base", "token": "usdc"} def _save_config(self, config: dict): """Save configuration.""" self.config_file.write_text(json.dumps(config, indent=2)) ``` The supplied value is stored and then printed in full: ```python elif args.command == 'config': if args.config_command == 'set': config = client._load_config() # Parse key path keys = args.key.split('.') # Set nested value current = config for key in keys[:-1]: if key not in current: current[key] = {} current = current[key] current[keys[-1]] = args.value client._save_config(config) print(f"✓ Set {args.key} = {args.value}") ``` # ...[truncated 2968 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Do not accept private keys as ordinary command-line arguments** - Remove the documented `config set wallet.private_key <key>` workflow. - If interactive entry is unavoidable, use `getpass.getpass()` so the key is not echoed. - Warn users that shell arguments and shell history are unsuitable for secrets. 2. **Avoid storing raw private keys** - Prefer OS-backed secret stores such as Keychain, Credential Manager, Secret Service, or a dedicated hardware/software wallet. - Prefer an external signer API so this Skill never receives the private key. - If persistence is essential, encrypt the key using a user-provided secret or platform-protected key. 3. **Enforce restrictive filesystem permissions** - Create `~/.x402` with mode `0700`. - Create secret-bearing files atomically with mode `0600`. - Verify and reject unsafe ownership, symlinks, and overly permissive existing files before reading or writing. - Separate non-sensitive configuration from wallet secrets. 4. **Never print secret values** - Replace: ```python print(f"✓ Set {args.key} = {args.value}") ``` with: ```python print(f"✓ Set {args.key}") ``` - Redact known secret fields in all diagnostics and exception paths. 5. **Align documentation and implementation** - Do not claim that private keys are “stored securely” until secure storage is implemented. - If `X402_PRIVATE_KEY` support is retained, implement it explicitly and document the risks of environment-variable exposure. - Since the audited payment implementation is simulated and does not use the key, remove private-key collection entirely until real local signing is safely implemented. 6. **Add regression tests** - Verify secrets never appear in stdout or stderr. - Verify generated secret files have owner-only permissions. - Verify secret values are not stored in ordinary configuration JSON. - Verify malformed paths or pre ...[truncated 55 chars]
