T09 · Insecure Skill Coding Practices
- Location
runtime/storage.py:43- Finding
Long-Lived API Key Stored Without Explicit Access Restrictions
- Content
View full analysis
Vulnerability Details
File Location:
runtime/skill_runtime.py:59-71,runtime/storage.py:43-57
Vulnerability Type: Plaintext credential storage with ambient filesystem permissions
Risk Level: HighVulnerable Code
python class FileSecretStore: def __init__(self, path: Path): self.path = path def get_secret(self, name: str) -> str | None: data = load_json(self.path, {}) value = data.get(name) return str(value) if value else None def set_secret(self, name: str, value: str) -> None: data = load_json(self.path, {}) data[name] = value save_json(self.path, data)python def ensure(self) -> None: self.root.mkdir(parents=True, exist_ok=True) self.reports_dir.mkdir(parents=True, exist_ok=True) def load_json(path: Path, default: Any) -> Any: if not path.exists(): return default return json.loads(path.read_text(encoding="utf-8")) def save_json(path: Path, payload: Any) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")Technical Analysis
The long-lived
cm_sk_live_...API key is serialized directly intosecrets.json. Neither the storage directory nor the secret file is assigned an explicit owner-only mode. The resulting access controls depend entirely on the process umask, inherited directory permissions, and whether the destination already exists.Path.write_text()also follows symbolic links and does not provide an atomic, exclusive file creation guarantee. If the storage location is writable or manipulable by another local principal, that principal may be able to read the credential or redirect the write through a pre-positioned symbolic link.This behavior exceeds the minimum safeguards expected for a file explicitly designated as a secret store.
At
...[truncated 1316 chars]
- Remediation
View remediation
Remediation Suggestions
- Create the Skill storage directory with mode
0700. - Create
secrets.jsonwith mode0600, independent of the ambient umask. - Use an atomic write pattern:
- Create a temporary file in the same directory with exclusive creation.
- Set mode
0600before writing sensitive data. - Flush and synchronize the file.
- Atomically replace the destination.
- Reject symbolic links for both the storage directory and secret file. Where supported, use no-follow file-opening flags.
- Validate ownership and permissions when loading an existing secret file; refuse to proceed if it is group-readable, world-readable, or owned by another principal.
- Prefer an operating-system credential manager or platform-provided secret store over plaintext JSON.
- Avoid using the generic
save_json()routine for credentials; implement a dedicated secret-writing function with mandatory security invariants.
- Create the Skill storage directory with mode
