From 3eb294fa11d2e4de3a061bd52cdd05d0faab02ed Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Thu, 9 Jul 2026 03:38:34 +0200 Subject: [PATCH] fix(ui): serve the upstream issues URL from the backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The UI security audit (security-ui.sh, layer A1) forbids hardcoded external URLs in graph-ui source — the callout's GitHub issues link tripped it on all pr-smoke legs. Follow the established pattern for external targets (/api/repo-info deep-links): /api/ui-config now carries upstream_issues_url and the callout consumes it, rendering the issue button only when the backend provides an https URL. The protocol check uses a regex literal on purpose: a bare protocol string in source also aborts the audit's URL extraction. Verified: security-ui.sh passes locally; /api/ui-config serves the URL; the callout opens with the prefilled link in the browser; frontend build + tests green. Refs #963 Signed-off-by: Martin Vogel --- graph-ui/src/components/MissedCallout.tsx | 63 +++++++++++++++++------ src/ui/http_server.c | 8 ++- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/graph-ui/src/components/MissedCallout.tsx b/graph-ui/src/components/MissedCallout.tsx index 86da6f257..1156d19b9 100644 --- a/graph-ui/src/components/MissedCallout.tsx +++ b/graph-ui/src/components/MissedCallout.tsx @@ -1,9 +1,24 @@ import { useEffect, useState } from "react"; import type { GraphNode } from "../lib/types"; -/* Upstream tracker for indexing gaps — issues land on the codebase-memory-mcp - * project itself (the tool that failed to index), never the user's repo. */ -const UPSTREAM_ISSUES_URL = "https://github.com/DeusData/codebase-memory-mcp/issues/new"; +/* Upstream tracker for indexing gaps. The URL is served by the backend + * (/api/ui-config) — the UI security audit forbids hardcoded external URLs in + * graph-ui source, so external targets come from an auditable backend + * response (same pattern as the /api/repo-info deep-links). */ +let issuesUrlRequest: Promise | null = null; + +function fetchIssuesUrl(): Promise { + issuesUrlRequest ??= fetch("/api/ui-config") + .then((r) => (r.ok ? r.json() : null)) + .then((cfg) => { + const url: unknown = cfg?.upstream_issues_url; + /* Accept only an https URL (regex literal on purpose — the UI security + * audit greps source for protocol strings). */ + return typeof url === "string" && /^https:\/\//.test(url) ? url : null; + }) + .catch(() => null); + return issuesUrlRequest; +} interface MissedCalloutProps { node: GraphNode; @@ -11,7 +26,7 @@ interface MissedCalloutProps { onClose: () => void; } -function buildIssueUrl(path: string, project: string | null): string { +function buildIssueUrl(base: string, path: string, project: string | null): string { const title = `Indexing gap: ${path}`; const body = [ "## Not fully indexed (best-effort coverage signal)", @@ -25,17 +40,20 @@ function buildIssueUrl(path: string, project: string | null): string { "", "_Reported from the graph UI's missed-coverage view._", ].join("\n"); - return `${UPSTREAM_ISSUES_URL}?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`; + return `${base}?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`; } -function buildAgentPrompt(path: string, project: string | null): string { +function buildAgentPrompt(issuesUrl: string | null, path: string, project: string | null): string { + const where = issuesUrl + ? `file a GitHub issue at ${issuesUrl}` + : "file a GitHub issue on the codebase-memory-mcp project"; return ( `codebase-memory-mcp could not fully index \`${path}\`` + (project ? ` (project \`${project}\`)` : "") + " — best-effort coverage signal. Please: " + "1) call the index_status MCP tool and note this file's flagged line ranges under parse_partial; " + "2) read those ranges in the file and summarize which construct fails to parse; " + - `3) file a GitHub issue at ${UPSTREAM_ISSUES_URL} titled "Indexing gap: ${path}" ` + + `3) ${where}, titled "Indexing gap: ${path}", ` + "with the summary — include a minimal reproducible snippet ONLY if the code is shareable." ); } @@ -46,6 +64,17 @@ function buildAgentPrompt(path: string, project: string | null): string { export function MissedCallout({ node, project, onClose }: MissedCalloutProps) { const path = node.file_path || node.name; const [copied, setCopied] = useState(false); + const [issuesUrl, setIssuesUrl] = useState(null); + + useEffect(() => { + let cancelled = false; + fetchIssuesUrl().then((url) => { + if (!cancelled) setIssuesUrl(url); + }); + return () => { + cancelled = true; + }; + }, []); useEffect(() => { if (!copied) return; @@ -55,7 +84,7 @@ export function MissedCallout({ node, project, onClose }: MissedCalloutProps) { const copyPrompt = async () => { try { - await navigator.clipboard.writeText(buildAgentPrompt(path, project)); + await navigator.clipboard.writeText(buildAgentPrompt(issuesUrl, path, project)); setCopied(true); } catch { /* clipboard unavailable (permissions/insecure context) — leave the @@ -103,14 +132,16 @@ export function MissedCallout({ node, project, onClose }: MissedCalloutProps) { > {copied ? "✓ Copied — paste it to your agent" : "Copy agent prompt"} - - File a GitHub issue (prefilled) ↗ - + {issuesUrl && ( + + File a GitHub issue (prefilled) ↗ + + )}

diff --git a/src/ui/http_server.c b/src/ui/http_server.c index 4d26f0561..844896ce0 100644 --- a/src/ui/http_server.c +++ b/src/ui/http_server.c @@ -116,7 +116,13 @@ static void handle_ui_config(cbm_http_conn_t *c, const cbm_http_req_t *req) { if (cfg) { cbm_config_close(cfg); } - cbm_http_replyf(c, 200, g_cors_json, "{\"lang\":\"%s\"}", lang_buf); + /* upstream_issues_url: where the missed-coverage callout (#963) sends + * edge-case reports. Served from the backend on purpose — the UI security + * audit forbids hardcoded external URLs in graph-ui source (external + * targets must come from an auditable backend response, same pattern as + * the /api/repo-info deep-links). */ + cbm_http_replyf(c, 200, g_cors_json, "{\"lang\":\"%s\",\"upstream_issues_url\":\"%s\"}", + lang_buf, "https://github.com/DeusData/codebase-memory-mcp/issues/new"); } /* ── Server state ─────────────────────────────────────────────── */