Watch
1
0
Fork
You've already forked RedFlag
0
Commit graph RedFlag/server/internal
Author SHA1 Message Date
Fimeg
8b1884eadc fix: nil logger on Windows scan path, rpmEVRAhead equal-version false positive, desktop-self replay token burned before install
- 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
2026-06-11 02:01:43 -04:00
Fimeg
2da1e92fe9 feat: setup accepts operator-supplied signing keypair + validation
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.
2026-06-11 02:01:43 -04:00
Fimeg
244d9091ee feat: process explorer — on-demand /proc scanning with full osquery parity
Agent-side: reads /proc for all PIDs with 25+ fields (identity, resources,
state, disk I/O, elevation) plus related data on drill-down (open files,
sockets, pipes, env keys, memory map, namespaces, listening ports). Pure
/proc reads, no subprocess spawns.

Server-side: dedicated tables (agent_process_snapshots, agent_processes,
agent_process_related) with JSONB for related data. On-demand scan via
scan_processes command, last-10-snapshot retention. Four endpoints:
report, get latest, get detail, trigger scan.

UI: new Processes tab in agent detail with sortable/filterable table,
search by name/cmdline, state/user filters, and ProcessDetailModal with
tabs for Overview, Network, Files, Environment, Memory, Namespaces.
2026-06-11 02:01:43 -04:00
Fimeg
b9e083c4f7 test+fix: supply-chain consumer/OSV coverage; local-approve and update handler work
OSV fail-closed paths, safeTokenFilename traversal, bind check,
disallowed package types covered in agent/internal/supplychain.
Artifact hash resolution and dnf scanner test fixes. Server update
handlers extended for the local-approve flow with OSV version test
coverage.
2026-06-11 02:01:42 -04:00
Fimeg
4896fb6856 ci: Gitea Actions pipeline — release gate, embedded UI build, guided release script
ci.yml: vet, race tests, clippy, full web build, AI-attribution and
action-pin enforcement. release.yml: gate job verifies tag against
versions.go/docker-compose/Cargo/CHANGELOG, forward-only and on public,
before anything builds; web UI staged into the embed path (gitignored
dist made a bare go build ship an empty dashboard); binaries and docker
image must self-report the tag; release created via Gitea's own API.
scripts/release.sh is the operator path: checks runner, secret, branch,
versions, changelog — asks before every mutation, watches the run after.
bump-version.sh gains current-version display, dirty-tree warning,
duplicate check, changelog check, confirmation. build-secure-agent.sh
retired (bare go build, no version injection, single Makefile caller).
2026-06-11 02:01:42 -04:00
Fimeg
2f3363cbce feat: post-upgrade attestation — new binary proves the swap took
Old binary drops a marker (command_id, from/to) once the swap is
committed, on both the helper path and the legacy path. New binary
checks it at startup: running >= target just clears the marker
(check-in confirm still owns success); short of target means the swap
failed or rolled back, so it files a failed update_agent report under
the original command_id and the server clears is_updating right away
instead of sitting out the stuck-update timeout. Marker survives
failed reports for retry, drops on 409 or after 24h.
2026-06-11 02:01:42 -04:00
Fimeg
2084019be7 fix: grant CAP_SYS_PTRACE on agent binary for display/process discovery
The agent runs as redflag-agent user and cannot read /proc/[pid]/environ
from the logged-in user's session processes (owner-only permissions).
Without this, discoverSessionDisplay() finds nothing — screenshot fails
on Wayland because XDG_RUNTIME_DIR/WAYLAND_DISPLAY are never discovered.

- Installer: setcap cap_sys_ptrace=eip after placing the binary
- Helper (agent self-update): restore setcap after atomic replace,
  since rename() creates a new inode and strips file capabilities

Non-fatal if setcap is unavailable — agent degrades gracefully,
just loses screenshot and per-process telemetry.
2026-06-11 02:01:42 -04:00
Fimeg
411f25b1b7 v0.2.7.0 2026-06-11 02:01:42 -04:00
Fimeg
aeff8a67a8 fix: desktop build uses Node 20 stage; tray installs + autostarts by default 2026-06-10 09:45:24 -04:00
Fimeg
1241c1ef01 feat: embed web UI in server binary + local trigger-scan write endpoint
Server becomes self-contained: web/dist embedded via go:embed
(server/internal/webui), SPA served from the binary with JSON-404 guard on
/api paths, nginx web container removed from compose (31336 now maps to the
server). Clean checkouts without the UI copy build API-only.

Agent local API gains its first write endpoint, POST /v1/actions/trigger-scan
(FEAT-002 write path): group-ACL authorized, single-flight, 202/409/503
semantics. Registered agents run the same HandleScanUpdates path as a signed
scan command (empty command_id, no ack tracking); standalone agents scan
through the orchestrator into the local read model only. Also repairs
localapi tests left uncompilable by the desktop-provider parameter.
2026-06-10 08:38:18 -04:00
Fimeg
c3d84ec7f9 feat: UI primitives + agent version bump
- PageState, Modal, Pagination, StatCard primitives
- Migrated Dashboard, Updates, LiveOperations, Agents, Docker, TokenManagement
- AgentUpdate, AgentUpdatesModal now use Modal primitive
- Version bump to 0.2.6.9
2026-06-08 19:40:06 -04:00
Fimeg
7cbf174652 feat: unified event timeline, desktop bundling, UI primitives
Unified timeline:
- client_errors bridge to system_events (component='client')
- Admin action audit middleware on /admin/* routes
- History page filters: type, severity dropdowns
- ChatTimeline accepts externalType/externalSeverity props

Desktop app bundling:
- Docker: Tauri builder stage (Rust + Node + webkit2gtk)
- Server signs desktop binary at startup, serves via /api/v1/desktop/:arch
- Install script downloads + verifies desktop binary (Step 7c)
- Agent spawns desktop as child process, monitors + restarts on crash
- Desktop config: enabled, max_restarts, restart_delay_sec
- Session detection: /proc environ scan (Linux), query session (Windows)
- Desktop health: POST /v1/desktop every 30s from tray app
- /v1/status includes desktop running/pid/enabled state

UI primitives:
- Modal, PageState, Pagination, StatCard components
- Dashboard, Updates, Agents pages refactored to use primitives
- Novell aesthetic preserved throughout
2026-06-08 19:09:58 -04:00
Fimeg
c0a717ab26 fix: README, .env.example, ErrorBoundary, client-logger, HEALTHCHECK, Docker hygiene
- README: version v0.2.6.8, corrected stale gate claim, updated changelog
- .env.example: merged two competing files into one, deleted bootstrap duplicate
- ErrorBoundary: new component wrapping app, prevents white-screen crashes
- Layout sidebar: version display from /api/health, Docs link to GitHub
- client-logger: debug/trace logger gated behind localStorage.redflag_debug=1,
  routes through existing /logs/client-error server endpoint (ETHOS #1)
- All web console.log calls rerouted through client-logger instead of deleted
- Server health endpoint returns version field
- Server accepts client_debug/client_trace in error_type validation
- Dockerfiles: pinned alpine:latest->3.21, nginx:alpine->1.27-alpine,
  added HEALTHCHECK directives
- docker-compose: healthcheck blocks for server and web services
- .dockerignore: created to slim Docker build context
2026-06-08 18:23:39 -04:00
Fimeg
ab6fc48925 feat: v0.2.6.8 — dark/light tray app theme, version bump
LocalAgentApp: dark default with ☀/☾ toggle; palette-driven inline
styles (DARK/LIGHT objects) so both modes work without Tailwind dark
mode configuration. Dense Novell-style layout: status strip, section
headers with red accent bar, monospace identifiers, dot indicators per
scanner status. No Tailwind class changes — web/package-lock.json
untouched.

Version bump 0.2.6.7 → 0.2.6.8.
2026-06-08 17:22:01 -04:00
Fimeg
ffffe9b956 feat: FEAT-002 local agent API, desktop tray spine, screenshot handler
Slices 1-3 of the local agent IPC surface:
- Read model (local_status.go, loop wired): update counts, scanner status,
  check-in state, token receipt counts — no token material exposed
- Local IPC (localapi/): Unix socket (group=redflag-local, 0660) + Windows
  named pipe (SDDL: LocalSystem/Admins/RedFlagLocal); five read-only endpoints
- `redflag-agent -local-status` CLI probe of the local API surface
- Screenshot capture handler (screenshot.go, dispatch wired)
- Tauri desktop spine (desktop/): tray icon, left-click window, local IPC reader
- Desktop React entry (web/src/desktop/LocalAgentApp.tsx, index.desktop.html,
  vite.desktop.config.ts)
- Installer group provisioning: linux.sh creates redflag-local, sets
  SupplementaryGroups; windows.ps1 creates RedFlagLocal security group
- Server-side: screenshot receipt handler on agents, updates handler additions
- web/package.json: @tauri-apps/api + tauri CLI dev dep added
2026-06-08 17:08:58 -04:00
Fimeg
b0e2a77528 fix: pin installed version instead of clearing on install
After successful install (confirm_dependencies or capability receipt),
pin the installed version as selected_version instead of clearing it.
This prevents subsequent scans from silently advancing to a newer
unapproved version. The pin is the enforcement mechanism.
2026-06-08 16:07:33 -04:00
Fimeg
7d089ab5d2 v0.2.6.7 — GATE-006 D, version row OSV check
- evaluateSupplyChainHold checks version row osv_status for target
  version, closing hole where we OSV-check one version but install
  another.
- confirm_dependencies uses TransitionByPackageFrom with source-state
  guard (StatusInstalling → Installed/Failed).
- Clears pinned version on successful install.
- Version bump to v0.2.6.7.
2026-06-08 16:01:05 -04:00
Fimeg
14cd0884b4 fix: consolidate heartbeat, lower dashboard polling
- useHeartbeatStatus reads from agent metadata (single source of
  truth) instead of separate endpoint (eliminated split-brain).
- Online/offline threshold fixed to 10min to match server (was 15min).
- Dashboard polling lowered from 30s to 15s.
- Toggle feedback: invalidate agent queries immediately, clear
  loading state after 2s.
- Removed command-table fallback from GetHeartbeatStatus endpoint,
  dead helper functions, and unused GetRecentHeartbeatCommands.
2026-06-08 16:01:01 -04:00
Fimeg
f83f5a0960 fix: Windows install path — ACL repair, host/port, CRLF
- Install script repairs config ACL before detection (unreadable
  config.json caused false 'not installed' and 409 re-registration).
- Config permissions use stable SIDs (SYSTEM, LOCAL SERVICE,
  Administrators) instead of localized account names.
- Install URL preserves port for non-localhost hosts.
- Server bind address vs reachable host distinction fixed.
- Windows install script served as CRLF.
- Installer tolerates missing Ed25519 verifier.
2026-06-08 16:00:01 -04:00
Fimeg
8ced1d5a47 fix: make Windows installer tolerate missing Ed25519 verifier 2026-06-08 10:47:07 -04:00
Fimeg
7ae7429878 fix: bake reachable host into install script, not server bind addr
getServerURL returned localhost:31337 (the server's own bind address), so
a remote agent's binary/manifest/config fetches pointed at itself and
failed with "Unable to connect". Prefer the host the client actually
reached us on (Host + X-Forwarded-Proto, which nginx forwards), falling
back to PublicURL then the configured bind addr. Also set charset=utf-8 so
irm stops mangling the ✓/⚠ glyphs.
2026-06-08 09:15:26 -04:00
Fimeg
495d5fb3a7 fix: serve Windows install script as CRLF
Windows PowerShell 5.1's parser chokes on here-strings in LF-only .ps1
files — the embedded config template blew up with a cascade of
"Unexpected token ':'". Convert the windows script to CRLF at serve time
(parses clean in 5.1 and 7; .ps1 should be CRLF anyway). Linux stays LF.
2026-06-08 09:07:43 -04:00
Fimeg
f811b47e0b fix: Windows one-liner self-authenticates again (irm | iex)
The Windows installer relied on a Mandatory=$true -Token param, but the
one-liner ran the script with no -Token -> PowerShell dropped to an
interactive prompt and the install looked hung.

Bake the token/server into the rendered script like the Linux template
already does, drop param() and #Requires (both no-op under iex; runtime
admin check still enforces elevation), and switch the command to
'irm ... | iex' so the body actually pipes. -Token/-Server/-Skip become
RF_TOKEN/RF_SERVER/RF_SKIP_SERVICE_INSTALL env overrides.
2026-06-08 08:44:27 -04:00
Fimeg
40f57edefc v0.2.6.4 2026-06-08 08:14:24 -04:00
Fimeg
b09453e849 install the version that cleared the soak window, not the bleeding edge
the supply-chain check was judging available_version while the gate could
install a different one. re-run OSV against the version we actually install
(operator-pinned, else gated), and auto-pin the newest soak-aged clean
version when the gate's enforced — forward-only, operator force-pin wins.
2026-06-08 08:14:20 -04:00
Fimeg
f136c12902 shed agent writes when the db pool runs dry
last half of S8. the pool bump and bounded background took most of the pressure
off, but a full fleet can still pin every connection. now there's a valve:

- middleware reads db.DB.Stats(); at the saturation threshold it answers 503 +
  retry-after instead of letting writes queue into a deadlock
- fails open — unlimited pool never sheds, default only trips at 100% in-use.
  tunable via REDFLAG_DB_SHED_UTILIZATION and _RETRY_AFTER_SECONDS
- wired to the agent write group only. health, metrics, auth, register/renew
  and the dashboard stay reachable so you can still watch it and log in while
  it's hot

table-driven test across the branches, race clean.
2026-06-07 21:16:53 -04:00
Fimeg
cf061a13a1 seal the settings path so secrets land encrypted
the encrypt/decrypt hooks existed but the write path wrote values straight to
the column, so a sensitive setting would have gone in as plaintext. closed it:

- sensitive values serialize + encrypt before they persist (base64 aes-gcm over
  the json), and updates now carry the is_encrypted flag through
- non-sensitive settings are untouched — still plain json, same as before
- audit log redacts sensitive old/new values instead of recording them raw

round-trip test proves sensitive values never hit the column in plaintext and
still decrypt back; non-sensitive stay readable. no backfill needed — nothing
writes secrets through this path yet, the defaults are empty.

builds clean, vet quiet, tests pass.
2026-06-07 21:16:39 -04:00
Fimeg
f7882241c7 give the system a window you can scrape, and patch a migration that couldn't run twice
OBS-001A: an authenticated /metrics so something other than a human reading
logs can watch the box. no new dep — emits prometheus text straight from the
counters we already keep:

- /metrics behind a dedicated bearer token (sha-256 hash stored, plaintext only
  from REDFLAG_METRICS_TOKEN for bootstrap). constant-time compare, rotates
  without a restart, disabled by default, never an open route
- exports db pool, taskrunner snapshot, scheduler + queue, breaker state, and
  the deferred-advisory count — read live on each scrape, bounded labels only
- settings + migration 054 for observability.metrics_enabled / _token_hash

also fixed migration 046 — it added a column and an index without IF NOT EXISTS
and backfilled off a column it then drops, so it couldn't survive a second run.
guarded every step; the idempotency lint is green again. only the migrations
the runner hasn't recorded see the change, so live dbs don't care.

builds clean, vet quiet, new tests pass.
2026-06-07 20:59:29 -04:00
Fimeg
ae411cf5d4 say it out loud when the advisory feed goes dark
the breaker fails open so a down osv never blocks a patch — good — but the
auto-confirm gate is fail-closed, so a dark feed quietly stops auto-approval
and parks packages unvetted. that truth was sitting in the logs where nobody
looks. now it's a banner.

- /api/v1/health/advisory: breaker state + count of deferred packages
  (self-healing — a successful recheck clears the flag) + a degraded flag
- amber bar in the layout, only when degraded: "feed offline, auto-approval
  suspended, manual still works." says feed-down isn't patching-down
- narrow slice of the gate-visibility work; the full posture panel stays in
  its own session

builds clean, tsc's happy.
2026-06-07 20:17:18 -04:00
Fimeg
e99ad6e8c7 gave the server a breaker so a sulking upstream can't drag it down
osv.dev or repology going dark used to mean every check sat there burning its
30s timeout, one after another. now there's a breaker (ported the agent's, it's
already proven) wrapping both:

- osv: one breaker over the batch + single-query paths. trips after 5 fails in
  a minute, fails open while tripped — an unreachable advisory feed never blocks
  a patch. that's the whole sovereignty bet
- repology: same deal, best-effort, 404 doesn't count against it
- both visible at /health/tasks so you can see them trip and heal

db-pool shedding (503 + retry-after) is the other half — left it for later, the
pool bump + bounded background already took most of that pressure off.

race detector's clean.
2026-06-07 19:45:36 -04:00
Fimeg
d25f6ea030 swept the cobwebs, stopped re-dialing the same three hosts
three more off the scale list:

- rate-limit map now gets swept on a cadence (taskrunner.Every) instead of
  growing forever — nobody was calling the cleanup. first old ticker moved
  onto the runner
- subsystem load was one db query per agent at startup; now it's a single
  ANY($1) for all the online ones. 100 agents, 1 query
- outbound http clients (osv, registries, upstream, agent) were inheriting
  the stock transport that keeps 2 idle conns per host — so every scan burst
  re-dialed. shared tuned transport now, 10 per host, 90s idle

builds clean both modules.
2026-06-07 19:35:32 -04:00
Fimeg
82018bfb80 taught the rocks to stop tripping over each other under load
server was sized for a campfire, not a fleet. 25 db connections, every agent
report flinging goroutines into the void, the syncer plodding one repo at a
time while clutching a lock nobody needed. loosened the choke points:

- db pool 25 -> 100 + connection lifetime, all env-tunable
- bounded pool for the report-path fire-and-forget work; /health/tasks to
  watch it breathe. no more unbounded goroutine spray per report
- upstream syncer runs concurrent now, dropped the dead mutex around repology
  fetches, reconciler single-flights instead of locking through the whole crawl
- scheduler caps jobs per tick so an aligned fleet can't stampede the db
- swatted a context-cancel bug that was quietly killing immediate syncs

builds clean, race detector's calm.
2026-06-07 19:17:08 -04:00
Fimeg
5b1a16ca3e v0.2.6.2 — osv scans moved to detection, soak gate grew up
approval stopped re-scanning osv; it just reads what detection already
found. soak gate + age gate are real settings now (env→db→default), and
the dead soak-override column + table got composted.
2026-06-07 14:44:41 -04:00
Fimeg
b82649967e RECONCILE-001: scan-set closure (close-by-absence) + v0.2.6.1
Treats each ecosystem scan as the authoritative full set for that
(agent, ecosystem) pair. Packages absent from a successful scan that
are still in a waiting state (pending/approved) are closed to installed
with out-of-band provenance — no operator action required.

State machine:
- Added pending/approved → installed edges (out-of-band resolution path)
- Added installed → pending edge (reactivation when a new version reappears)
- ReconcileFromScan updated to match: installed now reopens, ignored/failed preserved

Server (ReportUpdates):
- closeScanAbsentRows goroutine: diff waiting rows against reported set,
  transition absent rows via transitionStatus (guarded UPDATE, idempotent)
- Provenance stamping: redflag_receipt if a consumed capability token exists,
  out_of_band otherwise
- System event emitted per closure for audit trail
- scanEcosystemSupported gate: dnf/apt only; failed/partial scans never close rows

Agent:
- UpdateReport extended with Ecosystem + ScanSucceeded fields
- APT/DNF scan handlers now always report on successful scan (even 0 updates)
- HandleScanAPT/DNF/Updates: report failure is non-fatal (transport problem,
  scan succeeded locally)

Queries:
- GetTrackedNonResting: scoped to pending/approved only — in-flight states
  (checking_dependencies, pending_dependencies, installing) are orchestrator-owned
- TransitionByID: routes closure through the state machine
- HasConsumedTokenForUpdate: provenance check for the reconciler
- UpdateCurrentStateInTx SQL CASE: installed now reopens to pending on re-scan

Tests: reconcile_test.go (5 unit tests including load-bearing
TestWaitingStatesResolveOutOfBand), reconcile_test.go handler tests (7 sub-tests).

Bump: v0.2.6.1
2026-06-06 20:46:29 -04:00
Fimeg
b346c1386e v0.2.6.0 2026-06-05 21:34:12 -04:00
Fimeg
1ec7999d8f fix: code review batch 1 + dead store setting
- #1: ReconcileAll goroutine uses context.Background, not request ctx
- #4: InstallVersion now checks maintenance window before dry-run
- #5+#23: tickAliases fetches stale slugs once before loop; rename shadowing var
- #6: UpsertReconciled sql.ErrNoRows on manual conflict is a no-op, not a warn
- #7: normalizeRepoToEcosystem uses ordered slice, not non-deterministic map
- #8: MatchByContainer ILIKE escapes % and _ metacharacters via REPLACE
- #9: EnqueueDryRun uses target_version key for selected_version, keeps available_version for freshness
- #13: InstallVersion drops second GetUpdateByID, mutates struct locally
- #14: EnrichFromMetadata reserved map is package-level var, not per-call alloc
- #21: recordGateOverride shared helper; recordSupplyChain/SoakOverride delegate to it
- store.ts: remove dead notificationsEnabled setting (no callers outside store)
2026-06-05 21:26:44 -04:00
Fimeg
f005255e68 feat: docker enrichment pipeline, package detail, update history pagination 2026-06-05 21:18:06 -04:00
Fimeg
d1424e8377 feat: FEAT-002 metadata pipeline, GATE-005 soak gate, BRIDGE-001 auto-discovery
FEAT-002: Server-side metadata pipeline
- ReportUpdates merges PackageDescription/CVEList/KBID/SizeBytes into metadata JSONB
- UpdateCurrentStateInTx uses JSONB merge (||) instead of replace
- Fixes current_version bug (missing EXCLUDED.current_version)
- EnrichFromMetadata() on UpdateState populates display fields from metadata
- mergedVulnerabilities() deduplicates agent CVEs with OSV.dev results
- VulnerabilityEntry struct for unified vulnerability representation

GATE-005: Version soak-gate
- Migration 050: selected_version column, version_soak_overrides table
- soak_gate.go: SoakGateConfig/EvaluateSoakGate (14-day default, block enforcement)
- POST /updates/:id/install-version endpoint with soak evaluation
- EnqueueDryRun reads COALESCE(selected_version, available_version)
- Override journals to system_events (recordSoakOverride)

BRIDGE-001: Tracked software auto-discovery
- Migration 051: repology_slug/container_image_pattern/binary_probe on tracked_software,
  repology_aliases table, match_method/package_name on agent_tracked_software
- RepologyCache: fetches /api/v1/project/{slug}/packages, normalizes repos to ecosystems
- ReconciliationQueries: MatchByRepology/MatchByContainer/MatchByExactName
- Reconciler: cascade matching service with hourly loop, hooks into ReportUpdates
- UpsertReconciled: preserves manual operator bindings
- COMMON_SEEDS updated with repology_slug values
- Syncer updated with 24h alias refresh ticker
2026-06-05 17:43:11 -04:00
Fimeg
938d0bee35 v0.2.5.2 2026-06-05 16:37:56 -04:00
Fimeg
cad1554d94 feat: SETTINGS-001 reversible token encryption + one-liner restore
Reversible AES-256-GCM encryption for registration tokens so the
install one-liner can auto-fill the token value again. Migration 049
adds token_encrypted column; token_hash kept for lookup.

AgentManagement.tsx gets platform cards (Linux, Windows; macOS = soon)
with generateInstallCommand for all platforms. Inert until backend lands.

system_event_logger.go extracted from deleted event_stream.go —
SystemEventLogger survives, unified substrate does not.

security_settings_service.go and secrets_manager.go simplified.
2026-06-05 16:17:47 -04:00
Fimeg
4ccbf8c5fd fix: heartbeat auto-queue treats duplicate-pending as benign (ETHOS #4)
queueSystemHeartbeat now checks for the unique violation on
idx_agent_pending_subsystem and logs at INFO instead of WARN.
A duplicate means the desired heartbeat is already in flight —
not a failure. Suppresses the scary WARN on every rapid-polling
enable where a heartbeat was already pending.
2026-06-05 16:17:36 -04:00
Fimeg
5d7babff22 fix: History 500, rename to GetFleetActivity, remove dead unified substrate
GetFleetActivity replaces GetAllUnifiedHistory — filters now apply once on
the outer aliased result instead of per-arm, fixing the agent_id ambiguity
when the logs arm joins update_packages (the 500).

Three new UNION arms: update_events, update_version_history, system_events.
Package name joined to update_logs via update_package_id.

Dead code removed: event_stream.go (handler + service), UnifiedEventTimeline.tsx,
useEvents.ts — orphaned from the abandoned unified path (HANDOFF-2026-06-05).
SystemEventLogger extracted to system_event_logger.go (unstaged, next commit).
2026-06-05 16:17:27 -04:00
Fimeg
62f2764260 v0.2.5.1: lifecycle, live operations, unified history
Lifecycle:
- ReopenUpdate + ResolveUpdate replace RetryUpdate; routes for
  GET /updates/:id/lifecycle and POST reopen/resolve
- confirmUpdateCommand marks update_agent completed on version attestation
- migration 048: started/running added to update_logs.result
- helper atomic_replace_binary: copy-to-sibling then rename() (ETXTBSY)

Live operations:
- event_stream service + /events endpoints, LiveOperations page
- capability-token queries for the live view

History + CVE:
- History page reads /events/recent: filterable lifecycle/command/
  system/orchestrator timeline with agent crosslinks
- CVE drill-down: OSV parse carries CVSS vector, fixed version, published
  date, severity; issuer-linked aliases (CVE->NVD, GHSA->GitHub, ALSA->errata)
- DependencyClosureTree: one shared closure component in update detail
- STARTED (blue spinner) and PARTIAL (amber) result badges
2026-06-05 09:13:42 -04:00
Fimeg
6c331e909b update_logs.result: add started/partial/running — fix agent-report badge semantics
The schema only allowed success/failed/partial. The agent sends 'started' as a
progress report and 'partial_failure' when a multi-scanner scan had mixed
results. Both were being remapped to 'failed' by the server's fallthrough
default, so the timeline showed a red FAILED badge for 'starting agent update'
and for scans where only some scanners errored.

Two-value migration (no-data):
  - Add 'started' and 'running' to the update_logs.result CHECK constraint
  - Add them to isValidResult so they pass through without remapping
  - Fix the fallthrough switch: partial_failure -> partial (not -> failed)
  - Clean up if/else chain to a switch while we're in there

UI:
  - STARTED badge (blue spinner) for progress reports
  - PARTIAL badge (amber triangle) for partial results
  - Both ChatTimeline and HistoryTimeline updated

Event renderer:
  - 'started' -> 'Agent binary update initiated'
  - 'partial' for install/update_agent cases
2026-06-05 09:13:42 -04:00
Fimeg
0dcfe25705 unified agent+helper upgrade: closure carries both binaries
Server: mintAgentSelfToken includes helper in closure, sends
helper_download_url + helper_checksum in command params.

Agent: downloads and stages both binaries, passes --helper-file to helper.

Helper: parses --helper-file, separates closure into agent+helper entries,
self-updates helper binary first, then installs agent. Falls back to
agent-only if closure has 1 entry (backward compatible).
2026-06-05 09:13:42 -04:00
Fimeg
b30261791d fix: result file 0644 so agent can read back from root-owned helper
The helper runs as root via systemd-run. With 0640 root:root, the
unprivileged agent user cannot read the result. The result directory
is 0700 agent-owned which already blocks other local users.
2026-06-05 09:13:42 -04:00
Fimeg
482e5a9aad security: path traversal, file perms, sudoers/polkit scope, staging cleanup
- consumer.go: safeTokenFilename() blocks path traversal via token ID
- consumer.go: TOCTOU sanity check on result token_id
- main.rs: result file written 0640 (was 0644)
- linux.sh.tmpl: sudoers wildcards restricted to tokens/* and results/*
- linux.sh.tmpl: polkit scoped to manage-transient-units
- agent_update.go: clean up pending-upgrade.bin on failure
- updates.go: clear is_updating flag on failed update_agent
- bump 0.2.3.7
2026-06-05 09:13:42 -04:00
Fimeg
cff31d6106 v0.2.3.5: unlock self-update + gated installs on fresh hosts
We kept claiming self-update worked. On a clean box it didn't.

- linux.sh.tmpl: install a polkit rule so the service user can invoke the
  helper via systemd-run. Without it every gated install and self-update
  hit auth_admin and died on a TTY-less service.
- self-update: drop the post-update .bak sweep. It ran unprivileged against
  a root-owned backup and could only ever log permission-denied. The helper
  already keeps .bak as the single rollback slot.
- metrics/docker reports: stop finalizing the command at ingest. It raced
  ReportLog and 409'd the history-bearing log, silently dropping system and
  docker scans from History. ReportLog is the sole finalize point now, same
  as dnf/storage.
2026-06-05 09:13:42 -04:00
Fimeg
5758b26875 swap uuid lib, windows installer pass, README/RAF copy
- google/uuid -> gofrs/uuid/v5 across server + agent
- windows.go: cross-platform binding cleanup
- linux install template: disable sudo lecture for TTY-less service user
- README: XZ/SolarWinds lede, stable-release note, single attack-surface block
2026-06-03 15:39:49 -04:00
Fimeg
6327c13460 fix: metadata race, gate predicate drift, OSV concurrency, closure pre-check
ApproveUpdateWithVulns uses JSONB merge (||) instead of full replace —
concurrent checkClosureAndAdvance no longer loses its keys.

evaluateSupplyChainHold gates on ClosureCleared not ClosureChecked —
manual and auto paths now share the predicate for real.

RunOSVChecks fans out batches with goroutines bounded by the 4-slot
semaphore instead of running them sequentially.

Zero-dep capability path verifies a pinned closure exists before
transitioning to installing — no more opaque mint failure after state
change.
2026-06-01 15:21:50 -04:00