Honeytoken injection was silently off for streaming SSR - #16
Merged
Conversation
Interception required !res.headersSent. Angular SSR — and Nuxt, and anything else that calls res.writeHead() then pipes — commits headers before the first byte of body, so that guard disabled injection for exactly the apps most likely to be serving server-rendered HTML. The failure was silent in the worst way: the page rendered correctly, no error was logged, and nothing was injected. A trap that is not there looks identical to a trap nothing has walked into. headersSent was the wrong question. What matters is whether a Content-Length has been committed that can no longer be corrected — growing the body past a declared length truncates it at the client. Under chunked encoding none is declared, so the body is free to change, which is precisely the streaming case. FOUND BY INSTALLING IT, NOT BY TESTING IT All eight existing tests passed, because every one of them used res.send(), which buffers and sets Content-Length. The suite agreed with itself and was wrong about production. It took wiring the adapter into a real Angular SSR app and grepping the rendered HTML for the link to see it. Two tests added for the paths that were missing: writeHead + chunked injects, and a response with a committed Content-Length is left alone.
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.
Follow-up to #15, found by installing it on a real Angular SSR site.
What was broken
Interception required
!res.headersSent. Angular SSR — and Nuxt, and anything else callingres.writeHead()then piping — commits headers before the first byte of body, so that guard disabled injection for exactly the apps most likely to be serving server-rendered HTML.The failure was silent in the worst way: page rendered correctly, no error logged, nothing injected. A trap that isn't there looks identical to a trap nothing has walked into.
headersSentwas the wrong questionWhat matters is whether a
Content-Lengthhas been committed that can no longer be corrected — growing the body past a declared length truncates it at the client. UnderTransfer-Encoding: chunkednone is declared, so the body is free to change. That's the streaming case.Found by installing it, not by testing it
All eight existing tests passed. Every one used
res.send(), which buffers and setsContent-Length. The suite agreed with itself and was wrong about production.It took wiring the adapter into a real Angular SSR app and grepping the rendered HTML for the link:
Then following the bait produced the first meaningful SDK detection in this product's history:
{ "source": "sdk_tripwire", "score": 90, "level": "HIGH", "signals": [{ "name": "tripwire_triggered", "category": "honeypot", "confidence": 0.95 }] }Every
sdkdetection before today scored 0.Testing
199 green. Two tests added for the paths that were missing:
writeHead+ chunked injects, and a response with a committedContent-Lengthis left untouched.