feat(fastify): inject the honeytoken link, closing the last gap in #482 - #18
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the Fastify third of WebDecoy/app#482.
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
onSendrather than the Express approachExpress wraps
res.write/res.endbecause it has no supported hook for rewriting a finished body. Fastify has one:onSendhands over the payload and takes back a replacement, and Fastify recomputesContent-Lengthfrom 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.writewith 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
jest.config.js— the package was runningjest --passWithNoTestswith no config and no testsDocs
Documents auto-injection for Fastify, and rewrites the Express "Set a Scraper Trap" section, which still taught the manual
honeytoken()placement this issue removed.