T09 · Insecure Skill Coding Practices
Error
- Location
- service/config.py:22
- Finding
- Plaintext administrative and OAuth credentials are stored and exposed without restrictive file permissions<![CDATA[ ## Vulnerability Details **File Location**: `service/config.py:22-33`, `scripts/auth_file_lib.py:35-39`, `scripts/install_web_app.py:118-150` **Vulnerability Type**: Plaintext secret storage and disclosure **Risk Level**: High ### Technical Analysis The generated web-administration password is stored directly in `install-info.json`, while OAuth profiles are written to JSON files through a generic write operation. Neither operation explicitly creates the files with owner-only permissions. ```python def save_install_info(data: JsonDict) -> None: ensure_skill_dirs() INSTALL_INFO_PATH.write_text( json.dumps(data, ensure_ascii=False, indent=2) + '\n', encoding='utf-8', ) ``` ```python def save_json_atomic(path: Path, data: JsonDict) -> None: tmp_path = path.with_suffix(path.suffix + '.tmp') tmp_path.write_text( json.dumps(data, ensure_ascii=False, indent=2) + '\n', encoding='utf-8', ) tmp_path.replace(path) ``` The installation result contains and prints the plaintext password: ```python install_info = { 'ok': True, 'host': args.host, 'port': port, 'username': creds['username'], 'password': creds['password'], # ... } save_install_info(install_info) # ... print(f"Username: {creds['username']}") print(f"Password: {creds['password']}") ``` The resulting permissions depend entirely on the caller's umask. Under a common `0022` umask, newly created files can be readable by other local users. The same issue affects temporary OAuth profile files and other JSON state files created elsewhere through unrestricted `write_text()` calls. Printing the password is useful for first-run access, but it also places the secret in terminal capture, installation logs, automation output, and agent tool output. Persistent plaintext storage is needed only if Basic Authentication remains the design; broad file readability is not necessary for the declared functionality. ### Attack Path 1. ...[truncated 1305 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Create state directories with mode `0700`. - Create credential, callback, profile, and backup files with mode `0600`, independent of the process umask. - Use `os.open()` with `O_CREAT | O_EXCL` and an explicit `0o600` mode for temporary secret files. - After atomic replacement, explicitly verify and enforce the destination mode. - Set a restrictive umask such as `0o077` before creating any runtime state. - Avoid printing the full password in JSON or normal output. Prefer a one-time interactive display or an explicit `--show-password` option. - Prevent passwords from entering service logs and agent-visible command output. - Consider storing a salted password hash rather than the recoverable web password. - Apply the same permissions policy to OAuth sessions, callbacks, profile slots, token ledgers, and backups. ]]>
