1. The robot scan UI runs locally, on the user's machine#
- Status: accepted (provisional - revisit after the beta, see "Revisit when")
- Date: 2026-10-04
- Epic: Robot Scan UI (spec in the project vault: Robot Scan UI - Spec)
Context#
The robot scan UI lets a user add a robot they own, see it as a diagram with each weakness pinned to the part it's in, and recheck a finding after fixing it. Scans have to reach the robot, which sits on a lab or office network.
Two ways to run it:
- A. Local web UI.
glitched uiserves the app on127.0.0.1, using the same database, checks and safety rules as the CLI. - B. Hosted app plus a local runner. A web app on the internet; a small runner on the robot's network pairs with it and runs scans.
A hosted page can't - and shouldn't - reach into a private network, so B needs the runner anyway. B also needs accounts, hosting, a secure runner link, and moves device names, addresses and results off the user's network.
Decision#
Build A first.
- Nothing about the user's robots leaves their machine; no accounts.
- It reuses everything that exists: the database,
glitched.safety, the passive checks, exposure matching, Docker Compose. - Design the API between the UI and the scanner as if it were remote (JSON over HTTP, scans as jobs with progress events), so B is an addition later, not a rewrite.
Consequences#
- One machine at a time; no sharing between team members until B.
- The UI is a local web server, which other web pages in the user's browser could try to call. It must:
- bind to
127.0.0.1only, never0.0.0.0; - check the
Hostheader is127.0.0.1:<port>orlocalhost:<port>(blocks DNS rebinding); - require a random token created at start-up (printed in the URL that
glitched uiopens, then kept in a cookie) on every API call, so no other site can start a scan. - Every action stays available from the CLI. The CLI remains the way to script Glitched and run it in CI.
Findings model#
The existing findings table belongs to one assessment (one scan run). The UI
needs one list per robot that persists across scans and has its own lifecycle,
so a robot's findings are a new table, kept in sync from what Glitched already
knows. (Schema changes are added in place, as always.)
device_findings - one row per robot × source × reference:
| Column | Meaning |
|---|---|
device_id |
the robot (devices) |
kind |
how we know: observed (a check saw it), declared (the user's answers), known_vulnerability (CVE matched by platform or component+version), possible (EMB3D threat that fits but wasn't tested) |
source, ref_id |
where it comes from: check/TID-406, profile/PID-15, cve/CVE-…, emb3d/TID-… |
part |
diagram anchor: network, cloud_link, main_controller, debug_port, peripherals, whole_robot |
title, detail, evidence |
plain-language finding, its latest detail, the latest evidence text |
status |
open, fix_applied, resolved, accepted_risk, not_applicable |
status_note, accepted_until |
why (required for accepted risk) and until when |
first_seen_at, last_seen_at, resolved_at, last_assessment_id |
history pointers |
device_finding_events - every status change: when, from, to, by whom (scan,
recheck, sync, user), note.
Sync rules (after each scan, and whenever a robot's profile or the threat intel changes):
- A finding that is present and has no row → new row,
open. - Present again after
resolved→open("reappeared"). - Still present after
fix_applied→open("still present"). - An observed finding whose check now says not present →
resolved(by scan). Declared, known-vulnerability and possible findings that no longer match the profile (answer changed, SBOM version updated) →resolved(by sync). - Inconclusive check results change nothing but the evidence.
accepted_riskandnot_applicableare the user's call and are never changed by a scan - except that an accepted risk pastaccepted_untilreturns toopen.
API outline#
All JSON, under /api, token required. Long operations return a job id;
progress streams from /api/jobs/{id}/events (server-sent events).
| Method and path | Does |
|---|---|
GET /api/robots |
robots with open-finding counts and last scan time |
POST /api/robots |
add a robot: name, host, type, authorization (safety rules apply) |
GET /api/robots/{id} |
robot, profile, diagram parts, threat-intel freshness |
PUT /api/robots/{id}/profile |
questionnaire answers or a glitch.yaml |
POST /api/robots/{id}/sbom |
upload a CycloneDX/SPDX SBOM |
GET /api/robots/{id}/scan-plan |
exactly what a scan will send, before running it |
POST /api/robots/{id}/scans |
start a scan → job |
GET /api/robots/{id}/findings |
the robot's findings, ranked |
PATCH /api/findings/{id} |
set status: fix_applied, accepted_risk (note + until), not_applicable, open |
POST /api/findings/{id}/recheck |
re-run only the check behind an observed finding → job |
GET /api/jobs/{id}/events |
progress events; DELETE /api/jobs/{id} cancels |
Revisit when#
Beta testers say they need to share results with a team or manage a fleet from one place - that's option B, as its own epic.