Commit Graph

573 Commits

Author SHA1 Message Date
8181f39ae2 feat(profiler): persist raw SSH KEX algorithm ordering
Prober already emits kex_algorithms in hassh_fingerprint syslog events, but
the raw ordered list was only queryable via the generic bounty store. Add a
dedicated AttackerBehavior.kex_order_raw column (TEXT, JSON list) so
post-v1 KEX-order fingerprinting has a typed, indexable home.

Pipeline:
  - sniffer_rollup() now consumes hassh_fingerprint events and collects
    distinct kex_algorithms strings across ports.
  - build_behavior_record() JSON-encodes the list (NULL when empty).
  - sqlmodel_repo._deserialize_behavior() parses it back into a list.

Closes pre-v1 gap #1 from SIGNAL_CAPTURE_AUDIT.md.
2026-04-22 21:29:46 -04:00
25838eb9f3 refactor(profiler): split behavioral.py into topical modules
Break the 603-line behavioral.py into timing/classify/tools/phases/fingerprint
sibling modules plus a slim orchestrator. Public API unchanged: behavioral.py
re-exports every previously-exposed symbol, so worker.py and existing tests
keep working with zero import changes.

No behavior change; all 64 profiler tests pass.
2026-04-22 21:10:19 -04:00
b51095cec5 style(web): unify button sizing across pages (padding/font/spacing) 2026-04-22 18:35:40 -04:00
4bf671b316 style(web/topologies): unify header buttons with shared outlined style 2026-04-22 18:32:43 -04:00
9d64d8a046 style(web/mazenet): tint palette chip with user accent color for contrast 2026-04-22 18:29:19 -04:00
c804d3111a style(web/mazenet): enlarge palette port/proto chip for legibility 2026-04-22 18:26:29 -04:00
602a0e1efc feat(web/mazenet): add Mail, Comms, Observability, Containers groups + remaining services 2026-04-22 18:23:24 -04:00
9c38a3f11a feat(web/mazenet): group Service Fleet items by category (Remote Access, Web, Databases, etc.) 2026-04-22 18:19:21 -04:00
1674316788 feat(web/mazenet): glide transitions for service fleet + inspector panels 2026-04-22 18:16:17 -04:00
e0231bf990 style(web/mazenet): rename PALETTE toggle to SERVICE FLEET 2026-04-22 18:14:17 -04:00
e35358afd1 feat(web/mazenet): fullscreen button also triggers browser fullscreen API 2026-04-22 18:12:38 -04:00
ef34df4a7d feat(web/mazenet): fullscreen canvas mode (hides topbar + sidebar, Esc to exit) 2026-04-22 18:11:37 -04:00
31d02a9726 feat(web/mazenet): toggleable palette (deployer) panel 2026-04-22 18:10:18 -04:00
8985c28fab fix(web/mazenet): stop canvas from overflowing viewport (flex-size shell instead of fixed calc) 2026-04-22 18:08:44 -04:00
f3e366a2a3 fix(web/topologies): stop page from overflowing viewport (min-height off by topbar+padding) 2026-04-22 18:07:09 -04:00
53647d66b7 feat(web/swarm): fold agent enrollment into a wizard on Swarm Hosts 2026-04-22 18:05:26 -04:00
bff350400f style(web/swarm): align Swarm pages with shared page-header primitive 2026-04-22 17:59:27 -04:00
fcfc4eba3b style(web/topologies): drop extra padding so header aligns with fleet/dashboard 2026-04-22 17:55:06 -04:00
f94887393c style(web/topologies): align page header with shared style, center empty state
- TopologyList header now uses .page-header + .page-title-group +
  .page-sub like Dashboard/Attackers/DeckyFleet; title typography and
  separator match the rest of the app.
- Pluralisation fix: '0 topologyies' → '0 TOPOLOGIES', singular '1
  TOPOLOGY'.
- When the list is empty the EmptyState renders in its own flex
  container that fills the viewport so the card is centered both
  axes, with bumped icon/title/hint sizing for the hero treatment.
