fix(zoom): restrict token and config file permissions to owner-only

The Zoom harness was writing OAuth tokens (tokens.json) and app
credentials (config.json) to ~/.cli-anything-zoom/ without setting
restrictive file permissions, leaving sensitive secrets world-readable
by default.

This adds owner-only permissions (0o600 for files, 0o700 for the
config directory) after every write, matching the pattern already used
by the AnyGen harness. On Windows, where os.chmod only controls the
read-only flag, we also call icacls to strip inherited ACLs and grant
full control exclusively to the current user.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Saeed Al Mansouri
2026-03-25 15:40:55 +04:00
parent be8c5f64b4
commit 1dcb7dce71
@@ -5,6 +5,9 @@ It is the only module that makes network requests.
"""
import json
import os
import platform
import subprocess
import time
import requests
from pathlib import Path
@@ -25,9 +28,35 @@ TOKEN_FILE = CONFIG_DIR / "tokens.json"
CONFIG_FILE = CONFIG_DIR / "config.json"
def _restrict_path(path: Path, mode: int):
"""Set file/directory permissions, with icacls enforcement on Windows.
On Unix, uses os.chmod directly.
On Windows, os.chmod only controls the read-only flag, so we also
run icacls to grant access exclusively to the current user.
"""
try:
path.chmod(mode)
except OSError:
pass
if platform.system() == "Windows":
try:
username = os.environ.get("USERNAME", "")
if username:
subprocess.run(
["icacls", str(path), "/inheritance:r",
"/grant:r", f"{username}:(F)"],
capture_output=True, timeout=10,
)
except (FileNotFoundError, subprocess.TimeoutExpired):
pass # icacls not available or timed out — best effort
def get_config_dir() -> Path:
"""Get or create config directory."""
"""Get or create config directory with owner-only permissions (0o700)."""
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
_restrict_path(CONFIG_DIR, 0o700)
return CONFIG_DIR
@@ -40,10 +69,11 @@ def load_config() -> dict:
def save_config(config: dict):
"""Save OAuth app config."""
"""Save OAuth app config with owner-only permissions (0o600)."""
get_config_dir()
with open(CONFIG_FILE, "w") as f:
json.dump(config, f, indent=2)
_restrict_path(CONFIG_FILE, 0o600)
def load_tokens() -> dict:
@@ -55,11 +85,12 @@ def load_tokens() -> dict:
def save_tokens(tokens: dict):
"""Save OAuth tokens to disk."""
"""Save OAuth tokens to disk with owner-only permissions (0o600)."""
get_config_dir()
tokens["saved_at"] = time.time()
with open(TOKEN_FILE, "w") as f:
json.dump(tokens, f, indent=2)
_restrict_path(TOKEN_FILE, 0o600)
def get_authorize_url(client_id: str, redirect_uri: str) -> str: