Skip to content

fix: dashboard sign-out, safelist delete, FastNetMon vector, clipboard failures - #148

Open
lance0 wants to merge 5 commits into
mainfrom
fix/issue-triage-144-147
Open

lance0 wants to merge 5 commits into
mainfrom
fix/issue-triage-144-147

Conversation

@lance0

@lance0 lance0 commented Sep 26, 2026

Copy link
Copy Markdown
Owner

Fixes four reported issues, each root-caused in the running stack rather than patched at the symptom. No release/tag intended here.

#146 — removing a safelist prefix returned "API error 404"

nginx is not involved: it preserves %2F in every proxy_pass form (verified against a stub upstream). The Next.js catch-all proxy receives percent-decoded route segments and rejoined them with /, so DELETE /v1/safelist/8.9.8.9%2F32 reached the backend as /v1/safelist/8.9.8.9/32, missed the single-segment route and 404'd — the prefix stayed safelisted.

  • frontend/app/api/prefixd/[...path]/route.ts: re-encode each segment (upstreamPath), preserving the search string for GET.
  • src/api/routes.rs: DELETE /v1/safelist/{*prefix} so a literal slash also matches (curl users, proxies that decode %2F).
  • tests/integration.rs: regression tests for the encoded and literal-slash forms; docs/api.md documents both.

#147 — no way to sign out (and no role-based permissions)

/v1/auth/login and /v1/auth/me return the operator flat (operator_id, username, role), but the client read data.operator, which is undefined. UserMenu returns null without an operator, so a signed-in user saw no menu and could not sign out or switch roles; role was also undefined, so usePermissions denied admin actions (safelist, users, playbooks, reload) to real admins.

  • frontend/lib/auth.ts: map the wire shape to the client Operator; getCurrentUser returns null for an unexpected payload.
  • frontend/hooks/use-auth.tsx: isAuthenticated: Boolean(operator).
  • frontend/__tests__/auth-lib.test.ts: covers both endpoints, the 401 case, and a wrong-shape payload.

#145 — FastNetMon SYN floods reported as udp_flood

Vector inference matched the first protocol word anywhere in the details, so FastNetMon's idle line outgoing udp traffic: 0 mbps beat the actual attack type.

  • scripts/prefixd-fastnetmon.sh: drop lines that report a zero metric, match tokens on word boundaries (ack no longer matches inside "packets"), prefer TCP flag vectors over udp.
  • tests/fastnetmon_vector.rs: drives the script with a stub curl and asserts the payload vector — covers the reporter's details, a real udp flood, tcp_ack with "packets per second" text, and all-zero details.
  • docs/detectors/fastnetmon.md: table + matching rules.

#144 — incident report not in the clipboard

navigator.clipboard only exists in secure contexts, so the copy handlers threw on a plain-HTTP dashboard: nothing copied, no error shown.

  • frontend/lib/clipboard.ts: Clipboard API in secure contexts, hidden-textarea fallback otherwise, clipboard-write permission check, boolean result.
  • Four call sites (incident report, FlowSpec rule, event id/external id, webhook endpoint) now report Copy failed — clipboard unavailable.
  • docs/deployment.md: clipboard requires a secure context.

Measured in Chrome: insecure non-localhost origins report clipboard-write: denied and Chrome silently ignores even the legacy execCommand("copy") (returns true, clipboard unchanged), so production dashboards need HTTPS for clipboard actions — the reporter's workaround was the right one. The fallback still helps browsers that permit legacy writes.

Verification

  • cargo test --features test-utils: 250 unit + 4 script + 162 integration + 16 postgres pass (17 ignored: GoBGP/Docker).
  • bun run test: 97 tests. bun run build (production) succeeds. cargo fmt --check and cargo clippy --all-targets --features test-utils -- -D warnings clean.
  • Browser smoke tests against a real prefixd + PostgreSQL + mock BGP, using the production frontend build: user menu/sign-out/role switch, safelist add+remove from the Admin page, incident-report copy verified by reading the X11 clipboard, and the HTTP-origin failure toast.

Fixes #146
Fixes #147
Fixes #145
Refs #144

Next.js hands the catch-all proxy already percent-decoded segments, so
`DELETE /v1/safelist/8.9.8.9%2F32` was re-sent as `/v1/safelist/8.9.8.9/32`
and missed the single-segment route: the dashboard reported "API error 404"
and the prefix stayed safelisted.

Re-encode each segment before forwarding, and make the backend route a
wildcard capture so clients and proxies that decode `%2F` (curl, other
reverse proxies) work against it too.

Verified: proxied `%2F` delete 404 -> 204, add/remove of 203.0.113.77/32
through the dashboard UI, plus regression tests for both slash forms.

Fixes #146
/v1/auth/login and /v1/auth/me return the operator flat
({operator_id, username, role}), but the client read `data.operator`, which is
undefined. The user menu (user-menu.tsx returns null without an operator) never
rendered, so a signed-in user had no way to sign out or switch roles, and every
`usePermissions` role check denied admin actions to real admins.

Map the wire shape into the client Operator and make isAuthenticated a strict
boolean so a partial payload cannot read as authenticated.

Verified in a browser: header shows the username with Notifications/Sign out,
sign-out lands on /login, a viewer login shows the viewer role and hides the
Admin nav.

Fixes #147
…vector

Vector inference matched the first protocol word anywhere in FastNetMon's
details, so the idle line `outgoing udp traffic: 0 mbps` won over the actual
attack type and SYN floods were reported as udp_flood.

Ignore lines that report a zero metric, match tokens on word boundaries (so
`ack` no longer matches inside "packets"), and prefer TCP flag vectors over udp.

Verified: the reporter's details yield udp_flood with the old logic and
syn_flood with the new one; covered by tests/fastnetmon_vector.rs, which drives
the script with a stub curl.

Fixes #145
…ying

navigator.clipboard only exists in secure contexts, so copy buttons on a
plain-HTTP dashboard threw a TypeError, copied nothing and said nothing --
"incident report not in clipboard" with no visible error.

Route every copy through lib/clipboard.ts: use the Clipboard API when the
context allows it, fall back to a hidden textarea, and check the
clipboard-write permission first, because Chrome reports `denied` for
insecure origins and still returns true from the legacy copy command.
Callers now surface "Copy failed -- clipboard unavailable".

Chrome blocks clipboard writes for plain-HTTP origins other than localhost, so
production dashboards need HTTPS; documented in docs/deployment.md.

Verified in Chrome: on a secure origin the incident report is copied verbatim
(read back off the X11 clipboard), on an HTTP origin the failure toast shows and
nothing is claimed as copied.

Refs #144
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant