167 Commits

Author SHA1 Message Date
f2b3393669 chore: relicense to AGPL-3.0-or-later and add SPDX headers
Replaces LICENSE (GPLv3 -> AGPLv3) and prepends
`SPDX-License-Identifier: AGPL-3.0-or-later` to every source file
across decnet/, decnet_web/, tests/, scripts/, and tools/.

Rationale: closes the GPLv3 ASP loophole so any party operating a
modified DECNET as a network service must offer their modified
source. Personal copyright (Samuel Paschuan) + inbound=outbound
contributions make a future unilateral relicense infeasible.

- LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt)
- COPYRIGHT: project copyright notice
- tools/add_spdx_headers.py: idempotent header injector
  (shebang- and PEP 263-aware)

Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh).
No behavior change; comments only.
2026-05-22 21:04:16 -04:00
1b90048715 fix(api): /deckies/deploy becomes additive by default
The wizard POSTs only the new decky on each submit. The handler used to
treat every INI as the complete desired fleet (config.deckies = INI) so
the reconciler tore down prior deckies as orphans — deploying a second
Windows workstation silently wiped the first.

Add replace_fleet to DeployIniRequest (default false). Default path
merges new deckies into existing config and rejects name/IP collisions
with 409. replace_fleet=true preserves set-desired-state semantics for
CLI / declarative callers. Lifecycle rows are created only for the
deckies submitted in the current call, so /deckies/lifecycle?ids=...
reflects exactly what this submit deployed.

build_deckies_from_ini gains reserved_ips so additive auto-allocation
skips IPs already held by the existing fleet.
2026-05-22 18:14:50 -04:00
5b13a01ab6 feat(web): deploy wizard polls async lifecycle instead of holding HTTP
- New useLifecyclePolling(ids, intervalMs) hook: polls
  GET /deckies/lifecycle?ids=... every 2s until every row is terminal,
  surfaces transient HTTP failures without giving up.
- DeployWizard: drops the 180s axios timeout and the fake-log-driven
  deployOk flag. After POST 202, sets lifecycle_ids -> the hook drives
  the per-decky pill grid (PENDING / RUNNING / SUCCEEDED / FAILED).
  Real terminal lines stream into the log as rows resolve. Auto-close
  on all-success after 700ms.
- DeckyFleet.css: .lifecycle-grid + .lifecycle-pill in the existing
  fleet vocabulary; running pill pulses, failed pill borders alert.
- Existing 4 wizard render tests still pass; 4 new hook tests cover
  empty ids / single-success / polling-until-terminal / HTTP error.
2026-05-22 17:50:26 -04:00
e292fd7d05 feat(web): surface bgp_prefix and rpki_status in AttackerDetail and export
AttackerData type gets bgp_prefix / rpki_status / rpki_source.
TimelineSection renders prefix inline next to AS number; RPKI status
shows as a green RPKI VALID / red RPKI INVALID badge, or dim
NO ROA for not-found. rpki-status-badge CSS added to Dashboard.css.
Export network block extended with the three new fields.
2026-05-21 16:17:38 -04:00
f160eccdae fix(ui): scope empty-state color and font to dashboard context — suppress matrix-green bleed 2026-05-21 15:44:08 -04:00
cd3c1104b4 fix(ui): replace ad-hoc fingerprints empty state div with EmptyState component 2026-05-21 15:40:10 -04:00
28f26cc5f3 fix(ui): replace info-banner empty state in BehaviouralPrimitivesPanel with EmptyState 2026-05-21 15:23:33 -04:00
946636d8f4 feat(ui): wire icmp_error / icmp6_error fingerprint probes into AttackerDetail 2026-05-21 15:12:39 -04:00
a0f10d2c00 feat(ui): add renderers for ja4h, http2/3 settings, ja4-quic fingerprints
FingerprintGroup switch fell through to FpGeneric (raw JSON dump) for all
four new fingerprint_type values the ingester now produces. Add FpJa4h,
FpHttpSettings, FpJa4Quic components and wire them into the dispatcher;
also register their labels and icons in fpTypeLabel/fpTypeIcon.
2026-05-20 22:15:02 -04:00
80fff1efa4 fix(web): coerce fingerprint_type to string; sync frontend types and tests 2026-05-10 22:27:38 -04:00
724380901f fix(wizard): emit per-decky service config sections instead of prefix group
[decky.https] relied on ini_loader prefix-matching to propagate config
to decky-03/04/05 — silent and fragile. Now emits [decky-03.https],
[decky-04.https], [decky-05.https] explicitly so the INI is self-evident
and doesn't depend on pattern matching side-effects.
2026-05-10 01:00:43 -04:00
0653e500b5 feat(services): HTTP/2 + HTTP/3 support via Caddy reverse-proxy
Swap Werkzeug for Caddy as the protocol layer for http and https decoy
services. Flask keeps owning app logic (fake_app, custom_body, headers,
syslog) on 127.0.0.1:8080; Caddy terminates h1/h2/h2c/h3 on the wire
with real-world TLS/QUIC fingerprints.