2026-04-22 17:53:35 -04:00
5704e8fcce fix(topology): delete topology_mutations in delete-cascade
delete_topology_cascade manually deletes status_events, edges, deckies
and lans but overlooked topology_mutations, so deleting any topology
that ever had a mutation enqueued (i.e. edits while active|degraded)
failed with an FK IntegrityError. Add the missing DELETE and extend
the cascade test to seed a mutation row.
2026-04-22 17:50:30 -04:00
3f460bab84 feat(web): show MazeNET decky running count + roll into dashboard
MazeNET header now reports '{running}/{total} DECKIES RUNNING' so
operators can see per-topology runtime status at a glance.

Dashboard ACTIVE DECKIES counters used to reflect only the fleet state
file; TopologyDecky rows (MazeNET deployments) are now added in —
deployed_deckies = fleet + all topology rows, active_deckies = fleet
(no runtime field) + topology rows whose state is 'running'.
2026-04-22 17:48:04 -04:00
b802d59c70 style(web): vertically center empty-state in logs table 2026-04-22 17:32:53 -04:00
1472f1da0a style(web): drop border on empty-row td in logs tables 2026-04-22 17:31:50 -04:00
070ad9397c style(web): skip row-hover highlight on empty-state rows
Hovering the empty-state row in LiveLogs/Dashboard tables briefly lit
the full-width td with the data-row glow. Tag the placeholder tr with
.empty-row and scope the .logs-table hover rule to :not(.empty-row).
2026-04-22 17:29:39 -04:00
fe8dd08ba6 style(web): center EmptyState contents with consistent min-height
Base .empty-state now flex-centers its icon/title/hint/CTA with a
140px min-height so icon-bearing empty states in the Dashboard side
panels (DECKIES UNDER SIEGE, TOP ATTACKERS) stop looking cramped.
Component-scoped rules (attackers-root, bounty-root, logs-root)
remain more specific and are unaffected.
2026-04-22 17:27:20 -04:00
4d1e6c0838 feat(web): add ? cheatsheet and / focus-search hotkeys
- New ShortcutsHelp modal enumerates global, nav G-chord and palette
  bindings; openable via ? (Shift+/) or the command palette.
- / dispatches a global decnet:focus-search event; Attackers, Bounty
  and LiveLogs listen and focus their in-page search inputs (pages
  without a local search are skipped per plan).
- Respects the existing editable-element guard and Alt+K palette
  toggle; no rebinds to prior shortcuts.
2026-04-22 17:25:32 -04:00
ecb813ad38 feat(web): retrofit empty states to shared EmptyState primitive
Replace ad-hoc empty-state markup across Dashboard, TopologyList,
LiveLogs, Attackers, Bounty, AttackerDetail, SwarmHosts, RemoteUpdates
and CommandPalette with the new <EmptyState> component. Themed icons
+ hints improve discoverability; TopologyList and SwarmHosts gain
CTAs to their respective creation flows.
2026-04-22 17:22:07 -04:00
de63a0ab5c feat(web/fleet): DeckyFleet reskin, inspect drawer, and modal retrofit
- Fleet grid rewrite: richer decky cards (archetype, services, swarm
  chip, mutation status) with click-to-inspect.
- Deploy wizard: track server-accepted deploys separately so the
  placeholder log stream only auto-closes on success; surface failures.
- DeployWizard + IntervalEditor migrated to the shared <Modal>
  primitive — gains ESC-close, backdrop click, Tab focus trap, and
  body scroll lock without changing visual design.
