Skip to content

feat(watchdog): daemon logs every attack for triage; threatcrush watchdog to verify, fix, PR - #283

Merged
ralyodio merged 3 commits into
masterfrom
worktree-tui-watchdog-mode
Oct 4, 2026
Merged

ralyodio merged 3 commits into
masterfrom
worktree-tui-watchdog-mode

Conversation

@ralyodio

@ralyodio ralyodio commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

A ban says who attacked, not whether it worked. Watchdog mode keeps every attack on disk so each one can be verified afterwards and, when it is a real hole, fixed in a PR.

What's in it

  • The daemon logs every attack from its event bus to <state dir>/watchdog/attacks.jsonl (daemon/watchdog-service.ts): buffered, flushed each second and on shutdown, rotated at 50 MB, deduped. On by default; [watchdog] enabled = false turns it off, [watchdog] dir moves it. An unwritable dir is reported in status and the daemon log, never fatal.
  • Shared with adm when root: /var/lib/threatcrush/watchdog is setgid 2770 root:adm, the log and triage.json are 660, the same group the IPC socket trusts. Log readers can list and mark without sudo. A user-mode daemon keeps it 700/600.
  • IPC status gains watchdog: {enabled, dir, logged, answered, errored, error?}. The TUI header shows ◉ WATCHDOG 42 · 3 answered · 1 5xx from it; the TUI writes nothing itself.
  • What counts as an attack: any CRS-matched web request (even below the ban threshold), or any medium+ detection from another module. Firewall notices, heartbeats and plain 4xx/5xx are not.
  • log-watcher now puts the HTTP status, method and full request target (url) in web event details. Status is what separates refused (4xx) from answered (2xx, a possible hole) or errored (5xx).
  • threatcrush watchdog [list|show|brief|mark|path]: findings grouped by vhost+method+path+attack type, worst outcome first; refused/triaged hidden unless --all. brief <id> prints a Markdown hand-off (samples, repro curl, verify-then-fix steps) for an issue, PR body or agent. mark <id> hole|fp|fixed --note --pr stores the verdict. Reads the system daemon's directory first, like the client prefers the system socket.

Tested

  • tsc --noEmit clean; CLI vitest 49 files / 497 tests pass (new: core/__tests__/watchdog.test.ts, daemon/__tests__/watchdog-service.test.ts, view badge tests).
  • Built daemon booted under a throwaway HOME: logs [watchdog] logging attacks to …, IPC status returns {"enabled":true,…}, dir created 700 in user mode.
  • Built CLI against a seeded log: list ordering (answered SQLi → 5xx LFI → SSH detection → 40× refused /wp-login.php as one row), brief, mark fixed --pr dropping it from the queue, --dir before and after the subcommand.

Daemons need upgrading and restarting to start logging (and for web events to carry status).

🤖 Generated with Claude Code

… a PR

A ban says who attacked, not whether it worked. Watchdog mode keeps every
attack the dashboard sees so each one can be verified afterwards and, when
it is a real hole, fixed in a PR.

- `threatcrush tui --watchdog`, or `w` in the dashboard, appends attacks to
  <state dir>/watchdog/attacks.jsonl (owner-only, buffered, rotated at 50 MB,
  deduped across reconnect backfills). Turning it on also sweeps what is
  already on screen. Header badge: `◉ WATCHDOG 42 · 3 answered · 1 5xx`.
  Demo mode refuses, so canned events never reach the real log.
- An attack is any CRS-matched web request (even below the ban threshold),
  or any medium+ detection from another module; firewall notices,
  heartbeats and plain 4xx/5xx are not.
- log-watcher now puts the HTTP status, method and full request target in
  web event details. Status is what separates a probe that was refused
  (4xx) from one the app answered (2xx) or crashed on (5xx).
- `threatcrush watchdog [list|show|brief|mark|path]`: findings grouped by
  vhost+method+path+attack type, worst outcome first; `brief` prints a
  Markdown hand-off (samples, repro curl, verify-then-fix steps) for an
  issue, PR body or agent; `mark <id> hole|fp|fixed --note --pr` records
  the verdict in triage.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

17 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 8 | LOW: 8

Severity Rule Location
HIGH secret-aws-access-key prd/0003-detect-hardcoded-secrets-before-they-are-committed-or-served.md:126
MEDIUM sql-string-concatenation .github/workflows/migrate-dev2.yml:128
MEDIUM js-open-redirect apps/web/src/app/auth/login/page.tsx:67
MEDIUM js-unescaped-html-sink apps/web/src/app/hire/page.tsx:104
MEDIUM js-unescaped-html-sink apps/web/src/app/hire/page.tsx:108
MEDIUM js-open-redirect apps/web/src/app/invite/[token]/invite-client.tsx:55
MEDIUM js-open-redirect apps/web/src/components/funding/FundingClient.tsx:97
MEDIUM js-unescaped-html-sink apps/web/src/components/GuideReader.tsx:265
MEDIUM js-uninitialized-buffer packages/scan/src/node-rules.ts:456
LOW secret-generic-credential apps/web/src/app/api/auth/refresh/route.ts:17
LOW secret-generic-credential apps/web/src/app/api/auth/reset-password/route.ts:26
LOW secret-generic-credential apps/web/src/app/api/auth/reset-password/route.ts:27
LOW secret-generic-credential PRD.md:269
LOW tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:121
LOW tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:122
LOW sh-remote-script-execution scripts/smoke-test.sh:72
LOW secret-aws-access-key scripts/smoke-test.sh:150

Snippets are redacted; ThreatCrush never prints matched credential material.

Comment thread apps/cli/src/core/watchdog.ts Fixed
ralyodio and others added 2 commits October 4, 2026 09:08
…e-system-race)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The TUI only kept attacks while someone had a dashboard open with `w` on.
The daemon now logs every attack from its event bus, always:

- daemon/watchdog-service.ts subscribes to the bus, flushes each second
  and on shutdown, and reports {enabled, dir, logged, answered, errored,
  error?} in the IPC `status` reply. `[watchdog] enabled = false` turns it
  off; `[watchdog] dir` moves it. An unwritable dir is reported, not fatal.
- A root daemon shares /var/lib/threatcrush/watchdog with `adm` (the group
  its socket already trusts): setgid 2770, log and triage.json 660, so log
  readers can `threatcrush watchdog list` and `mark` without sudo. A
  user-mode daemon keeps it 700/600.
- The TUI no longer writes anything: the header badge shows the daemon's
  counts, and the `w` key and `tui --watchdog` flags are gone (unreleased).
- `threatcrush watchdog` reads the system daemon's directory first, like
  the client picks the system socket.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ralyodio ralyodio changed the title feat(tui): watchdog mode — log every attack, triage it, hand holes to a PR feat(watchdog): daemon logs every attack for triage; threatcrush watchdog to verify, fix, PR Oct 4, 2026
@ralyodio
ralyodio merged commit d722190 into master Oct 4, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants