From 70da526921f21cdd82aa2211f8595cc2bf89528e Mon Sep 17 00:00:00 2001 From: Dicken Date: Fri, 24 Jul 2026 17:26:37 +0200 Subject: [PATCH] round33: API-Scanner Kontrollprobe gegen Geraete, die jede Anfrage einheitlich beantworten (false positives behoben) --- apps/backend/src/scanner/apiDetector.ts | 40 +++++++++++++++++-------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/apps/backend/src/scanner/apiDetector.ts b/apps/backend/src/scanner/apiDetector.ts index 41b1ae7..c6a9f9e 100644 --- a/apps/backend/src/scanner/apiDetector.ts +++ b/apps/backend/src/scanner/apiDetector.ts @@ -106,32 +106,48 @@ function looksLikeJson(body: string): boolean { return trimmed.startsWith("{") || trimmed.startsWith("["); } +function isApiLikeResponse(result: RawProbeResult): boolean { + const contentTypeIsApi = + !!result.contentType && + (result.contentType.includes("json") || + result.contentType.includes("graphql") || + result.contentType.includes("xml")); + const bodyIsJson = looksLikeJson(result.bodySnippet); + const hasAuthChallenge = !!result.wwwAuthenticate; + return contentTypeIsApi || bodyIsJson || hasAuthChallenge; +} + /** * Prüft die kuratierten Kandidaten-Pfade unter einer Basis-URL parallel und * liefert alle, die auf eine tatsächlich vorhandene API hindeuten: * - eine JSON-Antwort (egal ob 200 oder z. B. 401 "unauthorized" - eine * JSON-Fehlermeldung zeigt trotzdem "hier läuft eine API"), * - ein Content-Type, der explizit auf JSON/GraphQL/XML-API hindeutet, - * - oder ein "WWW-Authenticate"-Header (401 mit diesem Header ist ein sehr - * starkes Signal für eine authentifizierungspflichtige API, selbst wenn - * der Body selbst nur eine schlichte Textmeldung ist). + * - oder ein "WWW-Authenticate"-Header (401 mit diesem Header ist normalerweise + * ein starkes Signal für eine authentifizierungspflichtige API). * Reine HTML-Seiten (z. B. eine 404-Fehlerseite des Frontends oder eine * Login-Weiterleitung ohne API-Signal) zählen nicht. + * + * VORAB ein Kontroll-Check gegen einen garantiert nicht existierenden Pfad: + * manche Geräte (z. B. IP-Kameras) beantworten JEDE Anfrage einheitlich mit + * z. B. Basic-Auth-Aufforderung oder demselben JSON-Fehlerobjekt - dann + * würden buchstäblich ALLE Kandidaten-Pfade fälschlich als "API gefunden" + * durchgehen (siehe Bugreport: eine Reolink-Türklingel "hatte" plötzlich + * Home-Assistant-, Proxmox- UND Portainer-APIs). Reagiert das Kontroll-Pfad + * genauso "positiv" wie ein echter Treffer, ist dem Gerät für diese Prüfung + * grundsätzlich nicht zu trauen - dann lieber gar kein Ergebnis als 30 falsche. */ export async function detectApis(baseUrl: string): Promise { + const control = await fetchRaw(`${baseUrl}/__launchpad_api_probe_${Date.now()}__`); + if (control && control.status !== 404 && control.status !== 0 && isApiLikeResponse(control)) { + return []; + } + const checks = await Promise.all( CANDIDATE_PATHS.map(async ({ path, type }) => { const result = await fetchRaw(`${baseUrl}${path}`); if (!result || result.status === 0 || result.status === 404) return null; - - const contentTypeIsApi = - result.contentType?.includes("json") || - result.contentType?.includes("graphql") || - result.contentType?.includes("xml"); - const bodyIsJson = looksLikeJson(result.bodySnippet); - const hasAuthChallenge = !!result.wwwAuthenticate; - - if (!contentTypeIsApi && !bodyIsJson && !hasAuthChallenge) return null; + if (!isApiLikeResponse(result)) return null; const detected: DetectedApi = { path, type, status: result.status }; return detected;