feat(vpn): command-detection mail becomes a summary + evidence file #1435

Merged
hermes merged 1 commit from inbox-detection-summary into main 2026-10-01 23:22:19 -06:00
Collaborator

The detection mail quoted every raw record of the pass (up to 10 per rule, each cut at 700 chars). One pass with a few rules ran past the runner's 8 KB body cap, so the mail arrived truncated mid-record — the reader could not tell what fired, or that the rest had been cut. Owner asked for a readable summary with a bit of log as proof.

scan.py now prints a bounded summary per hit and keeps every raw record:

  • fired / hosts / when / ran as / uid, then the distinct commands with counts (xN = records this command explains), argv cut at 180 chars, first 6 with a remainder count
  • one sample record as proof, fields cut at 240 chars
  • every raw record of the whole pass, plus each hit's LogsQL query, is written to $HERMES_HOME/detection-cron/evidence/<utc>.txt; the mail names the path (168 files kept, ~7 days of hourly hits)
  • per-hit query: and endpoint: lines drop out of the mail (they live in the evidence file)

Same promise as the cursor: say everything, print once. Nothing that fired is dropped from the record — it is moved to the named file. No agent invocation, no triage change: this stays a no_agent job whose stdout is the mail, and it still works with triage disabled.

Triage prompt no longer repeats raw records; it reads the evidence file and explains.

.forgejo/tests/test-command-detection-delivery.py pins the new shape: exactly one sample record quoted, repeats counted rather than reprinted, mail stays under 3000 chars for 10 x ~940-char records, the evidence file is named, exists, holds every record in full and carries the hit's query.

Verified locally: python3 .forgejo/tests/test-command-detection-delivery.py (all checks passed) and test-command-detection-rules.sh (4 checks + 7 mutations ok), plus a real pass report against the live pass.json — 47 lines / 4309 chars, down from a truncated 8 KB body.

The detection mail quoted every raw record of the pass (up to 10 per rule, each cut at 700 chars). One pass with a few rules ran past the runner's 8 KB body cap, so the mail arrived truncated mid-record — the reader could not tell what fired, or that the rest had been cut. Owner asked for a readable summary with a bit of log as proof. `scan.py` now prints a bounded summary per hit and keeps every raw record: - `fired` / `hosts` / `when` / `ran as` / `uid`, then the **distinct commands with counts** (`xN` = records this command explains), argv cut at 180 chars, first 6 with a remainder count - **one sample record** as proof, fields cut at 240 chars - every raw record of the whole pass, plus each hit's LogsQL query, is written to `$HERMES_HOME/detection-cron/evidence/<utc>.txt`; the mail names the path (168 files kept, ~7 days of hourly hits) - per-hit `query:` and `endpoint:` lines drop out of the mail (they live in the evidence file) Same promise as the cursor: say everything, print once. Nothing that fired is dropped from the record — it is moved to the named file. No agent invocation, no triage change: this stays a `no_agent` job whose stdout is the mail, and it still works with triage disabled. Triage prompt no longer repeats raw records; it reads the evidence file and explains. `.forgejo/tests/test-command-detection-delivery.py` pins the new shape: exactly one sample record quoted, repeats counted rather than reprinted, mail stays under 3000 chars for 10 x ~940-char records, the evidence file is named, exists, holds every record in full and carries the hit's query. Verified locally: `python3 .forgejo/tests/test-command-detection-delivery.py` (all checks passed) and `test-command-detection-rules.sh` (4 checks + 7 mutations ok), plus a real pass report against the live `pass.json` — 47 lines / 4309 chars, down from a truncated 8 KB body.
feat(vpn): command-detection mail becomes a summary + evidence file
All checks were successful
NixOS Build Check / Flake input regression guard (pull_request) Successful in 6s
PR base guard / Base drift / conflict check (pull_request) Successful in 8s
NixOS Build Check / Worker-scope reaper worker signature (mutation-tested) (pull_request) Successful in 8s
Secret Scan / gitleaks scan (pull_request) Successful in 11s
NixOS Build Check / Scope changed deployable hosts (pull_request) Successful in 24s
foremerge intent (advisory) all 3 changed path(s) covered by a live foremerge intent
NixOS Build Check / .forgejo/tests python suites (hermetic) (pull_request) Successful in 1m42s
NixOS Build Check / Build changed host closures (native, one eval) (pull_request) Successful in 1m53s
NixOS Build Check / Hermes terminal timeout tree kill (pull_request) Successful in 41s
NixOS Build Check / Hermes cron lost fire claim (pull_request) Successful in 21s
NixOS Build Check / Hermes dispatcher worktree base (pull_request) Successful in 1m36s
NixOS Build Check / foremerge accept-order contract (real binary) (pull_request) Successful in 7s
d0d416d21f
The detection mail quoted every raw record of the pass (up to 10 per rule,
each cut at 700 chars). A pass with a few rules ran past the runner's 8 KB
body cap, so the mail arrived truncated in the middle of a record: the
reader could not tell what fired or that the rest had been cut.

scan.py now prints a bounded summary per hit and keeps the raw records:

  - fired / hosts / when / ran as / uid, then the DISTINCT commands with
    counts (xN = records this command explains), argv cut at 180 chars,
    first 6 with a remainder count
  - ONE sample record as proof, fields cut at 240 chars
  - every raw record of the whole pass, plus each hit's LogsQL query, is
    written to $HERMES_HOME/detection-cron/evidence/<utc>.txt and the mail
    names the path (168 files kept)

Same promise as the cursor: say everything, print once. The mail stays well
under 8 KB for 10+ near-identical records, and nothing that fired is dropped
from the record — it is moved to the named file.

Triage prompt no longer repeats raw records: it reads the evidence file.

test-command-detection-delivery.py pins the new shape: one sample quoted,
repeats counted, mail under 3000 chars for 10 x 940-char records, evidence
file named, existing and complete.
hermes merged commit f3b6c12632 into main 2026-10-01 23:22:19 -06:00
hermes deleted branch inbox-detection-summary 2026-10-01 23:22:19 -06:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ymrtech/nix-config!1435
No description provided.