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 Timeout Configurations on External Fetch Calls
**Vulnerability:** Resource exhaustion / Denial of Service (DoS) risk due to missing timeouts on external API fetch calls.
**Learning:** The native `fetch` API does not implement a default timeout, meaning requests to unresponsive external servers (like GitHub API or Dexscreener) can hang indefinitely, exhausting server connections and resources in Next.js API routes or sync scripts.
**Prevention:** Always add explicit timeouts to `fetch` requests, especially when calling external unmanaged resources. Use `AbortSignal.timeout(TIMEOUT_MS)` to enforce time limits and fail securely.
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 GITHUB_FETCH_TIMEOUT_MS = 30000;

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(GITHUB_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(GITHUB_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 SNAP_MARKET_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(SNAP_MARKET_TIMEOUT_MS),
});

if (!response.ok) {
Expand Down
Loading