- Add `multi_enum` FieldType to ServiceConfigField + _coerce
- Add `http_versions` field to HTTPService (h1/h2c) and HTTPSService
  (h1/h2/h3); selecting h3 emits UDP/443 port mapping in compose
- Rewrite both Dockerfiles with multi-stage Caddy binary copy +
  setcap for port binding as the logrelay user
- Entrypoints parse HTTP_VERSIONS JSON, render a Caddyfile, start
  Flask in background, wait for it, then exec Caddy
- https/server.py drops direct TLS handling; Caddy owns the cert
- Add ProxyFix to both server.py so Flask sees real attacker IPs
- Frontend: multi_enum checkbox-group renderer in ServiceConfigFields;
  FormValue union extended to string[]; compactPayload skips []
- Fix stale test_smtp_relay_schema_matches_smtp: relay schema is a
  superset of smtp, not equal; update assertions accordingly
2026-05-10 00:04:37 -04:00
ec5b49144e fix(ui): transparent input bg fallback so light-mode text is legible 2026-05-09 23:24:37 -04:00
8dde954559 feat(ui): restyle LLMTab with DeckyFleet/PersonaGeneration form vocabulary 2026-05-09 23:23:25 -04:00
d1478f900c fix(ui): remove unused _SENTINEL from LLMTab 2026-05-09 23:21:29 -04:00
39eb1ce5db refactor(ui): move LLM provider config into Config tab, remove standalone route 2026-05-09 23:20:11 -04:00
c66749209f feat(ui): LLMConfig panel + route (/realism-llm) + nav entry 2026-05-09 23:15:27 -04:00
1200ac9132 feat(stix): STIX→MISP download export (per-attacker + fleet)
Adds GET /api/v1/attackers/{uuid}/export/misp and
GET /api/v1/attackers/export/misp backed by misp_export.py, which
converts existing STIX bundles to MISP events via misp-stix
ExternalSTIX2toMISPParser. Fleet endpoint emits {response:[...]}
collection (one event per attacker). Frontend: STIX/MISP buttons on
AttackerDetail header and Attackers list. 13 new tests green.
2026-05-09 08:04:25 -04:00
e548be3c49 feat(web): wire EXPORT button to fleet STIX endpoint 2026-05-09 07:40:07 -04:00
915bc6d7ef feat(web): add Download STIX button to AttackerHeader 2026-05-09 07:24:59 -04:00
c4d6eb5bb3 feat(web): mitre_url deeplinks + lazy groups subpanel in TTPInspector
Every technique_id in TechniqueBar and TTPInspector now links to its
canonical attack.mitre.org page. The inspector drawer gains a GROUPS
subpanel that lazy-fetches the new /ttp/techniques/{id}/groups endpoint
and renders each MITRE-tracked intrusion-set with deeplink and aliases.

