fix(web,anchor-sdk): harden markdown rendering, add security headers, cap toml reads - #1002
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
determined-001
force-pushed
the
feat/cloud-page
branch
from
August 10, 2026 00:25
5c74234 to
5378d54
Compare
… cap toml reads
Four smaller findings from the same audit.
**Unsanitized docs markdown.** `lib/docs.ts` called `marked.parse()` with
default options, which passes raw HTML straight through, and the result went
to `dangerouslySetInnerHTML` in app/docs/[[...slug]]/page.tsx. `lib/reference.ts`
had already solved this with a locked-down renderer whose comment records
manual verification against <script>, onerror= and javascript: payloads — the
docs path simply never got it.
Rather than copy that renderer a second time, it moves to lib/markdownSafety.ts
and both call sites use it. The two being out of step is precisely how one of
them ended up unprotected; sharing the code means a future fix lands on both.
`resolveInternalLink` stays in reference.ts and is passed in, since only the
reference section rewrites .md paths onto routes.
Not remotely triggerable — the content is repo markdown. It matters because
this repo merges contributor docs PRs at volume and docs diffs attract the
least review of any change, so `<img src=x onerror=...>` in an innocuous-looking
PR would become stored XSS on the docs domain.
**No security headers.** next.config.js was `{}`, so responses carried no CSP,
HSTS, X-Frame-Options, X-Content-Type-Options or Referrer-Policy. /demo/contracts
was framable. Adds all of them plus Permissions-Policy. `'unsafe-inline'` is
required for styles (the app uses inline style props throughout) and for Next's
bootstrap script; `'unsafe-eval'` is development-only for React Refresh.
`connect-src 'self'` is correct here — the browser only talks to this app's own
SSE routes, the server is what reaches Horizon and RPC.
**X-Powered-By.** Removed. It advertised the exact framework version needed to
choose from the CVE list this branch just patched.
**Unbounded stellar.toml read.** `discoverAnchor` called `response.text()` with
no limit, so a hostile home domain could exhaust a consumer's memory. Now capped
at 100 000 bytes, matching `verifyWebhook`'s existing `maxBodyBytes` default in
pulse-webhooks. content-length is checked first, but it is a claim rather than a
promise, so the cap is also enforced while streaming and the reader is cancelled
on breach instead of leaving a hostile server transmitting.
Verified live: all six headers present, X-Powered-By absent, docs and reference
pages render unchanged (heading anchors, code blocks and links all intact).
12 renderer tests, 3 toml-cap tests; 70 pass in anchor-sdk, 22 in apps/web.
Note the renderer tests cover lib/markdownSafety.ts directly; that docs.ts uses
it is verified by the build and by rendering the live page, not by a unit test.
determined-001
force-pushed
the
fix/web-hardening
branch
from
August 10, 2026 00:28
a2d03c7 to
a901209
Compare
readCapped falls back to response.text() when a transport override returns something Response-shaped with no readable body. That branch was untested, which left anchor-sdk line coverage at 95.89% against a 96% floor.
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.
Four smaller findings from the same audit.
1. Unsanitized docs markdown
lib/docs.tscalledmarked.parse()with default options — which passes raw HTML straight through — and the result went todangerouslySetInnerHTMLinapp/docs/[[...slug]]/page.tsx.lib/reference.tshad already solved this with a locked-down renderer whose comment records manual verification against<script>,onerror=, andjavascript:payloads. The docs path simply never got it.Rather than copy that renderer a second time, it moves to
lib/markdownSafety.tsand both call sites use it. The two being out of step is precisely how one of them ended up unprotected; sharing the code means a future fix lands on both.resolveInternalLinkstays inreference.tsand is passed in, since only the reference section rewrites.mdpaths onto routes.Not remotely triggerable — the content is repo markdown. It matters because this repo merges contributor docs PRs at volume and docs diffs attract the least review of any change, so
<img src=x onerror=...>in an innocuous-looking PR would become stored XSS on the docs domain.2. No security headers
next.config.jswas{}. Responses carried no CSP, HSTS,X-Frame-Options,X-Content-Type-Options, orReferrer-Policy./demo/contractswas framable.Adds all of them plus
Permissions-Policy.'unsafe-inline'is required for styles (the app uses inlinestyleprops throughout) and for Next's bootstrap script;'unsafe-eval'is development-only for React Refresh.connect-src 'self'is correct — the browser only talks to this app's own SSE routes; the server is what reaches Horizon and RPC.3.
X-Powered-ByRemoved. It advertised the exact framework version needed to choose from the CVE list #998 just patched.
4. Unbounded
stellar.tomlreaddiscoverAnchorcalledresponse.text()with no limit, so a hostile home domain could exhaust a consumer's memory. Now capped at 100 000 bytes, matchingverifyWebhook's existingmaxBodyBytesdefault inpulse-webhooks.content-lengthis checked first, but it is a claim rather than a promise — so the cap is also enforced while streaming, and the reader is cancelled on breach instead of leaving a hostile server transmitting.Verification
Docs and reference pages render unchanged — heading anchors, code blocks, and links all intact.
12 renderer tests, 3 toml-cap tests. 70 pass in
anchor-sdk, 22 inapps/web.Scope note: the renderer tests cover
lib/markdownSafety.tsdirectly. Thatdocs.tsuses it is verified by the build and by rendering the live page, not by a unit test.