{"version": "2.1.0", "$schema": "https://json.schemastore.org/sarif-2.1.0.json", "runs": [{"tool": {"driver": {"name": "Repobility", "informationUri": "https://repobility.com", "rules": [{"id": "scanner-8ec34e6be3d278ed", "name": "Stray `console.log` in TS/JS \u2014 backend/src/index.ts:65", "shortDescription": {"text": "Stray `console.log` in TS/JS \u2014 backend/src/index.ts:65"}, "fullDescription": {"text": "Replace with the toast helper, an error boundary, or remove. `console.warn` / `console.error` are acceptable.\n\nWhy: Hygiene \u2014 easy to leak debug output.\nRule id: fq.console-leak"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "low", "confidence": 1.0}}, {"id": "scanner-653303ec0037c52d", "name": "Stray `console.log` in TS/JS \u2014 backend/src/services/photosService.ts:290", "shortDescription": {"text": "Stray `console.log` in TS/JS \u2014 backend/src/services/photosService.ts:290"}, "fullDescription": {"text": "Replace with the toast helper, an error boundary, or remove. `console.warn` / `console.error` are acceptable.\n\nWhy: Hygiene \u2014 easy to leak debug output.\nRule id: fq.console-leak"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "low", "confidence": 1.0}}, {"id": "scanner-5943cedb2cf993c7", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/fridge/FridgeLayout.tsx:119", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/fridge/FridgeLayout.tsx:119"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-0961b8f1f6e3ecf7", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/observatory/ObservatoryLayout.tsx:362", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/observatory/ObservatoryLayout.tsx:362"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-4ee17b0380b9a716", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/flux/FluxLayout.tsx:103", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/flux/FluxLayout.tsx:103"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-5cabf30e5cf134c9", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/classic/ClassicLayout.tsx:312", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/classic/ClassicLayout.tsx:312"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-dd4f412ae5f862a6", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/terminal/TerminalLayout.tsx:235", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/terminal/TerminalLayout.tsx:235"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-e2b41fc3b85838f1", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/SplitFlapBoard.tsx:232", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/SplitFlapBoard.tsx:232"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-e7de72caca8a9ece", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/CalendarGrid.tsx:101", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/CalendarGrid.tsx:101"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-f4ac6ed8d1971208", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/AgendaList.tsx:82", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/AgendaList.tsx:82"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-bfa99e795caa85ea", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/PlexWidget.tsx:26", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/PlexWidget.tsx:26"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-eeab7722765cf53d", "name": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/MediaWidget.tsx:138", "shortDescription": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/MediaWidget.tsx:138"}, "fullDescription": {"text": "A truncated value should reveal the full text on hover. Pass the full string via `title={...}` so the user can read it.\n\nWhy: P2 in CHECKLIST.md \u2014 truncate without hover-reveal.\nRule id: fq.truncate.no-title"}, "properties": {"scanner": "scanner-primary", "layer": "frontend", "severity": "info", "confidence": 1.0}}, {"id": "scanner-1f66ad88286ca30a", "name": "Dockerfile runs as root: backend/Dockerfile", "shortDescription": {"text": "Dockerfile runs as root: backend/Dockerfile"}, "fullDescription": {"text": "No non-root USER set. Containers running as root expand the blast radius of any vulnerability inside the image."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-3d2a6283f9ebab24", "name": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine", "shortDescription": {"text": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine"}, "fullDescription": {"text": "Container tags can be retagged upstream. Pin production base images to a reviewed digest (`image@sha256:...`) when reproducibility and supply-chain integrity matter."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "low", "confidence": 1.0}}, {"id": "scanner-60427b03771411b6", "name": "Dockerfile runs as root: frontend/Dockerfile", "shortDescription": {"text": "Dockerfile runs as root: frontend/Dockerfile"}, "fullDescription": {"text": "No non-root USER set. Containers running as root expand the blast radius of any vulnerability inside the image."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-faa134129e5545ff", "name": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine", "shortDescription": {"text": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine"}, "fullDescription": {"text": "Container tags can be retagged upstream. Pin production base images to a reviewed digest (`image@sha256:...`) when reproducibility and supply-chain integrity matter."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "low", "confidence": 1.0}}, {"id": "scanner-b048d166901fd868", "name": "Docker base image is tag-pinned but not digest-pinned: nginx:alpine", "shortDescription": {"text": "Docker base image is tag-pinned but not digest-pinned: nginx:alpine"}, "fullDescription": {"text": "Container tags can be retagged upstream. Pin production base images to a reviewed digest (`image@sha256:...`) when reproducibility and supply-chain integrity matter."}, "properties": {"scanner": "scanner-primary", "layer": "hardware", "severity": "low", "confidence": 1.0}}, {"id": "scanner-404056134ac94166", "name": "Insecure pattern 'cors_wildcard' in backend/src/index.ts:26", "shortDescription": {"text": "Insecure pattern 'cors_wildcard' in backend/src/index.ts:26"}, "fullDescription": {"text": "Found a known-risky pattern (cors_wildcard). Review and replace if possible."}, "properties": {"scanner": "scanner-primary", "layer": "security", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-68bae445c630381a", "name": "Insecure pattern 'direct_innerhtml_assignment' in backend/src/routes/admin.ts:509", "shortDescription": {"text": "Insecure pattern 'direct_innerhtml_assignment' in backend/src/routes/admin.ts:509"}, "fullDescription": {"text": "Found a known-risky pattern (direct_innerhtml_assignment). Review and replace if possible."}, "properties": {"scanner": "scanner-primary", "layer": "security", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-6372cebde0220094", "name": "No auth library detected", "shortDescription": {"text": "No auth library detected"}, "fullDescription": {"text": "The scanner did not find any standard auth library (JWT, OAuth, NextAuth, Auth0, etc.). The repo has auth/admin/session surface indicators, so auth may live in custom code, in a separate service, or be missing."}, "properties": {"scanner": "scanner-primary", "layer": "security", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-27924aa79fa4a517", "name": "GitHub Action is tag-pinned rather than SHA-pinned", "shortDescription": {"text": "GitHub Action is tag-pinned rather than SHA-pinned"}, "fullDescription": {"text": "schneegans/dynamic-badges-action@v1.7.0 can move without a code change in this repo. Pin third-party actions to a reviewed 40-character commit SHA."}, "properties": {"scanner": "scanner-primary", "layer": "cicd", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-3ab5d313dda8e5f9", "name": "Debug logging residue appears in source files", "shortDescription": {"text": "Debug logging residue appears in source files"}, "fullDescription": {"text": "Found 12 console/debugger/print-style debug statements in non-test source. This is a common fast-generation residue before production cleanup."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "low", "confidence": 1.0}}, {"id": "scanner-2d0c7b7ab8f8aacf", "name": "Critical user flow still appears backed by mock or placeholder data", "shortDescription": {"text": "Critical user flow still appears backed by mock or placeholder data"}, "fullDescription": {"text": "A payment/auth/admin/order/billing-style flow contains mock, fake, TODO, dummy, or placeholder markers in runtime source. In the Fable corpus this is a high-leverage completeness smell: the app can look finished while the money, identity, or tenant flow is still scaffolded."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-8a9b63458cfd1515", "name": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/piholeService.ts:70", "shortDescription": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/piholeService.ts:70"}, "fullDescription": {"text": "Bare `fetch(...)` will throw an unhandled rejection on network failure. Wrap in try/catch, attach a `.catch(...)`, or pass an AbortSignal with a timeout."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-7c1fa188abd1d96a", "name": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/calendarService.ts:51", "shortDescription": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/calendarService.ts:51"}, "fullDescription": {"text": "Bare `fetch(...)` will throw an unhandled rejection on network failure. Wrap in try/catch, attach a `.catch(...)`, or pass an AbortSignal with a timeout."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-68060e8c57419b4f", "name": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/weatherService.ts:50", "shortDescription": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/weatherService.ts:50"}, "fullDescription": {"text": "Bare `fetch(...)` will throw an unhandled rejection on network failure. Wrap in try/catch, attach a `.catch(...)`, or pass an AbortSignal with a timeout."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-823204e17b4947f2", "name": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/mediaService.ts:62", "shortDescription": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/mediaService.ts:62"}, "fullDescription": {"text": "Bare `fetch(...)` will throw an unhandled rejection on network failure. Wrap in try/catch, attach a `.catch(...)`, or pass an AbortSignal with a timeout."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-b124f2a59829fca0", "name": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/radarService.ts:46", "shortDescription": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/radarService.ts:46"}, "fullDescription": {"text": "Bare `fetch(...)` will throw an unhandled rejection on network failure. Wrap in try/catch, attach a `.catch(...)`, or pass an AbortSignal with a timeout."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "medium", "confidence": 1.0}}, {"id": "scanner-2eafc3188c36bb02", "name": "3 env vars used in code but missing from .env.example", "shortDescription": {"text": "3 env vars used in code but missing from .env.example"}, "fullDescription": {"text": "Drift between code and config docs. The first few: `DATA_DIR`, `PHOTOS_CACHE_DIR`, `PIHOLE_SESSION_DIR`. Add them (with a placeholder/comment) to .env.example so onboarding doesn't break."}, "properties": {"scanner": "scanner-primary", "layer": "quality", "severity": "info", "confidence": 1.0}}, {"id": "scanner-ff6ab815e4ed89c0", "name": "Dangling fetch: GET https://api.rainviewer.com/public/weather-maps.json (backend/src/services/radarService.ts:46)", "shortDescription": {"text": "Dangling fetch: GET https://api.rainviewer.com/public/weather-maps.json (backend/src/services/radarService.ts:46)"}, "fullDescription": {"text": "`backend/src/services/radarService.ts:46` calls `GET https://api.rainviewer.com/public/weather-maps.json` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: fetch\nNormalized path used for matching: `/https:/api.rainviewer.com/public/weather-maps.json`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-6fcd64a2d95c8351", "name": "Dangling fetch: GET /admin/theme (frontend/src/App.tsx:24)", "shortDescription": {"text": "Dangling fetch: GET /admin/theme (frontend/src/App.tsx:24)"}, "fullDescription": {"text": "`frontend/src/App.tsx:24` calls `GET /admin/theme` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: fetch\nNormalized path used for matching: `/admin/theme`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-d01ef4116c438895", "name": "Dangling fetch: GET /api/weather/radar (frontend/src/hooks/useRadar.ts:15)", "shortDescription": {"text": "Dangling fetch: GET /api/weather/radar (frontend/src/hooks/useRadar.ts:15)"}, "fullDescription": {"text": "`frontend/src/hooks/useRadar.ts:15` calls `GET /api/weather/radar` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: fetch\nNormalized path used for matching: `/weather/radar`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-a640928719c8ab99", "name": "Dangling fetch: GET /api/plex (frontend/src/hooks/usePlex.ts:19)", "shortDescription": {"text": "Dangling fetch: GET /api/plex (frontend/src/hooks/usePlex.ts:19)"}, "fullDescription": {"text": "`frontend/src/hooks/usePlex.ts:19` calls `GET /api/plex` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: fetch\nNormalized path used for matching: `/plex`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-1e842da6f53ca284", "name": "Dangling fetch: GET /api/calendar (frontend/src/hooks/useCalendar.ts:16)", "shortDescription": {"text": "Dangling fetch: GET /api/calendar (frontend/src/hooks/useCalendar.ts:16)"}, "fullDescription": {"text": "`frontend/src/hooks/useCalendar.ts:16` calls `GET /api/calendar` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: fetch\nNormalized path used for matching: `/calendar`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-bd06340139d1d316", "name": "Dangling fetch: GET /api/weather (frontend/src/hooks/useWeather.ts:16)", "shortDescription": {"text": "Dangling fetch: GET /api/weather (frontend/src/hooks/useWeather.ts:16)"}, "fullDescription": {"text": "`frontend/src/hooks/useWeather.ts:16` calls `GET /api/weather` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: fetch\nNormalized path used for matching: `/weather`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-8b06f19fccff31fd", "name": "Dangling fetch: GET /admin/theme (frontend/src/hooks/useDayNight.ts:16)", "shortDescription": {"text": "Dangling fetch: GET /admin/theme (frontend/src/hooks/useDayNight.ts:16)"}, "fullDescription": {"text": "`frontend/src/hooks/useDayNight.ts:16` calls `GET /admin/theme` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: fetch\nNormalized path used for matching: `/admin/theme`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-e10815d3d7ab6249", "name": "Dangling fetch: GET /api/pihole (frontend/src/hooks/usePihole.ts:34)", "shortDescription": {"text": "Dangling fetch: GET /api/pihole (frontend/src/hooks/usePihole.ts:34)"}, "fullDescription": {"text": "`frontend/src/hooks/usePihole.ts:34` calls `GET /api/pihole` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: fetch\nNormalized path used for matching: `/pihole`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-f160b899f3864540", "name": "Dangling fetch: GET /api/media (frontend/src/hooks/useMedia.ts:16)", "shortDescription": {"text": "Dangling fetch: GET /api/media (frontend/src/hooks/useMedia.ts:16)"}, "fullDescription": {"text": "`frontend/src/hooks/useMedia.ts:16` calls `GET /api/media` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: fetch\nNormalized path used for matching: `/media`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-8d253e133ad8934b", "name": "Dangling fetch: GET /api/photos (frontend/src/hooks/usePhotos.ts:16)", "shortDescription": {"text": "Dangling fetch: GET /api/photos (frontend/src/hooks/usePhotos.ts:16)"}, "fullDescription": {"text": "`frontend/src/hooks/usePhotos.ts:16` calls `GET /api/photos` but no backend route matches that path. This is a runtime 404 waiting to happen.\n\nTool: fetch\nNormalized path used for matching: `/photos`\nIf this points at an external API, prefix it with `https://` so the matcher skips it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "high", "confidence": 1.0}}, {"id": "scanner-83e27b789830003c", "name": "Unused endpoint: USE /api/weather", "shortDescription": {"text": "Unused endpoint: USE /api/weather"}, "fullDescription": {"text": "`backend/src/index.ts` declares `USE /api/weather` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-aebdad2655c302c7", "name": "Unused endpoint: USE /api/calendar", "shortDescription": {"text": "Unused endpoint: USE /api/calendar"}, "fullDescription": {"text": "`backend/src/index.ts` declares `USE /api/calendar` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-4f80f23801d6a038", "name": "Unused endpoint: USE /api/plex", "shortDescription": {"text": "Unused endpoint: USE /api/plex"}, "fullDescription": {"text": "`backend/src/index.ts` declares `USE /api/plex` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-6a3460d428c799fa", "name": "Unused endpoint: USE /api/photos", "shortDescription": {"text": "Unused endpoint: USE /api/photos"}, "fullDescription": {"text": "`backend/src/index.ts` declares `USE /api/photos` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-7be671586ef367e2", "name": "Unused endpoint: USE /api/pihole", "shortDescription": {"text": "Unused endpoint: USE /api/pihole"}, "fullDescription": {"text": "`backend/src/index.ts` declares `USE /api/pihole` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-ab91e694dc0e7551", "name": "Unused endpoint: USE /api/media", "shortDescription": {"text": "Unused endpoint: USE /api/media"}, "fullDescription": {"text": "`backend/src/index.ts` declares `USE /api/media` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-b2d8849fb6b1ac47", "name": "Unused endpoint: USE /admin", "shortDescription": {"text": "Unused endpoint: USE /admin"}, "fullDescription": {"text": "`backend/src/index.ts` declares `USE /admin` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-5baa8971ebe192a1", "name": "Unused endpoint: GET /", "shortDescription": {"text": "Unused endpoint: GET /"}, "fullDescription": {"text": "`backend/src/routes/weather.ts` declares `GET /` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-77898f6cfd7fa456", "name": "Unused endpoint: GET /radar", "shortDescription": {"text": "Unused endpoint: GET /radar"}, "fullDescription": {"text": "`backend/src/routes/weather.ts` declares `GET /radar` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-66aa32421e01a8a3", "name": "Unused endpoint: GET /radar/base/:z/:x/:y", "shortDescription": {"text": "Unused endpoint: GET /radar/base/:z/:x/:y"}, "fullDescription": {"text": "`backend/src/routes/weather.ts` declares `GET /radar/base/:z/:x/:y` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-1842d9bd6a3492ef", "name": "Unused endpoint: GET /radar/overlay/:z/:x/:y", "shortDescription": {"text": "Unused endpoint: GET /radar/overlay/:z/:x/:y"}, "fullDescription": {"text": "`backend/src/routes/weather.ts` declares `GET /radar/overlay/:z/:x/:y` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-c4695e8277670508", "name": "Unused endpoint: GET /thumb", "shortDescription": {"text": "Unused endpoint: GET /thumb"}, "fullDescription": {"text": "`backend/src/routes/plex.ts` declares `GET /thumb` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-1b5c4ab81a27a260", "name": "Unused endpoint: GET /theme", "shortDescription": {"text": "Unused endpoint: GET /theme"}, "fullDescription": {"text": "`backend/src/routes/admin.ts` declares `GET /theme` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-c4c08ab8672faa4f", "name": "Unused endpoint: PUT /theme", "shortDescription": {"text": "Unused endpoint: PUT /theme"}, "fullDescription": {"text": "`backend/src/routes/admin.ts` declares `PUT /theme` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-7ce17d6b092a81ca", "name": "Unused endpoint: POST /refresh", "shortDescription": {"text": "Unused endpoint: POST /refresh"}, "fullDescription": {"text": "`backend/src/routes/admin.ts` declares `POST /refresh` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-afb2929ff55f8dc7", "name": "Unused endpoint: GET /env", "shortDescription": {"text": "Unused endpoint: GET /env"}, "fullDescription": {"text": "`backend/src/routes/admin.ts` declares `GET /env` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}, {"id": "scanner-dc94ad32502cda74", "name": "Unused endpoint: PUT /env", "shortDescription": {"text": "Unused endpoint: PUT /env"}, "fullDescription": {"text": "`backend/src/routes/admin.ts` declares `PUT /env` but no frontend code we scanned calls it. This is fine if the endpoint serves external clients (mobile app, third-party, server-side webhooks). Otherwise it's dead code \u2014 consider removing or documenting who consumes it."}, "properties": {"scanner": "scanner-primary", "layer": "api", "severity": "low", "confidence": 1.0}}]}}, "automationDetails": {"id": "repobility/24521"}, "properties": {"repository": "beingforthebenefit/GBoard", "repoUrl": "https://github.com/beingforthebenefit/GBoard", "branch": "main"}, "results": [{"ruleId": "scanner-8ec34e6be3d278ed", "level": "note", "message": {"text": "Stray `console.log` in TS/JS \u2014 backend/src/index.ts:65"}, "properties": {"repobilityId": "db2171a39601fa41", "scanner": "scanner-primary", "fingerprint": "8ec34e6be3d278ed", "layer": "frontend", "severity": "low", "confidence": 1.0, "tags": ["frontend-quality", "fq.console-leak"]}}, {"ruleId": "scanner-653303ec0037c52d", "level": "note", "message": {"text": "Stray `console.log` in TS/JS \u2014 backend/src/services/photosService.ts:290"}, "properties": {"repobilityId": "320ec42fc7911918", "scanner": "scanner-primary", "fingerprint": "653303ec0037c52d", "layer": "frontend", "severity": "low", "confidence": 1.0, "tags": ["frontend-quality", "fq.console-leak"]}}, {"ruleId": "scanner-5943cedb2cf993c7", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/fridge/FridgeLayout.tsx:119"}, "properties": {"repobilityId": "312dc3b7efcc2108", "scanner": "scanner-primary", "fingerprint": "5943cedb2cf993c7", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-0961b8f1f6e3ecf7", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/observatory/ObservatoryLayout.tsx:362"}, "properties": {"repobilityId": "8f5e62fa69b2933e", "scanner": "scanner-primary", "fingerprint": "0961b8f1f6e3ecf7", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-4ee17b0380b9a716", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/flux/FluxLayout.tsx:103"}, "properties": {"repobilityId": "a3544f48ca6d7acf", "scanner": "scanner-primary", "fingerprint": "4ee17b0380b9a716", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-5cabf30e5cf134c9", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/classic/ClassicLayout.tsx:312"}, "properties": {"repobilityId": "8e52202ad19760b8", "scanner": "scanner-primary", "fingerprint": "5cabf30e5cf134c9", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-dd4f412ae5f862a6", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/layouts/terminal/TerminalLayout.tsx:235"}, "properties": {"repobilityId": "d4038ccf445ce916", "scanner": "scanner-primary", "fingerprint": "dd4f412ae5f862a6", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-e2b41fc3b85838f1", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/SplitFlapBoard.tsx:232"}, "properties": {"repobilityId": "68aaa46a09d61183", "scanner": "scanner-primary", "fingerprint": "e2b41fc3b85838f1", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-e7de72caca8a9ece", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/CalendarGrid.tsx:101"}, "properties": {"repobilityId": "f28724ab2b74f047", "scanner": "scanner-primary", "fingerprint": "e7de72caca8a9ece", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-f4ac6ed8d1971208", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/AgendaList.tsx:82"}, "properties": {"repobilityId": "cddaa84639942de0", "scanner": "scanner-primary", "fingerprint": "f4ac6ed8d1971208", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-bfa99e795caa85ea", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/PlexWidget.tsx:26"}, "properties": {"repobilityId": "60fdf04572512721", "scanner": "scanner-primary", "fingerprint": "bfa99e795caa85ea", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-eeab7722765cf53d", "level": "none", "message": {"text": "`truncate` class without `title=` for hover reveal \u2014 frontend/src/components/MediaWidget.tsx:138"}, "properties": {"repobilityId": "19d928919138636f", "scanner": "scanner-primary", "fingerprint": "eeab7722765cf53d", "layer": "frontend", "severity": "info", "confidence": 1.0, "tags": ["frontend-quality", "fq.truncate.no-title"]}}, {"ruleId": "scanner-1f66ad88286ca30a", "level": "warning", "message": {"text": "Dockerfile runs as root: backend/Dockerfile"}, "properties": {"repobilityId": "7afd2b0e8a8c9eeb", "scanner": "scanner-primary", "fingerprint": "1f66ad88286ca30a", "layer": "hardware", "severity": "medium", "confidence": 1.0, "tags": ["security", "container"]}}, {"ruleId": "scanner-3d2a6283f9ebab24", "level": "note", "message": {"text": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine"}, "properties": {"repobilityId": "e866ff9772e76d62", "scanner": "scanner-primary", "fingerprint": "3d2a6283f9ebab24", "layer": "hardware", "severity": "low", "confidence": 1.0, "tags": ["supply-chain", "docker", "pinned-dependencies"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "backend/Dockerfile"}, "region": {"startLine": 1}}}]}, {"ruleId": "scanner-3d2a6283f9ebab24", "level": "note", "message": {"text": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine"}, "properties": {"repobilityId": "06d6562bf7073228", "scanner": "scanner-primary", "fingerprint": "3d2a6283f9ebab24", "layer": "hardware", "severity": "low", "confidence": 1.0, "tags": ["supply-chain", "docker", "pinned-dependencies"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "backend/Dockerfile"}, "region": {"startLine": 19}}}]}, {"ruleId": "scanner-60427b03771411b6", "level": "warning", "message": {"text": "Dockerfile runs as root: frontend/Dockerfile"}, "properties": {"repobilityId": "735c01d8531dfd2c", "scanner": "scanner-primary", "fingerprint": "60427b03771411b6", "layer": "hardware", "severity": "medium", "confidence": 1.0, "tags": ["security", "container"]}}, {"ruleId": "scanner-faa134129e5545ff", "level": "note", "message": {"text": "Docker base image is tag-pinned but not digest-pinned: node:20-alpine"}, "properties": {"repobilityId": "3e90d440e1f9ece1", "scanner": "scanner-primary", "fingerprint": "faa134129e5545ff", "layer": "hardware", "severity": "low", "confidence": 1.0, "tags": ["supply-chain", "docker", "pinned-dependencies"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "frontend/Dockerfile"}, "region": {"startLine": 1}}}]}, {"ruleId": "scanner-b048d166901fd868", "level": "note", "message": {"text": "Docker base image is tag-pinned but not digest-pinned: nginx:alpine"}, "properties": {"repobilityId": "ad1858fcf19686fe", "scanner": "scanner-primary", "fingerprint": "b048d166901fd868", "layer": "hardware", "severity": "low", "confidence": 1.0, "tags": ["supply-chain", "docker", "pinned-dependencies"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "frontend/Dockerfile"}, "region": {"startLine": 20}}}]}, {"ruleId": "scanner-404056134ac94166", "level": "warning", "message": {"text": "Insecure pattern 'cors_wildcard' in backend/src/index.ts:26"}, "properties": {"repobilityId": "2a676a3a0d50f582", "scanner": "scanner-primary", "fingerprint": "404056134ac94166", "layer": "security", "severity": "medium", "confidence": 1.0, "tags": ["owasp", "cors_wildcard"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "backend/src/index.ts"}, "region": {"startLine": 26}}}]}, {"ruleId": "scanner-68bae445c630381a", "level": "warning", "message": {"text": "Insecure pattern 'direct_innerhtml_assignment' in backend/src/routes/admin.ts:509"}, "properties": {"repobilityId": "389ac3e509c6f8c2", "scanner": "scanner-primary", "fingerprint": "68bae445c630381a", "layer": "security", "severity": "medium", "confidence": 1.0, "tags": ["owasp", "direct_innerhtml_assignment"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "backend/src/routes/admin.ts"}, "region": {"startLine": 509}}}]}, {"ruleId": "scanner-6372cebde0220094", "level": "warning", "message": {"text": "No auth library detected"}, "properties": {"repobilityId": "a5b6035a5bbf8054", "scanner": "scanner-primary", "fingerprint": "6372cebde0220094", "layer": "security", "severity": "medium", "confidence": 1.0, "tags": ["coverage", "auth"]}}, {"ruleId": "scanner-27924aa79fa4a517", "level": "warning", "message": {"text": "GitHub Action is tag-pinned rather than SHA-pinned"}, "properties": {"repobilityId": "5a6324341513109a", "scanner": "scanner-primary", "fingerprint": "27924aa79fa4a517", "layer": "cicd", "severity": "medium", "confidence": 1.0, "tags": ["supply-chain", "github-actions", "pinned-dependencies"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": ".github/workflows/ci.yml"}, "region": {"startLine": 76}}}]}, {"ruleId": "scanner-27924aa79fa4a517", "level": "warning", "message": {"text": "GitHub Action is tag-pinned rather than SHA-pinned"}, "properties": {"repobilityId": "5a6324341513109a", "scanner": "scanner-primary", "fingerprint": "27924aa79fa4a517", "layer": "cicd", "severity": "medium", "confidence": 1.0, "tags": ["supply-chain", "github-actions", "pinned-dependencies"]}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": ".github/workflows/ci.yml"}, "region": {"startLine": 89}}}]}, {"ruleId": "scanner-3ab5d313dda8e5f9", "level": "note", "message": {"text": "Debug logging residue appears in source files"}, "properties": {"repobilityId": "a2fb9b465af9d7f9", "scanner": "scanner-primary", "fingerprint": "3ab5d313dda8e5f9", "layer": "quality", "severity": "low", "confidence": 1.0, "tags": ["debug", "cleanup", "repo-hardening", "generated-repo-pattern"]}}, {"ruleId": "scanner-2d0c7b7ab8f8aacf", "level": "warning", "message": {"text": "Critical user flow still appears backed by mock or placeholder data"}, "properties": {"repobilityId": "75148ae9279242e9", "scanner": "scanner-primary", "fingerprint": "2d0c7b7ab8f8aacf", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["placeholder", "mock-data", "critical-flow", "generated-repo-pattern"]}}, {"ruleId": "scanner-8a9b63458cfd1515", "level": "warning", "message": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/piholeService.ts:70"}, "properties": {"repobilityId": "6ba57dac833f2d22", "scanner": "scanner-primary", "fingerprint": "8a9b63458cfd1515", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "fragile-runtime", "robustness"]}}, {"ruleId": "scanner-7c1fa188abd1d96a", "level": "warning", "message": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/calendarService.ts:51"}, "properties": {"repobilityId": "09895350cc53abe4", "scanner": "scanner-primary", "fingerprint": "7c1fa188abd1d96a", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "fragile-runtime", "robustness"]}}, {"ruleId": "scanner-68060e8c57419b4f", "level": "warning", "message": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/weatherService.ts:50"}, "properties": {"repobilityId": "8a6bcf8fc2c77609", "scanner": "scanner-primary", "fingerprint": "68060e8c57419b4f", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "fragile-runtime", "robustness"]}}, {"ruleId": "scanner-823204e17b4947f2", "level": "warning", "message": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/mediaService.ts:62"}, "properties": {"repobilityId": "4fb0a6806d06c34d", "scanner": "scanner-primary", "fingerprint": "823204e17b4947f2", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "fragile-runtime", "robustness"]}}, {"ruleId": "scanner-b124f2a59829fca0", "level": "warning", "message": {"text": "`fetch()` without try/.catch or AbortSignal \u2014 backend/src/services/radarService.ts:46"}, "properties": {"repobilityId": "b3b2135542b656a6", "scanner": "scanner-primary", "fingerprint": "b124f2a59829fca0", "layer": "quality", "severity": "medium", "confidence": 1.0, "tags": ["integrity", "fragile-runtime", "robustness"]}}, {"ruleId": "scanner-2eafc3188c36bb02", "level": "none", "message": {"text": "3 env vars used in code but missing from .env.example"}, "properties": {"repobilityId": "bd934ba112b02ffa", "scanner": "scanner-primary", "fingerprint": "2eafc3188c36bb02", "layer": "quality", "severity": "info", "confidence": 1.0, "tags": ["integrity", "config-drift"]}}, {"ruleId": "scanner-ff6ab815e4ed89c0", "level": "error", "message": {"text": "Dangling fetch: GET https://api.rainviewer.com/public/weather-maps.json (backend/src/services/radarService.ts:46)"}, "properties": {"repobilityId": "a3d1969992316f86", "scanner": "scanner-primary", "fingerprint": "ff6ab815e4ed89c0", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "fetch"]}}, {"ruleId": "scanner-6fcd64a2d95c8351", "level": "error", "message": {"text": "Dangling fetch: GET /admin/theme (frontend/src/App.tsx:24)"}, "properties": {"repobilityId": "dbbf6564df18b9ed", "scanner": "scanner-primary", "fingerprint": "6fcd64a2d95c8351", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "fetch"]}}, {"ruleId": "scanner-d01ef4116c438895", "level": "error", "message": {"text": "Dangling fetch: GET /api/weather/radar (frontend/src/hooks/useRadar.ts:15)"}, "properties": {"repobilityId": "2fd3ab79c713a94c", "scanner": "scanner-primary", "fingerprint": "d01ef4116c438895", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "fetch"]}}, {"ruleId": "scanner-a640928719c8ab99", "level": "error", "message": {"text": "Dangling fetch: GET /api/plex (frontend/src/hooks/usePlex.ts:19)"}, "properties": {"repobilityId": "dd9a56700296e2e7", "scanner": "scanner-primary", "fingerprint": "a640928719c8ab99", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "fetch"]}}, {"ruleId": "scanner-1e842da6f53ca284", "level": "error", "message": {"text": "Dangling fetch: GET /api/calendar (frontend/src/hooks/useCalendar.ts:16)"}, "properties": {"repobilityId": "2e21cc9f645462e7", "scanner": "scanner-primary", "fingerprint": "1e842da6f53ca284", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "fetch"]}}, {"ruleId": "scanner-bd06340139d1d316", "level": "error", "message": {"text": "Dangling fetch: GET /api/weather (frontend/src/hooks/useWeather.ts:16)"}, "properties": {"repobilityId": "29519e5b6c4663ba", "scanner": "scanner-primary", "fingerprint": "bd06340139d1d316", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "fetch"]}}, {"ruleId": "scanner-8b06f19fccff31fd", "level": "error", "message": {"text": "Dangling fetch: GET /admin/theme (frontend/src/hooks/useDayNight.ts:16)"}, "properties": {"repobilityId": "cb4078484a77a0c2", "scanner": "scanner-primary", "fingerprint": "8b06f19fccff31fd", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "fetch"]}}, {"ruleId": "scanner-e10815d3d7ab6249", "level": "error", "message": {"text": "Dangling fetch: GET /api/pihole (frontend/src/hooks/usePihole.ts:34)"}, "properties": {"repobilityId": "33c411baf13b99a5", "scanner": "scanner-primary", "fingerprint": "e10815d3d7ab6249", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "fetch"]}}, {"ruleId": "scanner-f160b899f3864540", "level": "error", "message": {"text": "Dangling fetch: GET /api/media (frontend/src/hooks/useMedia.ts:16)"}, "properties": {"repobilityId": "5824c753100310c0", "scanner": "scanner-primary", "fingerprint": "f160b899f3864540", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "fetch"]}}, {"ruleId": "scanner-8d253e133ad8934b", "level": "error", "message": {"text": "Dangling fetch: GET /api/photos (frontend/src/hooks/usePhotos.ts:16)"}, "properties": {"repobilityId": "c0d9207c90e4cda0", "scanner": "scanner-primary", "fingerprint": "8d253e133ad8934b", "layer": "api", "severity": "high", "confidence": 1.0, "tags": ["wiring", "dangling-fetch", "fetch"]}}, {"ruleId": "scanner-83e27b789830003c", "level": "note", "message": {"text": "Unused endpoint: USE /api/weather"}, "properties": {"repobilityId": "19ccec5f5c665322", "scanner": "scanner-primary", "fingerprint": "83e27b789830003c", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-aebdad2655c302c7", "level": "note", "message": {"text": "Unused endpoint: USE /api/calendar"}, "properties": {"repobilityId": "711094ad29cc6cc2", "scanner": "scanner-primary", "fingerprint": "aebdad2655c302c7", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-4f80f23801d6a038", "level": "note", "message": {"text": "Unused endpoint: USE /api/plex"}, "properties": {"repobilityId": "3cab2b8b86f53794", "scanner": "scanner-primary", "fingerprint": "4f80f23801d6a038", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-6a3460d428c799fa", "level": "note", "message": {"text": "Unused endpoint: USE /api/photos"}, "properties": {"repobilityId": "7621014456d6b7ab", "scanner": "scanner-primary", "fingerprint": "6a3460d428c799fa", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-7be671586ef367e2", "level": "note", "message": {"text": "Unused endpoint: USE /api/pihole"}, "properties": {"repobilityId": "9869d8a7c6508413", "scanner": "scanner-primary", "fingerprint": "7be671586ef367e2", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-ab91e694dc0e7551", "level": "note", "message": {"text": "Unused endpoint: USE /api/media"}, "properties": {"repobilityId": "82330e4bb394912f", "scanner": "scanner-primary", "fingerprint": "ab91e694dc0e7551", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-b2d8849fb6b1ac47", "level": "note", "message": {"text": "Unused endpoint: USE /admin"}, "properties": {"repobilityId": "fee0a8161b8fd5a7", "scanner": "scanner-primary", "fingerprint": "b2d8849fb6b1ac47", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-5baa8971ebe192a1", "level": "note", "message": {"text": "Unused endpoint: GET /"}, "properties": {"repobilityId": "6618d11d30f111a3", "scanner": "scanner-primary", "fingerprint": "5baa8971ebe192a1", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-77898f6cfd7fa456", "level": "note", "message": {"text": "Unused endpoint: GET /radar"}, "properties": {"repobilityId": "6799a4d360d5d429", "scanner": "scanner-primary", "fingerprint": "77898f6cfd7fa456", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-66aa32421e01a8a3", "level": "note", "message": {"text": "Unused endpoint: GET /radar/base/:z/:x/:y"}, "properties": {"repobilityId": "72999c9f6b664416", "scanner": "scanner-primary", "fingerprint": "66aa32421e01a8a3", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-1842d9bd6a3492ef", "level": "note", "message": {"text": "Unused endpoint: GET /radar/overlay/:z/:x/:y"}, "properties": {"repobilityId": "ec3db482367447a4", "scanner": "scanner-primary", "fingerprint": "1842d9bd6a3492ef", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-c4695e8277670508", "level": "note", "message": {"text": "Unused endpoint: GET /thumb"}, "properties": {"repobilityId": "5834fc9449639f36", "scanner": "scanner-primary", "fingerprint": "c4695e8277670508", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-1b5c4ab81a27a260", "level": "note", "message": {"text": "Unused endpoint: GET /theme"}, "properties": {"repobilityId": "623272aade1020e8", "scanner": "scanner-primary", "fingerprint": "1b5c4ab81a27a260", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-c4c08ab8672faa4f", "level": "note", "message": {"text": "Unused endpoint: PUT /theme"}, "properties": {"repobilityId": "226cd4db667e17f7", "scanner": "scanner-primary", "fingerprint": "c4c08ab8672faa4f", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-7ce17d6b092a81ca", "level": "note", "message": {"text": "Unused endpoint: POST /refresh"}, "properties": {"repobilityId": "b008363ea4051627", "scanner": "scanner-primary", "fingerprint": "7ce17d6b092a81ca", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-afb2929ff55f8dc7", "level": "note", "message": {"text": "Unused endpoint: GET /env"}, "properties": {"repobilityId": "4d9a401936c7ba16", "scanner": "scanner-primary", "fingerprint": "afb2929ff55f8dc7", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}, {"ruleId": "scanner-dc94ad32502cda74", "level": "note", "message": {"text": "Unused endpoint: PUT /env"}, "properties": {"repobilityId": "bb6fcbab512dc611", "scanner": "scanner-primary", "fingerprint": "dc94ad32502cda74", "layer": "api", "severity": "low", "confidence": 1.0, "tags": ["wiring", "unused-endpoint"]}}]}]}