Centralizes TTP row interfaces into src/types/ttp.ts and API wrappers
into src/utils/ttpApi.ts to give the new GroupRef type a clean home and
avoid a third inline fetch declaration.
2026-05-09 06:57:10 -04:00
9675f4bf92 refactor(decnet_web/MazeNET): bump coverage floor after Inspector split
Suite is now 51 files / 259 tests, 25.68% lines / 21.43% branches.
Floor: lines 24->25, functions 21->22, branches 19->21,
statements 23->24. Inspector/index.tsx ends at 172 LOC, the only
other > 250 LOC file in MazeNET/ is NodeInspector (362) — the
node branch was the bulk of the original 606 LOC and its 7
add-service / tarpit form states stay co-located there.
2026-05-09 06:34:02 -04:00
4fbce6a8b0 refactor(decnet_web/MazeNET): split Inspector by selection type
Inspector.tsx (606 LOC) splits into Inspector/{NetInspector,
NodeInspector, EdgeInspector, ServiceInspector, index}.tsx plus
types.ts. The dispatcher (index.tsx) owns the title bar, the empty
state, the activeNetIds derivation, the pending-diff block, and the
topology-status block; each per-type panel takes only the props it
needs. NodeInspector keeps the 7 useStates for the add-service /
tarpit forms since they are node-only.

10 new dispatcher-level tests cover empty / node / net / edge /
service / observed-entity / internet-net / live-ops gating /
tarpit-controls / pending-diff. Selection type re-exported from
Inspector/index.tsx so MazeNET.tsx, Canvas.tsx, and
useMazeContextMenu.tsx keep their existing import path.
2026-05-09 06:33:12 -04:00
0a9a2f9021 refactor(decnet_web/AttackerDetail): trim shell + bump coverage floor
Drop unused icon/api/useEffect/Tag imports left behind by the
fingerprint, behaviour, and IntelPanel extractions. AttackerDetail.tsx
ends at 450 LOC across Phase 10 (down from 1652 / 73% reduction).
Coverage floor: lines 23->24, functions 20->21, branches 17->19,
statements 22->23.
2026-05-09 06:29:15 -04:00
4bd502d3bf refactor(decnet_web/AttackerDetail): lift IntelPanel
Move IntelPanel + IntelRow type + ProviderRow + VERDICT_TONE/fmtTs
helpers into AttackerDetail/IntelPanel/. AttackerDetail.tsx drops
from 680 to 449 LOC. New IntelPanel.test.tsx covers the loading,
absent (404), error (500), and ok states with MSW handlers.
2026-05-09 06:27:59 -04:00
e92d415304 refactor(decnet_web/AttackerDetail): lift behaviour panel block
Move BehaviouralPrimitivesPanel + 8 sub-components (BehaviorHeadline,
BeaconBlock, DetectedToolsBlock, TcpStackBlock, TimingStatsBlock,
PhaseSequenceBlock, AttributionBadge, KeyValueRow, StatBlock) plus
the OS_LABELS / BEHAVIOR_LABELS / TOOL_LABELS / BEHAVIOUR_DOMAIN_*
lookup tables and fmtOpt/fmtSecs into AttackerDetail/behaviour/.
AttackerDetail.tsx drops from 1220 to 680 LOC; existing
behaviour_panel test moves to behaviour/BehaviouralPrimitivesPanel.test.tsx
and now imports from the canonical location. The shell still
re-exports BehaviouralPrimitivesPanel for source compatibility.
2026-05-09 06:25:53 -04:00
1f3f58c42c refactor(decnet_web/AttackerDetail): lift fingerprint renderers + tests
Move 12 Fp* components, FingerprintGroup, getPayload, seqClassColor,
HashRow, fpType lookups, and UA color tables into
AttackerDetail/fingerprints/. AttackerDetail.tsx drops from 1652
to 1220 LOC; the orchestrator now imports the same helpers it used
to define inline. 10 new tests covering UA / HTTP-quirks / resumption
/ certificate / spoofed-source / TCP-stack / dispatch fallback.
2026-05-09 06:21:44 -04:00
b326d70852 refactor(decnet_web/Credentials): wire shell + bump coverage floor
Credentials.tsx: 487 -> 231 LOC. Page now composes CredsTable +
ReuseTable + useCredentials hook; URL-derived state (tab, query,
service, page) and selection/sort UI are the only concerns left
in the shell.
2026-05-09 06:12:58 -04:00
bf79581cc9 refactor(decnet_web/Credentials): extract CredsTable + ReuseTable + SortTh 2026-05-09 06:11:38 -04:00
e29a0094c9 refactor(decnet_web/Credentials): add useCredentials data hook 2026-05-09 06:10:53 -04:00
275fac5288 refactor(decnet_web/Credentials): extract types + helpers with tests 2026-05-09 06:09:57 -04:00
2c1ccec8fa refactor(decnet_web/SwarmHosts): wire shell + bump coverage floor
SwarmHosts.tsx: 513 -> 161 LOC. Page now composes EnrollmentWizard
+ useSwarmHosts hook; only the arm/confirm UI affordance and the
busy-set tracking remain in the shell.
2026-05-09 06:08:48 -04:00
780d395a46 refactor(decnet_web/SwarmHosts): add useSwarmHosts polled data hook 2026-05-09 06:07:38 -04:00
9def7fd22f refactor(decnet_web/SwarmHosts): extract EnrollmentWizard 2026-05-09 06:06:29 -04:00
3a8519b2a1 refactor(decnet_web/SwarmHosts): extract types + helpers with tests 2026-05-09 06:05:19 -04:00
31f4c54c32 refactor(decnet_web/Webhooks): wire shell + bump coverage floor
Webhooks.tsx: 642 -> 387 LOC. Page now composes FormRow + SecretModal
+ useWebhooks hook; toast policy is the only UI concern left in the
shell. Multi-select delete uses the hook's reload internally.
2026-05-09 06:04:14 -04:00
7408a04a90 refactor(decnet_web/Webhooks): add useWebhooks data hook 2026-05-09 06:02:37 -04:00
1ac64d2ae2 refactor(decnet_web/Webhooks): extract FormRow + SecretModal 2026-05-09 06:01:47 -04:00
44f4dd8c85 refactor(decnet_web/Webhooks): extract types + helpers with tests 2026-05-09 05:49:34 -04:00
ac64329a13 refactor(decnet_web/PersonaGeneration): wire shell + bump coverage floor
PersonaGeneration.tsx: 875 -> 357 LOC. Page now composes the data
hook + PersonaCard + PersonaEditor; bulk-import helpers stay in
helpers.ts; toast policy is the only UI concern left in the shell.
2026-05-09 05:48:16 -04:00
c1a65bf9a3 refactor(decnet_web/PersonaGeneration): add usePersonaGeneration data hook 2026-05-09 05:46:08 -04:00
97e72d975b refactor(decnet_web/PersonaGeneration): extract PersonaCard + PersonaEditor 2026-05-09 05:45:10 -04:00
a19d8bba17 refactor(decnet_web/PersonaGeneration): extract types + helpers with tests 2026-05-09 05:43:59 -04:00
6e0e1c204e refactor(decnet_web/MazeNET): wire hooks + bump coverage floor
Final integration step. The MazeNET page shell is now a thinner
composition of the existing module-level hooks (useMazeApi,
useMazeInteraction, useTopologyEditor, useTopologyStream,
useLayoutPersistor) PLUS the three new ones from this phase
(useFullscreenMode, useTopologyData, useMazeContextMenu).

