T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/notify.py:31
- Finding
- Push notification credentials are stored without enforced restrictive file permissions## Vulnerability Details **File Location**: `scripts/notify.py:31-45` **Vulnerability Type**: Plaintext credential storage with insufficient permission enforcement **Risk Level**: Medium ### Vulnerable Code ```python CONFIG_DIR = Path.home() / ".workbuddy" / "flight-monitor" CONFIG_FILE = CONFIG_DIR / "notify_config.json" def load_config() -> dict: if CONFIG_FILE.exists(): try: return json.loads(CONFIG_FILE.read_text(encoding="utf-8")) except Exception: pass return {} def save_config(cfg: dict): CONFIG_DIR.mkdir(parents=True, exist_ok=True) CONFIG_FILE.write_text(json.dumps(cfg, ensure_ascii=False, indent=2), encoding="utf-8") ``` The configuration written by this function includes the user-provided Bark, ServerChan, or PushDeer key: ```python cfg["service"] = service cfg["key"] = key save_config(cfg) ``` ### Technical Analysis Push-service credentials are saved in plaintext at `~/.workbuddy/flight-monitor/notify_config.json`. The code neither creates the directory with an explicit `0700` mode nor creates the credential file with an explicit `0600` mode. It also does not repair permissions on an existing file. Consequently, confidentiality depends entirely on the process umask and existing filesystem permissions. On a multi-user system, permissive defaults or a pre-created configuration file could allow another local account or compromised process to read the push credential. This network credential is necessary for the optional notification feature, but storing it without explicit access controls exceeds the minimum safe privilege model for that feature. ### Attack Path 1. A user runs `notify.py --setup` with a valid push-service key. 2. The key is written in plaintext to `~/.workbuddy/flight-monitor/notify_config.json`. 3. The file inherits permissions from the process umask or retains unsafe permissions if it already exists. ...[truncated 705 chars]
- Remediation
- ## Remediation Suggestions - Create `~/.workbuddy/flight-monitor` with mode `0700`. - Create credential files atomically with mode `0600`, rather than relying on the ambient umask. - After writing, explicitly verify and correct the file mode. - Reject symbolic links and use an atomic temporary-file-and-rename pattern to reduce file replacement risks. - Prefer an operating-system credential store or secret-management facility instead of plaintext JSON. - Avoid passing credentials directly on the command line because process listings and shell history may expose them. - If plaintext storage remains necessary, document the security implications and provide a command that verifies configuration permissions. Example hardening approach: ```python import os import tempfile CONFIG_DIR.mkdir(parents=True, exist_ok=True, mode=0o700) os.chmod(CONFIG_DIR, 0o700) fd, temp_name = tempfile.mkstemp(dir=CONFIG_DIR, prefix=".notify-", text=True) try: os.fchmod(fd, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as handle: json.dump(cfg, handle, ensure_ascii=False, indent=2) os.replace(temp_name, CONFIG_FILE) os.chmod(CONFIG_FILE, 0o600) finally: if os.path.exists(temp_name): os.unlink(temp_name) ```
