Commit b972e76
fix: close SSRF bypass, make marketing surface reachable, replace fabricated AI assistant (#1016)
* fix(pulse-webhooks)!: close the SSRF bypass in the delivery guard
`WebhookDelivery` kept its own `net.BlockList` of private ranges, separate
from the list in `UrlValidator`. The two drifted, and the copy guarding
outbound deliveries was the weaker one: it omitted `0.0.0.0/8`, the IPv6
unspecified address, CGNAT `100.64.0.0/10` and `192.0.0.0/24`.
Because each of those is an IP literal, `validateResolvedHostname` returned
early and never re-checked them. So `http://0.0.0.0:8080/` passed validation
and was fetched - and on Linux `0.0.0.0` routes to loopback, meaning a
customer-registered webhook URL could reach services bound to 127.0.0.1 on
the delivery worker. Verified end to end: `validateUrl` returned null for
`0.0.0.0` while correctly blocking `127.0.0.1`, and a real fetch reached a
loopback-bound server.
Extract the range checks into `private-ip.ts` and have both callers use it,
so there is nothing left to drift. `validateUrl` also picks up the
`.localhost` suffix check it was missing - RFC 6761 reserves the whole
suffix, not just the bare label.
`UrlValidator` is now exported. Its own docs claimed consumers wire it in
front of their own fetch, but it was never re-exported from index.ts, so
nobody could - the claim was false and the class was unreachable.
Adds 8 regression cases against the delivery path, not just the validator.
* fix(web): make the marketing surface reachable and stop overstating it
A first-time visitor landing on `/` could not get anywhere. Every nav link,
both hero buttons, the CTA, and all nine footer links were `href="#"` -
sixteen dead links on pages whose destinations (`/docs`, `/reference`,
`/demo/contracts`, `/starters`, `/changelog`) all already existed and worked.
Centralise every off-page destination in `lib/links.ts` so a renamed route
breaks in one file instead of rotting silently in four.
Also fixed, all in the same category of claiming more than is true:
- `/starters` linked three repos that do not exist - `orbital-next-starter`,
`orbital-express-starter`, `orbital-anchor-starter` all 404, so every
source link and all six deploy buttons pointed at nothing. Repointed at
the in-repo `examples/`, with the repo path now an actual link. Vercel's
clone flow takes `root-directory`, so the Next.js starter keeps a working
one-click; Railway's `templateUrl` has no subdirectory equivalent, so
those two link to their README rather than ship a button that deploys the
repo root by mistake.
- The anchor starter was described as having "a live React dashboard". It is
a CLI that writes an append-only JSON Lines audit log. Description now
matches the code.
- The footer hardcoded "● All systems operational" next to a Status link
that went nowhere. There is no uptime monitor behind it, so it read green
while the demo endpoint was returning 503. Removed rather than faked.
- The footer listed 2 of the published packages as unlinked plain text. Now
lists the 4 that are actually on npm, linked. `anchor-sdk` and
`orbital-indexer` are deliberately absent - they 404 on the registry, and
linking them would recreate the exact problem this commit fixes.
- The product is called Orbital, not "Orbit Stellar" - including in the
`<title>`, so the browser tab was wrong too.
- Twitter is dropped: no account exists, and an invented handle is worse
than an absent row.
`examples/next-starter` was untracked and unfinished - no page, no layout,
no README, and `lib/engine.ts` imported `./config.js`, which does not
resolve under `moduleResolution: "bundler"`, so it could never have built.
Completed and verified with a real `next build`.
* fix(web): replace the fabricated AI assistant and cap two open endpoints
The docs sidebar shipped an "AI Assistant" with no model behind it. It
greeted every visitor with "Ask me anything about the SDK, webhooks, or
real-time events", waited a hardcoded 1200ms to look like it was thinking,
then returned a canned template string with the question interpolated into
it. No "coming soon" label, mounted unconditionally, so it was live on every
/docs page - a developer evaluating the SDK asked a real question and got a
confident non-answer.
Rewritten as what it can honestly be: a docs search panel over the existing
/api/docs/search endpoint, using the same debounced fetch as SearchDialog.
Every line it shows is now a real section with a real link, and when nothing
matches it says so instead of inventing prose. Button relabelled AI ->
Search.
Two unmetered amplifiers on the same surface:
- /api/docs/search re-read, re-parsed and re-stripped every markdown file in
the corpus on every request, uncached, with no rate limit, on a route the
UI calls on a 200ms debounce. The content is build-time static, so it is
now parsed once per process. Query length is capped at 128 chars.
- /api/webhook-sample read an unbounded JSON body and HMAC-ed over an
unbounded caller-supplied secret. Body now capped at 4KB (413 past that),
secret at 256 chars, address at 56.
Also: clientIp() silently collapses every caller into a single "unknown"
bucket when neither x-vercel-forwarded-for nor TRUSTED_PROXY_HOPS is
available. Failing closed is right, but it turns perIpStreams: 1 into a
global limit - one SSE slot for the whole internet on a non-Vercel deploy.
Now warns once per process naming the fix.
* fix(pulse-core): stop dropping unified cursor writes, and de-flake two suites
`onCursor` fired `void this.persistUnifiedCursor(...)`, discarding the
promise. Nothing tracked it, so `stop()` could not wait for it and two writes
were never ordered against each other. Two consequences: a shutdown could
lose the newest cursor, and on a store whose writes complete out of order
(Postgres, Redis, the filesystem under load) the OLDER cursor could be the
one that survived. Either way the next start replays events that were
already delivered - which matters for a pipeline the anchor starter sells as
"audit-grade, replay-safe".
Writes are now chained, and `stop()` awaits the tail. The chain is null until
the first write is queued, deliberately: making `stop()` await
unconditionally deferred the teardown after it by a microtask and broke three
callers that read engine state immediately after stopping.
That also removes the race behind
EventEngine.unifiedCursorResume.test.ts, which slept 20ms after `stop()` and
hoped. It failed 2 of 3 local runs; now 3 of 3.
The two DeadLetterStore flakes had a different cause: unlike its sibling
pulse-webhooks.test.ts, that file never mocked `dns/promises`, so every
delivery attempt did a real DNS lookup of example.com - on real time, inside
`vi.useFakeTimers()`, where `vi.waitFor` could not wait for it. Mocked, as
the sibling already does.
examples/next-starter declared a `test` script with no tests, failing
`pnpm -r test`. Covered the config validation instead, which is the part
worth testing. Widened `loadConfig` to take the env shape it actually reads.
abi-registry's CLI-only config loader is annotated so bundlers stop trying to
trace a runtime path. This does NOT clear the "whole project was traced"
warning in apps/web - that needs the module moved behind its own subpath
export and dropped from index.ts, which is a public-API change, so the
comment says so rather than implying it is fixed.
---------
Co-authored-by: Salmatcre8 <118213044+Salmatcre8@users.noreply.github.com>1 parent ff53337 commit b972e76
35 files changed
Lines changed: 1341 additions & 382 deletions
File tree
- apps/web
- app
- api
- docs/search
- webhook-sample
- components
- docs
- lib
- examples
- anchor-starter
- next-starter
- app
- api/events/[address]
- lib
- test
- packages
- abi-registry/src
- pulse-core
- src
- test
- pulse-webhooks
- src
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
47 | | - | |
48 | | - | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
49 | 55 | | |
50 | | - | |
51 | | - | |
52 | | - | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
53 | 65 | | |
54 | | - | |
55 | | - | |
| 66 | + | |
| 67 | + | |
56 | 68 | | |
| 69 | + | |
57 | 70 | | |
58 | 71 | | |
59 | 72 | | |
| |||
63 | 76 | | |
64 | 77 | | |
65 | 78 | | |
66 | | - | |
67 | | - | |
68 | 79 | | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | 80 | | |
73 | | - | |
74 | | - | |
75 | | - | |
| 81 | + | |
76 | 82 | | |
77 | 83 | | |
78 | 84 | | |
79 | | - | |
80 | | - | |
81 | | - | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
82 | 88 | | |
83 | 89 | | |
84 | 90 | | |
85 | 91 | | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
86 | 132 | | |
87 | 133 | | |
88 | 134 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
40 | 48 | | |
41 | 49 | | |
42 | 50 | | |
43 | | - | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
44 | 62 | | |
45 | 63 | | |
46 | | - | |
| 64 | + | |
47 | 65 | | |
48 | 66 | | |
49 | | - | |
50 | | - | |
51 | | - | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
52 | 73 | | |
53 | 74 | | |
54 | 75 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | | - | |
| 16 | + | |
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
5 | 13 | | |
6 | 14 | | |
7 | 15 | | |
| |||
22 | 30 | | |
23 | 31 | | |
24 | 32 | | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
25 | 47 | | |
26 | 48 | | |
27 | 49 | | |
| |||
46 | 68 | | |
47 | 69 | | |
48 | 70 | | |
49 | | - | |
| 71 | + | |
50 | 72 | | |
51 | 73 | | |
52 | 74 | | |
| |||
69 | 91 | | |
70 | 92 | | |
71 | 93 | | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
77 | | - | |
78 | | - | |
79 | | - | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
85 | 101 | | |
86 | 102 | | |
87 | 103 | | |
88 | 104 | | |
89 | 105 | | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
| 106 | + | |
| 107 | + | |
100 | 108 | | |
101 | 109 | | |
102 | 110 | | |
103 | 111 | | |
104 | 112 | | |
105 | 113 | | |
106 | | - | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | | - | |
111 | | - | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
112 | 125 | | |
113 | 126 | | |
114 | 127 | | |
115 | 128 | | |
| 129 | + | |
116 | 130 | | |
117 | 131 | | |
| 132 | + | |
118 | 133 | | |
| 134 | + | |
| 135 | + | |
119 | 136 | | |
120 | | - | |
121 | | - | |
| 137 | + | |
| 138 | + | |
122 | 139 | | |
123 | 140 | | |
124 | 141 | | |
125 | 142 | | |
126 | 143 | | |
127 | 144 | | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
| 145 | + | |
| 146 | + | |
138 | 147 | | |
139 | 148 | | |
140 | 149 | | |
| |||
157 | 166 | | |
158 | 167 | | |
159 | 168 | | |
160 | | - | |
| 169 | + | |
161 | 170 | | |
162 | 171 | | |
163 | 172 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
6 | 8 | | |
7 | 9 | | |
8 | 10 | | |
| |||
85 | 87 | | |
86 | 88 | | |
87 | 89 | | |
88 | | - | |
| 90 | + | |
89 | 91 | | |
90 | 92 | | |
91 | 93 | | |
| |||
100 | 102 | | |
101 | 103 | | |
102 | 104 | | |
103 | | - | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
104 | 108 | | |
105 | 109 | | |
106 | 110 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
26 | | - | |
| 26 | + | |
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| |||
0 commit comments