Ship
Score 100/100 ·
3 findings
If this went live
Semantic pass skipped — no plain-English narrative for this scan.
Fix these first
- medium dangerouslySetInnerHTML renders a non-literal value as raw HTML. If the value is ever user-influenced this is XSS.
web/src/app/page.tsx:86-89
- low Empty catch block silently swallows errors. Failures (including security failures) disappear without a trace.
web/src/app/admin/route.ts:48-67
- low Empty catch block silently swallows errors. Failures (including security failures) disappear without a trace.
web/src/lib/analytics.ts:49-53
Category breakdown
| Category | Findings | Worst severity |
| Auth & access | 0 | — |
| Secrets | 0 | — |
| Data flow | 0 | — |
| Dependencies | 0 | — |
| Missing basics | 0 | — |
| AI smells | 3 | medium |
All findings (3)
medium
1. dangerouslySetInnerHTML renders a non-literal value as raw HTML. If the value is ever user-influenced this is XSS.
web/src/app/page.tsx:86-89
AI smells · confidence 0.60 · semgrep (ai.smell.dangerously-set-inner-html)
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(JSON_LD).replace(/</g, "\\u003c") }}
Fix: Render text via JSX (auto-escaped), or sanitise the HTML with DOMPurify before passing it in.
low
2. Empty catch block silently swallows errors. Failures (including security failures) disappear without a trace.
web/src/app/admin/route.ts:48-67
AI smells · confidence 0.80 · semgrep (ai.smell.empty-catch)
try {
const raw = JSON.parse(await fs.readFile(path.join(dir, name), "utf8")) as {
scan_id?: string;
Fix: Handle the error or at minimum log it; rethrow anything you cannot handle.
low
3. Empty catch block silently swallows errors. Failures (including security failures) disappear without a trace.
web/src/lib/analytics.ts:49-53
AI smells · confidence 0.80 · semgrep (ai.smell.empty-catch)
try {
umami?.track(event, data);
} catch {
Fix: Handle the error or at minimum log it; rethrow anything you cannot handle.