Skip to content

Explain decoy hits in plain language on the Detections page - #119

Merged
cport1 merged 1 commit into
mainfrom
explain-decoy-hits-443
Nov 10, 2026
Merged

cport1 merged 1 commit into
mainfrom
explain-decoy-hits-443

Conversation

@cport1

@cport1 cport1 commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Companion to WebDecoy/app#1736. Not released: no version bump, no release.sh, no R2 upload.

What changed

  • What happened column on Detections (after Date): one plain sentence for each tripwire hit, the same words the WordPress digest email uses for the same hit. Rows that are not decoy hits (heuristic scoring, test triggers, the decoy-served follow-up row) show nothing new; the technical columns stay as they were.
  • sdk/src/HitExplanation.php: PHP port of pkg/hitexplain.Explain. The sentences are not written here: sdk/src/registry/hit-explanations.generated.php and hit-explanation-vectors.generated.json are generated by go run ./cmd/export-hit-explanations -php|-golden in WebDecoy/app.
  • Each tripwire hit records trap_kind: hidden_link (honeytoken), fake_plugin (absent-plugin prefixes, now their own rule), trap_path (xmlrpc.php, custom paths), probe_path (default scanner paths), author_lookup / user_list (enumeration traps). It goes into the local row and the reported violation, so the cloud digest picks the same sentence.
  • Rows written before this have no kind and get an honest fallback: the hidden-link sentence for /__wd/ paths, otherwise "a path set up as a trap on your site".

Sentences say only what the hit records. They never claim how the visitor found the path or that anything was blocked. Query strings, fragments and control characters never reach a sentence, and the cell is esc_html'd. Copy is English only: the table is generated, so it is not in the .pot.

Tests (php tests/run.php, 210 passed)

  • Every golden vector from Go reproduces kind and sentence exactly (seen failing on a one-character drift).
  • Invalid UTF-8 path counts as not recorded.
  • Local rows: tripwire rows explained from stored flags, legacy rows fall back, non-decoy rows get nothing.
  • TripwireRule records the right kind per rule; an unknown kind cannot pick a sentence.
  • Source scan: every trap kind the plugin records is one the shared table knows.

From WebDecoy/app, WEBDECOY_WORDPRESS_PLUGIN=<this checkout> go test ./cmd/export-hit-explanations/ proves these generated files match pkg/hitexplain byte for byte.

Part of WebDecoy/app#443.

…/app#443)

A new What happened column shows one plain sentence for each tripwire hit,
the same words the WordPress digest email uses. The sentence table and golden
vectors are generated from WebDecoy/app's pkg/hitexplain; HitExplanation is a
PHP port pinned case for case by those vectors.

Each tripwire hit now records which trap it was (trap_kind): hidden_link for
the honeytoken, fake_plugin for absent-plugin paths, author_lookup and
user_list for the enumeration traps, probe_path for the default scanner
paths, trap_path otherwise. Rows written before this keep an honest generic
sentence.
@cport1
cport1 merged commit 3c77ec9 into main Nov 10, 2026
4 checks passed
@cport1
cport1 deleted the explain-decoy-hits-443 branch November 10, 2026 15:13
@cport1 cport1 mentioned this pull request Nov 10, 2026
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.

1 participant