2026-04-22 17:15:45 -04:00
e14527b382 feat(web): reskin Attackers, Bounty, and LiveLogs pages
Each page gets its own scoped stylesheet and is rewritten around the
shared design language: filter bars, paginated lists, empty-state
blocks, BountyInspector drawer. Behavioural surface is unchanged —
same API calls, same routes, same RBAC gating.
2026-04-22 17:15:35 -04:00
1518475946 feat(web/dashboard): reskin with richer live-activity panels
Rewrites Dashboard.tsx around three stacked panels — live interactions,
deckies-under-siege, and top-attackers — each with its own header,
empty state, and status accents. Dashboard.css fills in the supporting
grid + type system.
2026-04-22 17:15:27 -04:00
ccbe949238 feat(web): command palette, toasts, and global shell chrome
- CommandPalette (Alt+K): fuzzy action launcher with keyboard nav.
- Toasts: ephemeral notification stack + provider.
- useGlobalHotkeys: Alt+K palette toggle, G-chord navigation
  (G D/F/M/L/B/A/S/U/E/C), respects editable-element focus.
- Layout/App: wire ToastProvider at root, mount the palette inside the
  authed shell, introduce the global search box in the top bar.
- MazeNETRoute now renders TopologyList inline when no ?topology is
  present, instead of bouncing through a redirect.
- index.css: a few global token tweaks consumed by the new chrome.

Fixes a latent breakage: Config.tsx and MazeNET already imported
./Toasts/useToast but the directory was never committed.
2026-04-22 17:15:19 -04:00
dca6eddd5f feat(web/topology): hide DELETE on running topologies
The DELETE path on a topology whose containers are still up is a
footgun — even if the backend rejects the delete, surfacing the
button invites mistakes. Gate it so DELETE only shows for pending,
failed, and torn-down topologies. Active/degraded/deploying topologies
must be torn down first, which then reveals DELETE again.
2026-04-22 17:14:17 -04:00
6f537f52c2 fix(topology): remove DMZ gateway auto-attach on LAN create
POST /topologies/{id}/lans previously called _auto_attach_gateway()
whenever a non-DMZ LAN was created, which wired the DMZ gateway decky
to every new subnet. That's why a deployed gateway ended up with
eth0..ethN on every LAN regardless of what the user drew in MazeNET.

Drop the auto-attach helper entirely. The DMZ_ORPHAN deploy-time
validator (decnet/topology/validate.py:65-110) stays strict — users
must explicitly wire the gateway to each subnet they want bridged,
which is the whole point of having a topology editor.

useMazeApi.ts: drop stale auto-bridge reference from comment.
2026-04-22 17:14:09 -04:00
8632cee40a feat(web): retrofit drawers + CreateTopologyWizard with ESC/focus-trap
ArtifactDrawer, SessionDrawer, CreateTopologyWizard all now:
- close on ESC
- trap Tab/Shift+Tab focus within the panel
- lock body scroll while open
- restore prior focus on unmount

Uses the new useEscapeKey + useFocusTrap hooks. No visual changes;
the bespoke CSS shells (ctw-*, inline drawer styling) are preserved.
2026-04-22 17:09:45 -04:00
d0463c2c16 feat(web): add Modal + EmptyState primitives and a11y hooks
- Modal: shared backdrop/panel with ESC-close, backdrop-click-close,
  focus trap, body scroll lock; supports center + drawer-right variants,
  matrix/violet accents, default/wide widths.
- EmptyState: icon + title + hint + optional CTA; compact variant
  for tight rails.
- useEscapeKey, useFocusTrap: reusable hooks powering Modal; will also
  be adopted by CommandPalette and ContextMenu in follow-up commits.

