← back to Ventura Corridor
fix: 3 bugs caught by codex-2way audit at tick 73
1229e88dfe17393fb57089054d1cbd37d378779d · 2026-05-08 00:28:29 -0700 · SteveStudio2
PROSECUTOR (Kimi K2.5 cloud) audit on the 17-commit batch since
tick 49 found three real issues. All fixed:
1. public/index.html:353 — fleet panel ternary "escapeHtml ? escapeHtml(...)"
was unnecessary but legal (escapeHtml is defined at line 204). Removed
the dead ternary; also wraps m.label since it comes from network so
defense in depth. PROSECUTOR misread this as undefined-fn but flagged
the right surface area to harden.
2. src/server/index.ts:541-545 — RSS feed vertical filter was concatenating
user input into ILIKE pattern without escaping % or _ wildcards. So
?vertical=% would match every category. Real injection but low impact
(read-only, no auth bypass). Now escapes \%, \_, \\ in tokens before
the ILIKE assembly.
3. public/news.html:200 — Hot-only toggle stored Number(r.id) in Set but
filtered with String r.business_id — PG bigint always serializes as
string, so Set.has missed every row. Set.has now coerces both sides
to String. Also slices leaderboard rows to 1000 max as a tab-OOM guard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
M public/index.htmlM public/news.htmlM src/server/index.ts
Diff
commit 1229e88dfe17393fb57089054d1cbd37d378779d
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Fri May 8 00:28:29 2026 -0700
fix: 3 bugs caught by codex-2way audit at tick 73
PROSECUTOR (Kimi K2.5 cloud) audit on the 17-commit batch since
tick 49 found three real issues. All fixed:
1. public/index.html:353 — fleet panel ternary "escapeHtml ? escapeHtml(...)"
was unnecessary but legal (escapeHtml is defined at line 204). Removed
the dead ternary; also wraps m.label since it comes from network so
defense in depth. PROSECUTOR misread this as undefined-fn but flagged
the right surface area to harden.
2. src/server/index.ts:541-545 — RSS feed vertical filter was concatenating
user input into ILIKE pattern without escaping % or _ wildcards. So
?vertical=% would match every category. Real injection but low impact
(read-only, no auth bypass). Now escapes \%, \_, \\ in tokens before
the ILIKE assembly.
3. public/news.html:200 — Hot-only toggle stored Number(r.id) in Set but
filtered with String r.business_id — PG bigint always serializes as
string, so Set.has missed every row. Set.has now coerces both sides
to String. Also slices leaderboard rows to 1000 max as a tab-OOM guard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
public/index.html | 2 +-
public/news.html | 6 ++++--
src/server/index.ts | 7 ++++++-
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/public/index.html b/public/index.html
index aceafd0..9cdc12b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -443,7 +443,7 @@ async function loadBuildStatus() {
const modelStr = model ? `${model.name} ${model.pinned ? '⚓' : ''}` : 'idle';
const cpu = m.cpu.toFixed(0), ram = m.ram_pct.toFixed(0);
const dashUrl = port === '100.94.103.98:9930' ? 'http://100.94.103.98:9930' : 'http://localhost:9931';
- return `<a href="${dashUrl}" target="_blank" style="color:inherit;text-decoration:none"><span style="color:var(--metal)">${m.label}</span> · <span style="color:var(--ink)">${escapeHtml ? escapeHtml(modelStr) : modelStr}</span> · cpu <b style="color:var(--metal-glow)">${cpu}%</b> · ram <b style="color:var(--metal-glow)">${ram}%</b></a>`;
+ return `<a href="${dashUrl}" target="_blank" style="color:inherit;text-decoration:none"><span style="color:var(--metal)">${escapeHtml(m.label)}</span> · <span style="color:var(--ink)">${escapeHtml(modelStr)}</span> · cpu <b style="color:var(--metal-glow)">${cpu}%</b> · ram <b style="color:var(--metal-glow)">${ram}%</b></a>`;
};
const m1El = document.getElementById('fleet-mac1');
const m2El = document.getElementById('fleet-mac2');
diff --git a/public/news.html b/public/news.html
index c2ff8fe..81bc918 100644
--- a/public/news.html
+++ b/public/news.html
@@ -124,7 +124,7 @@ function render() {
rows = rows.filter(r => (r.category || '').toLowerCase().startsWith(vertical.toLowerCase()));
}
if (HOT_ONLY && HOT_BIZ_IDS) {
- rows = rows.filter(r => HOT_BIZ_IDS.has(r.business_id));
+ rows = rows.filter(r => HOT_BIZ_IDS.has(String(r.business_id)));
}
rows = rows.slice();
if (sort === 'biz') {
@@ -204,7 +204,9 @@ document.getElementById('hot-only').addEventListener('click', async () => {
if (!HOT_BIZ_IDS) {
try {
const lb = await fetch('/api/news/leaderboard').then(r => r.json());
- HOT_BIZ_IDS = new Set((lb.rows || []).filter(r => Number(r.fresh_7d) >= 3).map(r => Number(r.id)));
+ // PG bigint serializes as string; coerce both sides so Set.has matches
+ // (caught by codex-2way audit, tick 73)
+ HOT_BIZ_IDS = new Set(((lb.rows || []).slice(0, 1000)).filter(r => Number(r.fresh_7d) >= 3).map(r => String(r.id)));
} catch { HOT_BIZ_IDS = new Set(); }
}
} else {
diff --git a/src/server/index.ts b/src/server/index.ts
index 90e6684..70fe73c 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -5483,8 +5483,13 @@ app.get('/news/feed.xml', async (req, res) => {
const businessId = parseInt(String(req.query.business || ''), 10);
const params: any[] = [];
const conds: string[] = [];
+ // Escape ILIKE wildcards (% and _) in user input so ?vertical=% can't match
+ // every category. Caught by codex-2way audit (SQL wildcard injection,
+ // tick 73). Backslash-escape, then add the literal "%" suffix only on
+ // top-cat single-token form.
+ const sqlEscape = (s: string) => s.replace(/[\\%_]/g, '\\$&');
if (vertical) {
- const tokens = vertical.split(/[:\-_\s]+/).filter(Boolean);
+ const tokens = vertical.split(/[:\-_\s]+/).filter(Boolean).map(sqlEscape);
if (tokens.length === 1) {
params.push(tokens[0] + ': %');
conds.push(`b.category ILIKE $${params.length}`);
← 7d1fe85 feat(coverage): rejected-ideas drawer below the idea-loop fe
·
back to Ventura Corridor
·
fix: portable home dir in /api/ideas/{recent,rejects} (caugh 3853840 →