Skip to content

feat(fastify): inject the honeytoken link, closing the last gap in #482 - #18

Merged
cport1 merged 2 commits into
mainfrom
feat/fastify-honeytoken
Jul 31, 2026
Merged

feat(fastify): inject the honeytoken link, closing the last gap in #482#18
cport1 merged 2 commits into
mainfrom
feat/fastify-honeytoken

Conversation

@cport1

@cport1 cport1 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Closes the Fastify third of WebDecoy/app#482.

adapter before after
express ✅ 0.8.0 (streaming fix 0.8.1)
nextjs ✅ helper + docs
fastify ❌ nothing ✅ this PR

Fastify still shipped the original defect — generate a token, ask the developer to place the link. Leaving one adapter behind is the same failure the issue describes, just narrower: a plugin that looks installed and detects nothing.

Why onSend rather than the Express approach

Express wraps res.write/res.end because it has no supported hook for rewriting a finished body. Fastify has one: onSend hands over the payload and takes back a replacement, and Fastify recomputes Content-Length from the return value — so the truncation bug that guard exists for in Express cannot occur here.

Streams are left alone, loudly

A streamed reply is not rewritten. Buffering it to insert an anchor would trade the customer's streaming behaviour, and its memory profile on large responses, for a hidden link.

But silence is exactly the failure mode of #482, so it warns once per process with the markup to embed manually. Once, not per request — a line per request is noise that gets filtered, which is the same as not warning.

This deliberately differs from Express, which does buffer: there, streaming SSR writes through res.write with no declared length and buffering is the only way to reach the body; here, a stream is an explicit choice the framework hands us as a stream.

Verification

  • 11 cases through a real Fastify instance — JSON untouched, plain text untouched, streams intact, Content-Length consistent, link actually armed as a tripwire, same path across two boots
  • Verified the suite goes red when injection is disabled: 5 fail, and the 3 negative tests correctly stay green
  • 244 tests pass across all 5 packages, uncached
  • Adds jest.config.js — the package was running jest --passWithNoTests with no config and no tests

Docs

Documents auto-injection for Fastify, and rewrites the Express "Set a Scraper Trap" section, which still taught the manual honeytoken() placement this issue removed.

cport1 added 2 commits July 30, 2026 20:46
Express and Next.js got this in 0.8.x. Fastify still shipped the original
defect: generate a honeytoken, then ask the developer to place the link. Almost
nobody does, which is why sdk_tripwire had four rows in production ever while
the WordPress plugin — which injects the link itself — has real coverage.

Leaving one adapter behind is the same failure the issue is about, just narrower:
a plugin that looks installed and detects nothing.

WHY onSend AND NOT THE EXPRESS APPROACH

Express needs res.write/res.end wrapping because it has no supported hook for
rewriting a finished body. Fastify has one. onSend hands over the payload and
takes back a replacement, and Fastify recomputes Content-Length from what is
returned — so the truncation bug that guard exists for in Express cannot occur
here.

STREAMS ARE LEFT ALONE, LOUDLY

A streamed reply is not rewritten. Buffering it to insert an anchor would trade
the customer's streaming behaviour, and its memory profile on large responses,
for a hidden link — not a trade this plugin gets to make silently.

But silence is exactly the failure mode of #482, so it warns once per process
with the markup to embed manually. Once, not per request: a line per request is
noise that gets filtered, which is the same as not warning at all.

This differs from the Express adapter, which does buffer. There, streaming SSR
writes through res.write with no declared length and buffering is the only way to
reach the body at all; here, a stream is an explicit choice by the caller that
the framework hands us as a stream.

TESTS

Eleven cases through a real Fastify instance, covering the ways this could
corrupt a response rather than merely miss a detection: JSON untouched, plain
text untouched, streams intact, Content-Length consistent with the rewritten
body, and the link armed as a tripwire rather than being bait with no trap.

Verified the suite goes red when injection is disabled — five fail, and the
three negative tests correctly stay green.

Also adds jest.config.js; the package was running `jest --passWithNoTests`
against no config and no tests.

Docs: documents auto-injection for Fastify, and rewrites the Express "Set a
Scraper Trap" section, which still taught the manual honeytoken() placement that
this issue removed.
This repo is public and the JSDoc on exported options ends up in the shipped
.d.ts files, so several comments were publishing things that should not leave
the company:

  - adoption and scoring figures taken straight from the production database
  - an incident description that identified the site it happened on
  - references to issues in the private application repo, which render on
    GitHub as broken links to issues in THIS repo

The engineering rationale is worth keeping and stays — a maintainer still needs
to know why monitor is the default and why a trap beats a User-Agent. What goes
is the specific internal data backing it, restated in terms a reader outside the
company can act on:

  - production counts and averages -> the relationship they demonstrate
    ("scores near zero on a page view, an order of magnitude higher on a trap")
  - the named-customer incident -> the risk it illustrates
  - exact weight percentages stay, since the public threat-scoring docs already
    publish them
  - private issue references -> removed, or described in prose

Regenerated registry.generated.ts, whose header named the private repo.

Verified: build clean, 244 tests pass across all five packages, and the sweep
for internal strings now comes back empty against packages/*/dist/*.d.ts, which
is what actually ships to npm.

Note for the record: 0.8.0, 0.8.1 and 0.9.0 are already published with the old
text in their type definitions. This fixes the source going forward; those
versions would need deprecating or superseding to remove it from the registry.
@cport1
cport1 merged commit ca003ab into main Jul 31, 2026
2 checks passed
@cport1
cport1 deleted the feat/fastify-honeytoken branch July 31, 2026 01:53
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