- MazeNET.tsx: 980 -> 715 LOC. The fullscreen + body-class
  effects, the topology hydrate / SSE stream / deploy /
  flashErr plumbing, and the four context-menu builders are
  all gone from the shell.
- Page still owns the per-operation editor callbacks
  (removeNet/Node/Edge, duplicateNode, addServiceToNode, etc.)
  because they need direct access to setNodes/setEdges/setNets
  for optimistic patches alongside their REST calls — those
  setters are exposed by useTopologyData for that reason.

Coverage floor bumped after the phase:

  lines       17 -> 19
  functions   15 -> 17
  branches    13 -> 14
  statements  16 -> 18

Phase 5 final scoreboard: 37 test files, 172 tests, all green.
2026-05-09 05:39:32 -04:00
f33a011900 refactor(decnet_web/MazeNET): extract useMazeContextMenu
Lift the context-menu builder out of the page shell. The hook
owns ctxMenu open/close state and exposes one builder per
surface (node / net / edge / canvas); the actual operations come
in via callbacks so the page keeps its optimistic-patch logic
unchanged.

- New MazeNET/useMazeContextMenu.tsx
- useMazeContextMenu.test.ts covers menu lifecycle (open/close),
  node-menu items, observed-entity locking, internet-net
  delete-disabled, canvas-menu Add subnet/DMZ items, and the
  edge-menu Remove invocation.
