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 Fetch Timeouts
**Vulnerability:** External `fetch` calls without timeouts could lead to server resource exhaustion and Denial of Service (DoS) due to hanging connections.
**Learning:** The native `fetch` API lacks a default timeout, leaving applications vulnerable if external APIs respond slowly or become unresponsive.
**Prevention:** Always add an explicit timeout using `signal: AbortSignal.timeout(TIMEOUT_MS)` to all external `fetch` calls.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint": "^9.39.4",
"eslint-config-next": "16.2.5",
"github-slugger": "^2.0.0",
"mdast-util-to-string": "^4.0.0",
Expand Down
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 = 15000;

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Optional GitHub fetches abort sync

Medium Severity

Adding AbortSignal.timeout to fetchGithubJson and fetchGithubText makes timeouts throw before optional handling runs. Callers that pass optional: true only degrade on HTTP 404/409; a slow optional doc, tree, or release fetch now fails the entire sync-sources run instead of continuing with empty or partial data like missing resources do.

Additional Locations (1)
Fix in CursorΒ Fix in Web

Reviewed by Cursor Bugbot for commit 43e213a. Configure here.

});

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

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_DATA_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_DATA_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 Add a timeout that survives revalidation

After this request has a cached response and the 30s next.revalidate interval expires, Next performs the refresh as a stale revalidation; in Next 16.2.5 the patched fetch intentionally omits init.signal for stale revalidations before calling the origin. This new AbortSignal.timeout therefore only covers cold cache misses, while the production background refresh to Dexscreener can still hang indefinitely and keep the pending revalidation/socket around. Use a timeout path that survives revalidation, or opt out of Next's fetch cache, before relying on this as the DoS mitigation.

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

});

if (!response.ok) {
Expand Down
Loading