Agents and History pages now use the primitives FilterBar with
URL-synced filter state via useFilterUrl. Adds test setup infra
(vitest + jsdom) and page-level test shells.
Store the fleet-join TOTP seed as AES-256-GCM ciphertext instead of
a SHA-256 hash. Hash-only can never verify a time code without the
host disclosing the seed — which made the 2FA a second cleartext
shared secret. Seed crosses the wire once, at token creation over
the admin-authenticated channel; the join request carries only the
6-digit code.
TriggerProcessScan was building an AgentCommand without Source, which hit
the agent_commands_source_check constraint every time. Every other
command-creation site sets this correctly; this one got missed.
Thanks to QiTechCo for the catch during v0.2.8.0 dev testing.
Co-Authored-By: Xzaviar <user@QiTechCo.qitc.io>
Delivery-tracker persistence failures (ack/receipt/confirmed Save) were
local-only log lines; a tracker that cannot persist risks double-delivery
or replay-rejection after a crash, so they now tee to the server event
buffer via TeeLogger. The untagged 'Command rejected' line gains ETHOS
tags. install.sh restorecon calls lose their 2>/dev/null — SELinux
relabel failures now print a tagged warning instead of vanishing.
Extract the repeated cn('badge', getStatusColor/getSeverityColor) span
into primitives; port Updates, Agents, Docker, LiveOperations,
PackageDetail, AgentUpdatesEnhanced. Docker's local colour maps deleted —
its image-lifecycle statuses join the central getStatusColor map, and its
severity palette aligns with the app-wide one. AgentUpdatesEnhanced's
divergent local severity palette replaced by the shared map.
The compiler inlines middleware.AuthMiddleware across packages, renaming
its closure to "<caller>.AuthMiddleware.func1" — the package-qualified
match ("middleware.AuthMiddleware") never hit, every agent-JWT route
was flagged as naked, and AuditAndExit refused boot (122 container
restarts overnight). Classify on bare names, web before agent since
WebAuthMiddleware contains AuthMiddleware. Regression test now uses the
real production middleware instead of a same-package fake.
The privileged executor no longer trusts its keyring, agent_id, or
replay-guard state by content alone. Before any read, each trust input
and its immediate parent must be root-owned, not group/other-writable,
and not a symlink — otherwise deny with EXIT_TRUST_PATH (26), fail
closed. This makes the gate self-defending: a packaging or installer
mistake that leaves a trust path writable can no longer be parlayed
into key injection, token rebinding, or replay-record clearing.
An agent_id file that exists but fails validation is a denial, never a
fall-through to the next location. The replay dir is validated after
ensure-exists so a pre-planted attacker-owned dir is refused, not
adopted.
The limiter is in-memory; a restart clears all counters, so an attacker
who can force one gets a fresh budget. For 60s after boot every limit
runs at half its configured budget (min 1), making a restart strictly
worse for the attacker while per-key limits keep reconnecting agents
comfortable. Restart semantics documented in OPERATIONS.md §6.
classifyFailure is now the single source of truth for which polling
failures are terminal (ErrRefreshTokenInvalid, ErrMachineMismatch —
wrapped or bare) vs transient. delayForFailure picks the curve: flat
10-minute delay for terminal states awaiting operator intervention,
jittered exponential (calculateBackoff) for everything else. The
terminalBackoff bool is gone from the polling loop. Task file said
to delete itself when this landed — done.
A token in the query string leaks to shell history, process lists, and
server access logs. The install endpoint now reads X-Registration-Token;
a query-string token is refused with guidance and is never echoed back
or logged. Server-built and web-UI install commands updated (curl -H,
irm -Headers).
A version pinned at approval (selected_version) survived the install,
so the next scan compared against the stale pin and plain Approve became
a no-op after the first install. Clear the pin and its metadata source
marker inside the transition tx once the package reaches installed;
subsequent scans track latest available again.
Both previously logged hash_verification_skipped and returned nil when the
server had registered an expected hash the installer cannot verify. Now:
empty expected hash errors (consistent with dnf/apt), and a registered hash
without an implemented verifier blocks the install instead of pretending.
No behavior change today — the server only registers hashes for npm/pypi,
and handlers skip VerifyHash on empty hash — this closes the latent path.
Walk every Gin radix tree at startup, classify each route's handler chain
(agent JWT / web JWT / metrics token), and refuse to boot if any route
lacks auth and is not on the explicit public allowlist. Adding a path to
PublicPathSet is a reviewable act.
Covers param routes, nested groups, and per-method trees (tested).
Container images table (per-agent-group) and runtime container state table
both use SortableTable with shared sort state. ~120 lines of raw table markup
removed. Stacks grid unchanged — card layout is the right shape for that.
ChatTimeline: when updateLink exists but packageName is falsy, show 'View Update'
instead of hiding the link entirely. Updates: clickable Agent/Update links in
command history rows (UI-AGENT-UPDATE-CROSSLINKING).
Every hook that polled had its own magic number. POLL tiers (LIVE/DASHBOARD/
DETAIL/OVERVIEW/STATIC/HEALTH) declared once in polling.ts, consumed everywhere.
Mechanical change, no behavior shift.
Column-driven table component under primitives/ — sortable headers, checkbox
selection, empty/loading states, pagination slot. Agents.tsx rewired as
proof-of-integration; ~220 lines of raw table markup collapsed into column
definitions + <SortableTable />. Same output, same behavior.
Rename CVE/vulnerability language to advisory/threat/fix across the stack:
- Dashboard: installed_cve_count→open_threat_count, security_update_count→available_fix_count
- Update detail: Known Vulnerabilities→Security Advisories
- AdvisoryType() helper for human-readable advisory ID prefixes
- clearVulnsOnInstall on installed transition with per-advisory security event logging
- StatsHandler takes checkInInterval for online/offline threshold
- AttentionPanel re-keyed on open-threats / available-fixes
Outbound-only: no listener, no control surface — RedFlag's journal is
authoritative, Wazuh is a best-effort mirror. DGRAM to the local agent's
queue socket, ECS-formatted, rule IDs mapped from security event types.
Opt-in via REDFLAG_WAZUH_ENABLED=true; disabled = socket never opened.
- Sink interface on SecurityLogger (mirror after journal write)
- Write deadline + one reconnect, then drop-count with rate-limited log
- 3 tests: frame/ECS shape, absent-socket non-blocking, unknown→generic
- NewOrchestrator initialises a log-only TeeLogger (nil buffer) so the
Windows service path never carries a nil logger into executeScan
- rpmEVRAhead returns false when all epoch/version/release components
compare equal; adds test cases for "0:2.0-1" vs "2.0-1" normalisation
- processDesktopSelfToken splits replayCheckAndRecordAgent into
replayCheckAgent (before install) + recordAgentTokenConsumed (after
successful install) so a transient install failure does not permanently
consume the token
- Comments on helperSelfStagingPath (Go) and DEFAULT_HELPER_SELF_SOURCE
(Rust) name each other as the cross-language counterpart
Extract serverSetupRequest type and resolveSetupSigningKeys(): when no keys
are provided the server generates a fresh Ed25519 pair (existing behaviour);
when a private key is provided it is validated and the public key derived from
it (public key may be omitted or supplied for cross-check). Mismatched pairs
are rejected 400. Remove configure-secrets route from welcome-mode router
(was only usable with Docker socket mounted, unreachable in that mode).
Add inferPublicURL() helper to fill publicURL from X-Forwarded-* headers when
the operator omits it. pq.QuoteLiteral() used for password in ALTER USER.
Tests: generate-when-missing, use-provided-pair, reject-mismatched-pair.