T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/config_manager.py:37
- Finding
- Plaintext API credential storage and full credential disclosure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/config_manager.py:37-44, 71-84` **Vulnerability Type**: Plaintext secret storage and sensitive information disclosure **Risk Level**: High ### Vulnerable Code ```python def save_config(key: str, secret: str) -> None: """Persist key and secret to config.json.""" data = load_config() data["key"] = key.strip() data["secret"] = secret.strip() with open(CONFIG_PATH, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) print(f"[OK] Configuration saved to: {CONFIG_PATH}") ``` ```python if args.command == "load": cfg = load_config() configured = is_configured(cfg) output = { "configured": configured, "key": cfg.get("key", ""), "secret": cfg.get("secret", ""), "config_path": CONFIG_PATH, } print(json.dumps(output, ensure_ascii=False, indent=2)) # Exit code: 0 when configured, 1 otherwise sys.exit(0 if configured else 1) ``` The original source contains Chinese comments and user-facing messages; they are translated above without changing the relevant program behavior. ### Technical Analysis The configuration manager stores the reusable Xiangyun API key and secret in an ordinary plaintext JSON file. The file is opened with the process's default permission behavior, so its final access mode depends on the user's current `umask`. The implementation does not explicitly restrict the file to its owner. The `load` command then prints both credentials in full to standard output. This is unnecessary for determining whether the Skill is configured and can expose credentials through terminal history, captured command output, Agent transcripts, CI logs, or parent processes that collect stdout. In addition, `scripts/config_manager.py:67-68` accepts credentials through `--key` and `--secret` command-line arguments. Depending on the operating system and execution environment, command-line arguments may ...[truncated 1455 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not print secrets from the `load` command. Return only a Boolean configuration status and, if necessary, a partially redacted key identifier. 2. Accept the secret through protected standard input, an interactive password prompt such as `getpass.getpass()`, or an operating-system credential manager instead of a command-line argument. 3. Prefer an operating-system secret store rather than `config.json`. 4. If file storage is unavoidable, create the file atomically with owner-only permissions such as `0600`. 5. Validate existing file ownership and permissions before reading credentials. 6. Avoid following symbolic links when creating or replacing the configuration file. 7. Document credential rotation and revocation procedures. 8. Ensure logs and exception handlers never include credential values. ]]>
