T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/setup_ads.py:4
- Finding
- Google Ads Credential File Is Created Without Restrictive Permissions## Vulnerability Details **File Location**: `scripts/setup_ads.py`, lines 4–12 **Vulnerability Type**: Insecure permissions on a sensitive configuration file **Risk Level**: Medium ### Vulnerable Code ```python def create_config_template(path): config = { 'developer_token': 'INSERT_DEVELOPER_TOKEN_HERE', 'client_id': 'INSERT_CLIENT_ID_HERE', 'client_secret': 'INSERT_CLIENT_SECRET_HERE', 'refresh_token': 'INSERT_REFRESH_TOKEN_HERE', 'use_proto_plus': True } with open(path, 'w') as f: yaml.dump(config, f, default_flow_style=False) ``` ### Technical Analysis The script creates `~/.google-ads.yaml` with Python's standard `open(path, 'w')` operation and does not explicitly enforce owner-only permissions. The resulting permissions therefore depend on the process umask. For example, a typical umask of `022` can produce a file with mode `0644`, making it readable by other local users. The file initially contains placeholders, but its intended purpose is to store a Google Ads developer token, OAuth client secret, and refresh token. After a user replaces the placeholders with real credentials, permissive file permissions can expose those secrets to other accounts on the same system. ### Attack Path 1. The user runs `scripts/setup_ads.py`. 2. The script creates `~/.google-ads.yaml` without explicitly setting mode `0600`. 3. The user inserts valid Google Ads credentials into the generated file. 4. On a multi-user host with a permissive umask, another local account reads the configuration file. 5. The attacker extracts the developer token, client credentials, and refresh token. 6. The attacker attempts to reuse those credentials through the Google Ads API. Exploitation requires local filesystem access under an account permitted to read the resulting file. Actual API access remains constrained by the validity, authorization scope, and account permissions o ...[truncated 622 chars]
- Remediation
- ## Remediation Suggestions Create the credential file atomically with owner-only permissions and refuse to overwrite an existing file: ```python import os import yaml def create_config_template(path): config = { 'developer_token': 'INSERT_DEVELOPER_TOKEN_HERE', 'client_id': 'INSERT_CLIENT_ID_HERE', 'client_secret': 'INSERT_CLIENT_SECRET_HERE', 'refresh_token': 'INSERT_REFRESH_TOKEN_HERE', 'use_proto_plus': True, } flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL fd = os.open(path, flags, 0o600) try: with os.fdopen(fd, 'w') as f: yaml.safe_dump(config, f, default_flow_style=False) except Exception: try: os.unlink(path) except OSError: pass raise ``` Additional hardening measures: - Verify the final mode with `os.stat()` and reject or correct group/world-readable permissions. - Apply `os.chmod(path, 0o600)` where appropriate as a defense-in-depth measure. - Warn users that the file contains sensitive credentials and must not be committed to source control. - Prefer a platform credential store or secrets manager where operationally feasible. - Rotate the refresh token, client secret, and developer token if unauthorized file access is suspected.