No retrofits yet — primitives only. tsc clean.
2026-04-22 17:04:37 -04:00
73ccf12678 fix(web): allow MazeNET canvas pan from inside net-box body
Pan drag previously required mousedown on the bare canvas (target ===
currentTarget). When zoomed in, net-boxes cover most of the viewport
so there was no bare grid to grab. Drop the guard — node/header/port/
resize handlers all call stopPropagation() already, so only net-box
body mousedowns bubble up to start the pan, which is exactly what
we want.
2026-04-22 16:43:38 -04:00
ef60b086ba feat(web): MazeNET canvas pan + zoom (0.25×–2.5×)
Wheel-to-zoom anchored at the cursor, ZOOM IN/OUT toolbar buttons, and
a live zoom% in the status bar. Pan layer gets transform-origin 0 0 and
a scale(zoom) factor; grid pattern tile scales with zoom; edge SVG is
overflow:visible so long edges don't clip at high zoom. World-space
hit-testing, resize deltas, and palette drops all divide by zoom.
Reset View zeroes pan AND zoom.
2026-04-22 16:40:47 -04:00
1f429cd00e feat(web): MazeNET 7b — service-level selection + inspector panel
Clicking a service tag selects it (stops node drag), extends Selection
discriminant with {type:'service',id,nodeId}, and renders an inspector
panel showing proto/port/subnet/risk chip + REMOVE SERVICE button
(gated off for observed nodes and degraded topologies). Service-tag
styling now pulls `risk` from DEFAULT_SERVICES metadata instead of
node.status alone.
2026-04-22 15:56:55 -04:00
6fbac5d057 feat(web): MazeNET 7a — canvas chrome + node-head visuals
Toolbar (RESET VIEW / AUTO-LAYOUT), status bar (GRAPH LIVE + pan + as-of
timestamp), 4-row legend, and archetype icon + status dot in each node
head.
2026-04-22 15:54:11 -04:00
91111ea7ee feat(cli): add decnet init --deinit to undo a previous bootstrap
Reverse of init, step-by-step: systemctl disable --now decnet.target,
remove every decnet-*.service + decnet.target unit file, drop the
polkit rule, drop the tmpfiles.d entry, daemon-reload, remove
/etc/decnet + /etc/decnet/config.ini, /run/decnet, /opt/decnet, and
userdel/groupdel the decnet identity.

Preserves /var/lib/decnet and /var/log/decnet by default — those
hold operator data. Pass `--deinit --purge` to rm -rf them too.
Idempotent on a clean host (every step prints [SKIP]). Honours
--dry-run.

5 new tests cover the full-undo path, --purge, idempotent clean-host
deinit, dry-run side-effect-free behaviour, and the --purge without
--deinit guard.
2026-04-22 14:31:56 -04:00
3dae44c652 feat(cli): add decnet init one-shot master-host bootstrap
Creates the decnet system user/group, installs every unit file from
deploy/ into /etc/systemd/system, drops the polkit rule, seeds
/opt/decnet + /var/{lib,log}/decnet + /etc/decnet + /run/decnet,
writes a placeholder /etc/decnet/config.ini, applies the new
tmpfiles.d entry so /run/decnet survives reboots, daemon-reloads,
and `systemctl enable --now decnet.target`.

Idempotent (re-runs print [SKIP] on already-configured items),
--dry-run previews the plan without touching anything, --no-start
defers the target start, --force overwrites even matching unit
files. Master-only (added to MASTER_ONLY_COMMANDS).

9 orchestration tests cover the non-root gate, dry-run, useradd/
groupadd argv, SKIP on present user/group, unit-file idempotency,
--force overwrite, --no-start suppression, happy path, and the
"deploy/ not found" error message.
2026-04-22 14:28:11 -04:00
6d769edce0 docs(debt): mark DEBT-034 (worker supervisor) shipped
Units + polkit rule + systemd_control helper + start endpoints +
installed flag + UI wiring all landed. SWARM-host start/stop and
crash-quarantine policy stay as named deferrals.
2026-04-22 14:14:22 -04:00
49a6a674e6 feat(web): wire Workers panel START + START ALL buttons
Per-row START button enabled iff `installed && status !== 'ok'`;
tooltip explains why it's disabled ("Unit not installed" /
"Already running"). Transient `starting` state shows `...` on the
button and auto-clears after 15s so the UI never gets stuck if the
heartbeat is slow.

START ALL WORKERS button in the header calls /workers/start-all and
renders the three counts in the toast:
`STARTED · N · ALREADY RUNNING · M · FAILED · K (first failure: …)`.
Tone flips to alert when K > 0.
2026-04-22 14:13:58 -04:00
13ea916943 feat(workers): add start + start-all endpoints (systemd supervisor)
POST /api/v1/workers/{name}/start — 202 on acceptance, 404 unknown
worker, 503 if the unit file is not installed, 502 if systemctl
returns non-zero (stderr snippet in detail, full stack logged).
Admin only.

