Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@
**Vulnerability:** XSS risk via unsanitized `<` characters in `JSON.stringify` output injected into `<script type="application/ld+json">`.
**Learning:** `JSON.stringify()` does not automatically escape `<` as `\u003c`. If dynamic or unsanitized content is serialized into a `<script>` tag via `dangerouslySetInnerHTML`, an attacker can include `</script>` to break out of the context and inject malicious scripts.
**Prevention:** Always replace `<` with `\u003c` when injecting JSON output into script tags, e.g., `JSON.stringify(data).replace(/</g, '\\u003c')`.
## 2025-06-06 - Missing timeouts on external fetch calls
**Vulnerability:** Denial of Service (DoS) and hanging requests due to external HTTP calls lacking timeouts.
**Learning:** The native `fetch` API in Node.js/browser environments does not time out by default. If external APIs (like GitHub or Dexscreener) fail to respond or respond slowly, requests hang indefinitely, leading to resource exhaustion or blocked event loops.
**Prevention:** Always include an explicit timeout in `fetch` calls using `signal: AbortSignal.timeout(TIMEOUT_MS)` to ensure network requests fail gracefully if a timely response is not received.
2 changes: 2 additions & 0 deletions scripts/sync-sources.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ async function fetchGithubJson(url, { optional = false } = {}) {
"X-GitHub-Api-Version": "2022-11-28",
...(githubToken ? { Authorization: `Bearer ${githubToken}` } : {}),
},
signal: AbortSignal.timeout(10000),
});

if ((response.status === 404 || response.status === 409) && optional) {
Expand All @@ -53,6 +54,7 @@ async function fetchGithubText(url, { optional = false } = {}) {
"X-GitHub-Api-Version": "2022-11-28",
...(githubToken ? { Authorization: `Bearer ${githubToken}` } : {}),
},
signal: AbortSignal.timeout(10000),
});

if ((response.status === 404 || response.status === 409) && optional) {
Expand Down
1 change: 1 addition & 0 deletions src/lib/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export async function getNetworkStatus(): Promise<NetworkStatus> {
headers: {
Accept: "application/json",
},
signal: AbortSignal.timeout(NODE_PROBE_TIMEOUT_MS),
});

if (!response.ok) {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/snap-market.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SNAP, correctedSnapFdv } from "@/lib/snap";

export const SNAP_MARKET_REVALIDATE = 30;
const MARKET_FETCH_TIMEOUT_MS = 10_000;

type DexPeriod = {
buys?: number;
Expand Down Expand Up @@ -79,6 +80,7 @@ export async function getSnapMarketData(): Promise<SnapMarketResponse> {
"user-agent": "hypersnap.org market data checker",
},
next: { revalidate: SNAP_MARKET_REVALIDATE },
signal: AbortSignal.timeout(MARKET_FETCH_TIMEOUT_MS),
});

if (!response.ok) {
Expand Down
Loading