Skip to content

PavlokPager — Security

Threat model (local CLI, single user)

PavlokPager holds one secret: a Pavlok auth token that can make the wearer's band vibrate/beep/zap. The realistic threats are:

  1. Token theft — another local user or a backup/sync process reading the token.
  2. Token leakage — the secret ending up in shell history, the process list, or logs.
  3. Abuse loop — a buggy or runaway agent firing zaps repeatedly.
  4. Log tampering / corruption breaking the safety budget.

Not in scope: network MITM (mitigated by TLS to a fixed host), multi-tenant auth, remote attackers (no listening service).

Controls

Threat Control Where
Token theft at rest DPAPI (ProtectedData, CurrentUser) on Windows; 0600 file + 0700 dir on Linux/macOS, created with those modes (no chmod race) Protect / WriteOwnerOnly / EnsureConfigDir
Token leakage via argv Secrets never positional; hidden prompt or piped stdin only Setup, LoginAsync, ReadSecret
Token leakage via output Raw API bodies never echoed; status+reason only; full body behind PAVLOK_DEBUG LoginAsync
Abuse loop maxPagesPer8Hours hard budget (default 3) → exit 3; maxZap clamp PageAsync, SendStimulusAsync
Log corruption Torn/invalid JSONL lines skipped, never thrown ReadLog
Transport HTTPS only, constant host, per-request Bearer SendStimulusAsync
CI supply chain GitHub Actions pinned to commit SHAs (not mutable tags) .github/workflows/security.yml

Gates run before every push

Per house policy, code ships to its private repo only after: (1) a security scan, and (2) the /council-code-review multi-model gate. Results for this repo:

1. Semgrep SAST (Docker)

docker run --rm -v "${PWD}:/src" -w /src `
  semgrep/semgrep@sha256:06938c1f365d3f67b8cedd8bc117607ae64253f88a0e768e9da9408548927dd6 `
  semgrep scan --config p/csharp --config p/security-audit `
               --config p/secrets --config p/owasp-top-ten Program.cs

Result: 725 rules, 1 file, 0 findings. Scan record: semgrep-scan.json. Full SARIF re-runs in CI on every push and is uploaded as a build artifact (see .github/workflows/security.yml). CI runs on [self-hosted, home-ci, windows] (moved off ubuntu-latest); the Semgrep image is pinned by content digest rather than the mutable latest tag.

2. Codex OWASP review

A focused second-model pass. Found and fixed (commit ed946c7): secrets-on-argv, raw-response echo, plaintext-creation window / directory perms. Marked clean: SSRF, TLS, auth-header handling, unsafe deserialization, path traversal.

3. Council review (Codex + Gemini)

Correctness + quality (commit 573ee28): corrupt-log tolerance, shared HttpClient, streamed log read, dry-run log tagging, strict arg validation.

4. Re-audit (org-migration prep, 2026-07-08)

Codex OWASP/supply-chain pass over the full tree: CLEAN for hardcoded secrets, injection, and unsafe download/exec. Two supply-chain findings, both fixed: container.image: semgrep/semgrep was an unpinned mutable tag (MEDIUM) — re-pinned by digest; runs-on: ubuntu-latest was a mutable hosted-runner label (LOW) — moved to the fixed self-hosted [self-hosted, home-ci, windows] pool, matching house CI policy. A churn job (5x test-suite repeat, fail on any disagreement) was also added to tests.yml, which was previously missing.

Reporting

This is a private, single-user tool. Security concerns → the repo owner (andrewjonesdev).