- Wiring into MazeNET.tsx lands next.
2026-05-09 05:34:58 -04:00
5f2a3f4629 refactor(decnet_web/MazeNET): extract useTopologyData
Lift the canvas data plane off the page shell. The hook owns:

  GET /topologies/:id            (hydrates nets/nodes/edges + meta)
  GET services + archetypes      (catalogs, with bundled fallback)
  POST /topologies/:id/deploy
  /topologies/:id/events SSE     (open only when active/degraded)
  flashErr() banner timer        (auto-clears actionErr after 4s)

State setters for nets / nodes / edges are returned so the
per-operation callbacks living in the page can optimistically
patch local state alongside their REST calls (matches the
existing pattern; wholesale lift would mean dragging every
mutation along too).

- New MazeNET/useTopologyData.ts
- useTopologyData.test.ts covers hydrate, loadErr surfacing,
  streamEnabled gating on active/degraded, onDeploy success +
  error paths, and the flashErr 4s auto-clear with fake timers.
- Wiring into MazeNET.tsx lands in the next commit.
2026-05-09 05:33:19 -04:00
212feb49e2 refactor(decnet_web/MazeNET): extract useFullscreenMode
Lift the four fullscreen-related side-effects off the page shell.
The hook owns:

  1. body class toggle so page CSS can hide its chrome
  2. browser fullscreen API request/exit (failures ignored)
  3. fullscreenchange listener so F11/Esc from outside our button
     keeps internal state in sync
  4. Esc keystroke handler

Returns { fullscreen, setFullscreen, toggle }.

- New MazeNET/useFullscreenMode.ts
- useFullscreenMode.test.ts (jsdom) covers initial toggle, body
  class lifecycle, Esc-to-exit, and unmount cleanup.
- MazeNET.tsx loses ~30 LOC of inline state + effects.
2026-05-09 05:31:39 -04:00
171e20e427 refactor(decnet_web/Config): wire hook + bump coverage floor
Final integration. The page shell is now a thin composition of
useConfig + the previously-extracted children:

- Config.tsx: 989 -> 131 LOC. Page owns only the activeTab state
  (and the "drop the users tab if the server didn't send users"
  effect). Every form lives inside its tab; toast wiring lives
  in AppearanceTab; window.alert calls live inside UsersTab.
- Tabs receive their `onSave* / onAddUser / ...` callbacks
  directly from the hook — no intermediate wrapper handlers.

Coverage floor bumped after the split:

  lines       14 -> 17
  functions   13 -> 15
  branches    11 -> 13
  statements  13 -> 16

Phase 4 final scoreboard: 34 test files, 156 tests, all green.
2026-05-09 05:27:47 -04:00
4a9cd90f90 refactor(decnet_web/Config): extract AppearanceTab
APPEARANCE panel — accent-color picker — into its own tab. State
is local since no other tab cares about the value; localStorage
persistence + the document.documentElement[data-accent] mirror
move along with it.

- New Config/tabs/AppearanceTab.tsx
- AppearanceTab.test.tsx covers the matrix default, reading the
  saved accent from localStorage on mount, and the click-to-flip
  flow writing both localStorage and the html data-accent attr.
2026-05-09 05:26:26 -04:00
ccae1612bd refactor(decnet_web/Config): extract GlobalsTab + DangerZone
GLOBAL VALUES panel + the developer-mode-gated DANGER ZONE
(reinit) into one tab file. Two stacked panels because they're
the two pieces of UX you ever see together on the globals tab;
splitting them into separate components would force the page
shell to re-pick the gating predicate.

- New Config/tabs/GlobalsTab.tsx (mutation-interval + DangerZone
  inline, since DangerZone is reinit-specific and won't be reused)
- GlobalsTab.test.tsx covers interval-format validation, the
  DANGER ZONE gating on developer_mode, the two-step reinit
  confirm flow, the totals chip ("PURGED: N logs, N bounties,
  N attacker profiles") on success, and viewer-mode rendering.
2026-05-09 05:25:49 -04:00