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
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@
**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 Fetch Timeouts
**Vulnerability:** External fetch calls hanging indefinitely.
**Learning:** The native `fetch` API in Node.js does not have a default timeout, leaving the application vulnerable to server resource exhaustion if an external service stops responding.
**Prevention:** Always use `signal: AbortSignal.timeout(TIMEOUT_MS)` when calling external APIs using `fetch`.
3 changes: 3 additions & 0 deletions scripts/sync-sources.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const SOURCE_REPO_ORDER = ["hypersnap", "hypersnap-docs-web", "snap", "protocol"

const DOCS_LINK_LIMIT = 80;
const README_HEADING_LIMIT = 10;
const FETCH_TIMEOUT_MS = 10_000;

const githubToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;

Expand All @@ -32,6 +33,7 @@ async function fetchGithubJson(url, { optional = false } = {}) {
"X-GitHub-Api-Version": "2022-11-28",
...(githubToken ? { Authorization: `Bearer ${githubToken}` } : {}),
},
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
});

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

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),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep network status fetches memoized

On the server-rendered home and /network pages, NetworkStatusGrid calls getNetworkStatus() and getNodeHealthStatuses() in the same render, and the first known node probes the same /v1/info URL; /network also calls getNetworkStatus() before rendering the grid. Passing a fresh AbortSignal opts these GETs out of Next's request memoization, so a cache miss now sends duplicate public-node requests per render instead of sharing one. Keep the timeout in a shared/cached helper or reuse the first public-node result so the hardening doesn't increase upstream load.

Useful? React with πŸ‘Β / πŸ‘Ž.

});

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;
export 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