T09 · Insecure Skill Coding Practices
- Location
sharesight/auth.py:36- Finding
OAuth access token is written before restrictive permissions are applied
- Content
View full analysis
None: """Save configuration to disk.""" CONFIG_DIR.mkdir(parents=True, exist_ok=True) with open(CONFIG_FILE, "w") as f: json.dump(config, f, indent=2) # Set restrictive permissions CONFIG_FILE.chmod(0o600) ``` ### Technical Analysis The configuration file contains the cached Sharesight OAuth bearer token. The code creates or truncates the file using `open(CONFIG_FILE, "w")` and only applies mode `0600` after the token has been written and the file has been closed. For a newly created file, its initial permissions are determined by the process umask. With a permissive umask, the file may temporarily be readable by other local users. This creates a time-of-check/time-of-use window between file creation and the subsequent `chmod`. The write is also not atomic. Process termination, filesystem errors, or concurrent invocations can leave a partially written JSON file. In addition, normal path-based `open` follows symbolic links. If an attacker can manipulate the configuration path under the victim's account or through an inadequately protected parent directory, the write could be redirected to another file accessible to that user. ### Attack Path 1. The victim runs `sharesight auth login` or another command that refreshes the OAuth token. 2. The application obtains an access token from Sharesight. 3. `save_config` creates or truncates `~/.config/sharesight-cli/config.json` using permissions derived from the current umask. 4. Before `chmod(0o600)` executes, a local attacker monitoring the path reads the bearer token from the temporarily accessible file. 5. The attacker submits the token to the Sharesight API before it expires. 6. The attacker gains the Sharesight API permissions ass ...[truncated 790 chars]- Remediation
View remediation
None: CONFIG_DIR.mkdir(parents=True, exist_ok=True, mode=0o700) CONFIG_DIR.chmod(0o700) fd, temp_path = tempfile.mkstemp(dir=CONFIG_DIR, prefix=".config-", text=True) try: os.fchmod(fd, 0o600) with os.fdopen(fd, "w") as f: json.dump(config, f, indent=2) f.flush() os.fsync(f.fileno()) os.replace(temp_path, CONFIG_FILE) except Exception: try: os.unlink(temp_path) except FileNotFoundError: pass raise ``` ]]>
