T09 · Insecure Skill Coding Practices
- Location
scripts/ccp_proxy.py:215- Finding
API Key Exposure Through Command-Line Arguments and Plaintext Configuration
- Content
View full analysis
Vulnerability Details
File Location:
scripts/ccp_proxy.py:215-221andscripts/ccp_proxy.py:245-263
Vulnerability Type: Sensitive credential exposure
Risk Level: MediumThe documented usage in
SKILL.md:27-28also instructs users to supply API keys directly as command-line arguments.Vulnerable Code
python def cmd_test(): args = sys.argv[2:] key = None model = "qwen-3.5" for a in args: if a.startswith("sk-"): key = a elif a and not a.startswith("-"): model = apython def cmd_configure(key=None, model=None): args = sys.argv[2:] key = key or next((a for a in args if a.startswith("sk-")), "") model = model or next((a for a in args if not a.startswith("sk-") and not a.startswith("-")), "") if not key.startswith("sk-"): print("[FAIL] 请提供有效的 API Key:python3 ccp_proxy.py configure sk-xxx [model-id]") return if not model: model = "qwen-3.5" try: with open(MODELS_JSON) as f: models = json.load(f) except FileNotFoundError: models = [] except json.JSONDecodeError: print("[FAIL] models.json 已存在但不是合法 JSON,请手工检查后再试") return models = [m for m in models if m.get("id") != model] entry = make_model_entry(model, key) models.append(entry) with open(MODELS_JSON, "w") as f: json.dump(models, f, ensure_ascii=False, indent=2)Technical Analysis
API keys supplied through
sys.argvcan be retained in shell history and may be visible in process listings while the command executes. Theconfigureoperation then stores the key in~/.workbuddy/models.jsonas plaintext.When the configuration file is newly created, the code does not explicitly assign restrictive permissions. Its resulting mode depends on the user's current umask. If the umask permits group or world access, another ...[truncated 1302 chars]
- Remediation
View remediation
Remediation Suggestions
- Do not accept API keys through command-line arguments by default.
- Read credentials interactively with
getpass.getpass(), through standard input, or from an operating-system credential store. - If backward compatibility requires an argument, clearly warn that it is insecure and provide a safer default interface.
- Create new configuration files with mode
0600, and verify that existing files are owned by the current user and are not group- or world-readable. - Write updates atomically through a securely created temporary file in the same directory, apply restrictive permissions, and then replace the destination.
- Where supported, store only a keychain reference in
models.jsoninstead of the credential itself. - Advise affected users to remove exposed commands from shell history and rotate any key previously supplied through argv.
