T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/monkeytype_stats.py:59
- Finding
- Monkeytype ApeKey Persisted Without Restrictive File Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/monkeytype_stats.py:59-63` **Vulnerability Type**: Plaintext credential storage with insufficient permission enforcement **Risk Level**: Medium ### Vulnerable Code ```python def save_config(config: dict): """Save config to workspace location""" WORKSPACE_CONFIG.parent.mkdir(parents=True, exist_ok=True) with open(WORKSPACE_CONFIG, 'w') as f: json.dump(config, f, indent=2) ``` The setup instructions at `SKILL.md:64-75` direct the Agent to save the user's Monkeytype ApeKey in this configuration file. ### Technical Analysis The `save_config` function stores the Monkeytype ApeKey in plaintext at `~/.openclaw/workspace/config/monkeytype.json`. It creates the parent directory and opens the file without explicitly enforcing owner-only permissions. Consequently, the resulting access permissions depend on the process umask and, when overwriting an existing file, its preexisting mode. On a host with permissive settings, another local user or process may be able to read the credential. The implementation also does not validate whether the destination or its parent components are symbolic links, which can make credential placement less predictable in an attacker-controlled local environment. Sending the ApeKey in an `Authorization` header over HTTPS to the fixed and documented `https://api.monkeytype.com` endpoint is necessary for the declared functionality and was not identified as unauthorized exfiltration. The security issue is the insufficiently protected local persistence of that credential. ### Attack Path 1. The user supplies a valid Monkeytype ApeKey during the documented setup flow. 2. The Agent stores the key in `~/.openclaw/workspace/config/monkeytype.json`. 3. The file is created or overwritten without explicitly setting mode `0600`; its effective permissions depend on the host configuration or existing file mode. 4. A local account or process with access to the workspace ...[truncated 1007 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Prefer the existing `MONKEYTYPE_APE_KEY` environment-variable mechanism or an operating-system secret manager instead of persistent plaintext storage. 2. If file persistence is required, create and maintain the configuration directory with mode `0700`. 3. Create the credential file atomically with mode `0600`, rather than relying on the process umask. 4. Correct permissions on preexisting configuration files before writing sensitive content. 5. Reject symbolic-link destinations and verify that the resolved file remains inside the intended configuration directory. 6. Write through a securely created temporary file in the same directory, set mode `0600`, flush and synchronize it, and then atomically replace the destination. 7. Clearly notify users that the config file contains a reusable credential and provide instructions for key revocation and rotation. 8. If unintended access may already have occurred, revoke the existing ApeKey in Monkeytype and generate a replacement after securing the storage location. Example hardening approach: ```python def save_config(config: dict): """Save sensitive configuration with owner-only permissions.""" WORKSPACE_CONFIG.parent.mkdir(parents=True, exist_ok=True, mode=0o700) os.chmod(WORKSPACE_CONFIG.parent, 0o700) if WORKSPACE_CONFIG.is_symlink(): raise RuntimeError("Refusing to write configuration through a symlink") flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC if hasattr(os, "O_NOFOLLOW"): flags |= os.O_NOFOLLOW fd = os.open(WORKSPACE_CONFIG, flags, 0o600) try: os.fchmod(fd, 0o600) with os.fdopen(fd, "w") as f: json.dump(config, f, indent=2) f.flush() os.fsync(f.fileno()) except Exception: try: os.close(fd) except OSError: pass raise ``` ]]>
