Skip to content

Security: iM3SK/cc-aio-mon

.github/SECURITY.md

Security Policy

Supported Versions

Only the latest release receives security fixes.

Reporting a Vulnerability

Do not open a public GitHub issue for security vulnerabilities.

Report privately via GitHub's Security Advisories feature. Include:

  • Description of the vulnerability
  • Steps to reproduce
  • Potential impact

You will receive a response within 72 hours. If confirmed, a fix will be released as soon as possible.

Scope

In scope:

  • monitor.py, statusline.py, shared.py, update.py, pulse.py
  • check-requirements.ps1, check-requirements.sh
  • Setup documentation under docs/
  • CI workflows under .github/workflows/

Out of scope:

  • Claude Code CLI itself — report to anthropics/claude-code
  • Python standard library — report to python.org/security
  • GitHub Actions runners and the GitHub platform — report to GitHub
  • Third-party packages pinned in .github/workflows/requirements-bandit.txt — report upstream to the respective maintainers

Security Model

CC AIO MON reads session data from Claude Code via stdin and writes snapshots to a local temp directory. The update.py --apply command invokes git pull --ff-only as a subprocess only when explicitly requested. The pulse.py module performs outbound HTTPS requests to Anthropic's public status endpoints (see below) — no credentials, no API keys, no user data are transmitted.

Outbound network (pulse.py):

  • GET https://status.claude.com/api/v2/summary.json — public status page JSON (every 30 s)
  • GET https://api.anthropic.com/v1/messages — unauthenticated liveness probe; expects 401/405 (every 30 s)
  • Request body: none. Headers: User-Agent: cc-aio-mon-pulse/<version> only — version string tracks shared.VERSION (single source of truth since v1.10.2).
  • Response size capped at 512 KB; socket timeouts 4–5 s.
  • Opt-out: set CC_AIO_MON_NO_PULSE=1 to disable the background worker entirely.

Monitored Surfaces

  • statusLine JSON input (stdin) — size-capped (1 MB), parse-error bounded.
  • Session IDs — regex-validated (^[a-zA-Z0-9_\-]{1,128}$), reserved names (rls, stats, pulse) blocked.
  • transcript_path (statusline JSON) — containment-checked: ~/.claude/projects/ itself must be a real directory, and the transcript must be a regular file inside it; symlinks/junctions are rejected. (added 2026-04; root validation tightened 2026-05)
  • Transcript content (read-only, ~/.claude/projects/) — per-file 50 MB cap, model IDs sanitized before terminal render. (added 2026-04)
  • File I/O — confined to $TMPDIR/claude-aio-monitor/ (writes) and ~/.claude/projects/ (reads). Atomic writes via NamedTemporaryFile + os.replace.
  • Subprocess — git only. shell=False, list args, GIT_TERMINAL_PROMPT=0, bounded timeouts, minimal env whitelist (added 2026-04). Never uses --no-verify / --force.
  • Network — Pulse: status.claude.com + api.anthropic.com HTTPS GET, no auth, bounded size (512 KB) + timeout (5 s). Update: git fetch origin main (HTTPS or SSH, configurable by user's git remote).

Opt-out env vars: CC_AIO_MON_NO_PULSE=1, CC_AIO_MON_NO_UPDATE_CHECK=1.

Key protections:

  • Session ID validated against [a-zA-Z0-9_-]{1,128} — prevents path traversal
  • All JSON fields sanitized before terminal output — prevents escape injection
  • Atomic writes via NamedTemporaryFile + os.replace() — prevents partial reads
  • File size limits on all reads (1 MB JSON/source files, 2 MB JSONL, 10 MB cross-session, 50 MiB transcripts, 512 KB pulse response, 1 MB pulse.jsonl) — prevents memory exhaustion
  • Symlink and NTFS junction rejection on temp data directory — lstat() + S_ISDIR verification with FILE_ATTRIBUTE_REPARSE_POINT check on Windows. Defends against TOCTOU races between mkdir and validation.
  • Temp directory created with 0o700 permissions where supported
  • update.py guards: dirty tree, wrong branch, detached HEAD, divergence, downgrade, Python version mismatch
  • pulse.py uses urllib.request.urlopen with default CA verification (no ssl._create_unverified_context); errors are caught by specific type, never broadly suppressed in probe logic
  • (v1.12.0) Both monitor.py (interactive TUI) AND update.py --apply (CLI updater) acquire the same singleton OS-level lock (shared.acquire_singleton_lock — fcntl.flock on Unix, msvcrt.locking on Windows) on $TMPDIR/claude-aio-monitor/monitor.lock. Prevents two concurrent dashboards from racing on shared snapshot reads and crash-log writes, AND prevents update.py --apply from rewriting .py files while monitor's threads still hold open references. monitor.py --list mode is exempt (one-shot, non-interactive read)
  • (v1.12.0; extended v1.12.2) monitor-crash.log rotated to .log.1 via shared.rotate_crash_log — bounds disk growth across repeated crash cycles. Since v1.12.2 _install_crash_logger calls it with always=True so the previous traceback is preserved even when both crashes are under 1 MB (two crashes in quick succession can no longer overwrite each other)
  • (v1.12.0) File-IPC schema tagged with shared.SCHEMA_VERSION (currently 1) — statusline writes the field on every snapshot + history entry; monitor reads via dict.get() so unknown values are silently ignored, providing forward-compat for future incompatible bumps
  • (v1.12.0) Post-pull syntax check unified in shared.check_syntax_after_pull — single canonical loop iterated by both update.py --apply (CLI) and monitor.py:_apply_update_worker (TUI background thread). A regression-guard test fails if either consumer reintroduces the parallel implementation

See also

There aren't any published security advisories