T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/setup.py:40
- Finding
- Zotero API Key Exposed Through Command-Line Arguments and Plaintext Configuration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup.py:40-61` **Additional Documentation Locations**: `SKILL.md:31-36`, `README.md:42-47` **Vulnerability Type**: Plaintext credential exposure and insecure secret storage **Risk Level**: Medium ### Vulnerable Code ```python url = sys.argv[1].strip() api_key = sys.argv[2].strip() if not url: print("Error: URL cannot be empty.") return 1 if not api_key: print("Error: API key cannot be empty.") return 1 config = TEMPLATE.copy() config["zotero"] = {**TEMPLATE["zotero"], "url": url, "apiKey": api_key} if CONFIG_PATH.exists(): print(f"⚠️ Config already exists at {CONFIG_PATH}") answer = input(" Overwrite? [y/N] ").strip().lower() if answer not in ("y", "yes"): print(" Aborted.") return 0 CONFIG_PATH.write_text(json.dumps(config, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") ``` The documented setup command directly includes the secret: ```bash python scripts/setup.py "<YOUR_LIBRARY_URL>" "<YOUR_API_KEY>" ``` ### Technical Analysis The setup workflow receives the Zotero API key through `sys.argv`. Command-line arguments can be exposed through shell history, process inspection utilities, operating-system auditing, terminal recordings, and command telemetry. The key is subsequently placed in the `apiKey` configuration field and written in plaintext to `config.json`. The code does not explicitly create or change the file to owner-only permissions. For a newly created file, effective permissions therefore depend on the process umask. For an existing file, `write_text()` generally retains its existing permissions. Although `.gitignore` excludes `config.json`, this only reduces accidental source-control commits. It does not protect the key from other local accounts, processes, backups, endpoint monitoring, or overly broad file permissions. The documentation recommends a key with library access, notes access, write access, and potentiall ...[truncated 1614 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Remove the API key from command-line arguments** - Accept the key through the `ZOTERO_API_KEY` environment variable, or prompt interactively with `getpass.getpass()`. - Do not include real secrets in commands that may be saved to shell history. 2. **Avoid inline secret storage by default** - Generate configuration containing only `apiKeyEnv` or `apiKeyPath`. - Keep the `apiKey` field empty unless the user explicitly chooses the less secure fallback. 3. **Enforce restrictive file permissions** - If a dedicated secret file is created, use owner-only mode `0600`. - Validate existing secret-file permissions and warn or refuse when group or world access is present. - If inline storage remains supported, explicitly protect `config.json` with mode `0600`. 4. **Reduce credential privileges** - Instruct users to grant only the library and write permissions required for their intended workflow. - Do not request notes access unless note management is actually needed. - Limit group permissions to explicitly required groups where Zotero supports that configuration. 5. **Improve documentation** - Replace the positional-key setup example with an environment-based or interactive workflow. - Clearly warn that inline configuration is plaintext and that `.gitignore` does not provide local access control. 6. **Add security tests** - Test that generated secret-bearing files have owner-only permissions. - Test that the recommended setup path does not place the API key in `sys.argv` or generated general-purpose configuration. ]]>