POST /api/v1/workers/start-all — best-effort: walks the worker list
in dependency order (bus → api → data-plane), skips already-active
and uninstalled units, aggregates outcomes into
{started, already_running, failed[]}. Returns 200 even on partial
failure; the caller reads the three lists.

Both endpoints delegate to the systemd_control helper, so the attack
surface for "what gets executed" is locked to `decnet-<validated-name>
.service` at two layers (router KNOWN_WORKERS + helper regex).
2026-04-22 14:12:29 -04:00
0fbb07c2ec feat(workers): bus-backed Workers panel (registry, control, installed flag)
Ships the backend half of Config → Workers:

* Worker registry aggregates `system.*.health` + `system.bus.health`
  heartbeats into a last-seen dict; OK / STALE / UNKNOWN tiers drop
  out of a 90s window (3× the 30s heartbeat interval).
* `GET /api/v1/workers` returns the snapshot plus `bus_connected`
  (so the UI can explain "all UNKNOWN" when the bus socket is down)
  and a per-row `installed` flag populated from
  `systemctl list-unit-files decnet-*.service` (cached 30s).
* `POST /api/v1/workers/{name}/stop` publishes a stop intent on
  `system.<name>.control`; workers listen via the shared control
  listener in `bus/publish.py`.
* Heartbeat + control listener wired into collector / profiler /
  sniffer / prober / mutator worker loops. API self-heartbeats too
  so the panel always has one ground-truth row.
* Topic helper `system_control(name)` + tests covering builder
  validation, control listener shutdown path, and the API surface
  (auth gating, bus-connected field, unknown-name 404).

Adds `StartFailure` / `StartAllResponse` models in anticipation of
the upcoming start endpoints (DEBT-034).
2026-04-22 14:10:39 -04:00
fcaac648a4 feat(web): add systemd_control helper for worker unit management
Thin async wrapper over `systemctl` — never shell=True, always
create_subprocess_exec. Unit names are built from
`decnet-<validated-name>.service`; the regex check is defence in depth
on top of the router-level KNOWN_WORKERS validation.

Exposes start / stop / is_active / list_installed; last is cached for
30s to keep the Workers panel cheap under REFRESH spam. On non-systemd
hosts list_installed returns an empty set, so the UI renders with
every row marked not-installed instead of 500-ing.
2026-04-22 14:08:35 -04:00
a41ef52249 chore(polkit): allow decnet group to manage decnet-*.service without password
Scoped rule — matches only `decnet-<name>.service` and `decnet.target`.
Any unit outside that regex falls through to the default polkit policy.
Required so the API (running as the `decnet` user) can invoke
`systemctl start decnet-<name>.service` non-interactively.
2026-04-22 14:07:17 -04:00
f21453afdc chore(systemd): add units for collector/profiler/sniffer/prober/mutator + decnet.target
Adds the five missing worker units plus a grouping target so
`systemctl start decnet.target` brings the whole fleet up in order.
Sniffer gets CAP_NET_RAW for scapy; collector and mutator join the
docker supplementary group for docker.sock access. Repoints
Documentation= across all existing units to the canonical
git.resacachile.cl wiki.
2026-04-22 14:06:42 -04:00
90d0c3b206 chore(deps): add pydeps to dev extras
Dev-only dependency-graph tool; no runtime impact.
2026-04-22 09:24:35 -04:00
a63708a3d1 test(templates): cover instance_seed helper and update service tests
Add tests/service_testing/test_instance_seed.py — pins NODE_NAME to assert
determinism of seeded functions and sweeps NODE_NAMEs to assert cross-fleet
divergence. Conftest gains load_real_instance_seed() so template tests see
the real seeding behavior instead of a stub. Existing template tests updated
to pin NODE_NAME and match seeded outputs.
2026-04-22 09:24:28 -04:00