T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/connections.json:1
- Finding
- Plaintext Odoo bearer credential bundled and persistently stored in the Skill package<![CDATA[ ## Vulnerability Details **File Location**: `scripts/connections.json:1-7`; `scripts/odoo_json2_client.py:64-79`; `SKILL.md:42-45, 116` **Vulnerability Type**: Hardcoded secret and insecure plaintext credential storage **Risk Level**: High ### Vulnerable Code `scripts/connections.json:1-7`: ```json { "odoo19c": { "base_url": "https://odoo19c.ylhctec.com", "database": "odoo19c", "api_key": "[REDACTED: plaintext 40-character API key was present]" } } ``` `scripts/odoo_json2_client.py:64-79`: ```python def _save_profiles(profiles: dict[str, dict[str, str]]) -> None: PROFILE_FILE.write_text( json.dumps(profiles, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) def save_profile(name: str, base_url: str, database: str, api_key: str) -> int: profiles = _load_profiles() profiles[name] = { "base_url": base_url.rstrip("/"), "database": database, "api_key": api_key, } _save_profiles(profiles) print(f"Profile saved: {name}") return 0 ``` `SKILL.md:42-45, 116`: ```markdown - Persist successful connections locally as named profiles containing `base_url`, `database`, and `api_key`. - Reuse saved profile by default in later turns; do not repeatedly ask for credentials. - If multiple profiles exist and user does not specify a target system at session start, ask which system/profile to use before proceeding. - Ask for `base_url`, `database`, and `api_key` only when no usable profile exists or when user wants a new/updated system. ``` ```markdown - Never log or persist API keys in files, command history snippets, or chat output. ``` ### Technical Analysis The distributed `connections.json` contains a plaintext, live-looking bearer credential together with its target host and database. The credential value has been redacted in this report to avoid further disclosure. The profile-saving implementation also serializes future API keys directly into `scripts/connectio ...[truncated 2954 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Immediately revoke and rotate the exposed credential.** - Treat the bundled key as compromised. - Review Odoo access logs for unexpected discovery, read, write, delete, or custom-method requests. - Restrict the replacement token to the minimum models and operations required. 2. **Remove credentials from distributed artifacts and repository history.** - Replace `scripts/connections.json` with an empty template. - Add the real profile file to `.gitignore` and package-exclusion rules. - Purge the exposed value from version-control history, caches, release archives, and backups where feasible. 3. **Use a secure credential storage mechanism.** - Prefer an operating-system keychain, managed secret store, or short-lived environment injection. - Store only a secret reference in the profile file. - Avoid accepting secrets directly through command-line arguments because process listings and shell history may expose them. 4. **If file storage is unavoidable:** - Store profiles outside the source and installation tree. - Create the file with owner-only permissions, such as mode `0600` on POSIX systems. - Validate ownership and permissions before reading it. - Clearly warn users that plaintext storage is being used. 5. **Enforce secure transport.** - Parse and validate `base_url`. - Require the `https` scheme. - Permit plaintext HTTP only through an explicit development-only override restricted to loopback addresses. 6. **Make the documentation consistent.** - Remove the instruction to persist raw API keys, or revise the implementation to use secure storage. - Preserve the existing guardrail prohibiting secrets in files, command history, and chat output. ]]>
