Watch
1
0
Fork
You've already forked RedFlag
0
Commit graph RedFlag/web
Author SHA1 Message Date
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
4dc750b687 fix: agent install URL preserves port for non-localhost hosts
getServerUrl() was stripping the port when hostname != localhost,
generating install commands on port 80. Now uses window.location.port
directly — the browser's host:port is always reachable by the agent.
2026-06-08 09:25:46 -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
ae3db516c0 let the agent spot sunshine and give the overview tab a face for it
observe-only: the agent folds detected integrations into its system-info
report under metadata.integrations; the dashboard renders what it reports.
nothing reaches into the host — an integration can be watched, not commanded.
2026-06-08 08:14:13 -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
edf799d8d5 feat: filter/search primitives — composable UI components + hooks
Replace monolithic FilterBar/useFilterState with proper primitives:

Components (pure UI, zero state):
- SearchInput — controlled search input with icon + clear
- FilterPill — colored dismissible chip
- FilterDropdown — styled select with label + active state
- FilterCountButton — clickable stat card with count + color

Hooks (single responsibility):
- useDebounce — generic value debounce
- useFilterUrl — URL param sync only
- useQueryParser — raw query → parsed text + filters + pills
- useMultimodalFilter — composes above two for key:value search

Pure functions:
- queryParser.ts — parseQuery/buildQueryString

Migrated all 6 pages:
- Docker: full multimodal search with key:value parsing, clickable stat cards
- Updates: SearchInput + FilterDropdown + FilterPill with clear all
- Agents: SearchInput + FilterDropdown (status, OS)
- LiveOperations: SearchInput + FilterDropdown
- TokenManagement: SearchInput
- History: SearchInput + useDebounce
2026-06-05 22:40:33 -04:00
Fimeg
cea3986fae fix: SPA nav hygiene + history crosslinks
- Layout/Dashboard refresh buttons use queryClient.invalidateQueries, not reload()
- LiveOperations view-update/view-agent use navigate(), not window.open new tab
- Updates.tsx filter sync uses setSearchParams, not window.history.replaceState
- ChatTimeline: View Agent shows hostname; package links go to /updates/package/:type/:name
2026-06-05 21:30:45 -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
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
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
b7eba59dd8 0.2.3.0: fix dnf dry-run success detection, clear stale auth on logout
dnf DryRun: --assumeno cancels the transaction, so DNF exits non-zero
even on a dry run that resolved cleanly — the old check failed those
(curl-style single-package upgrades with no extra deps showed FAILED).
But non-empty stdout is not success either: 'No match for argument',
'Nothing to do', and 'Error:' all print output and exit non-zero, and
treating them as success would mint a capability token for a transaction
that never installs (fail-open). Gate on an actually-resolved
transaction (a 'Transaction Summary' block, which never coexists with
'Nothing to do') instead.

web logout: clear the zustand persist key (auth-storage) alongside
auth_token and user, so a JWT from a prior server reinstall (JWT_SECRET
rotation) does not survive a logout + re-login cycle. Drop the redundant
localStorage removal in Layout — the store owns logout cleanup.
2026-05-31 21:19:32 -04:00
Fimeg
2a0c800659 v0.2.2.0: enforce package state machine across all transitions, vuln dashboard
Route every current_package_state status change through one transitionStatus
path: read the observed status, validate against PackageStatusTransitions,
run a status-guarded UPDATE, record terminal history. Replaces ten raw-SQL
transition functions whose WHERE guards validated nothing and silently
no-op'd on an illegal state. ApproveUpdate, the Reject/Install/Set* family,
BulkApprove and UpdatePackageStatus now share the core; illegal moves return
a named from->to error instead of a silent miss, and concurrent callers are
caught by the guarded row count.

Migration 047 renames the terminal success state updated -> installed in
current_package_state and update_version_history, realigning both CHECK
constraints with the Go PackageStatus/HistoryStatus constants.
UpdateStats updated_updates -> installed_updates to match.

UpdateCurrentStateInTx documents its reconcile CASE as the SQL twin of
models.ReconcileFromScan so the two stay in lockstep.

Dashboard: vulnerable-package count surfaced in AttentionPanel, plus a
Vulnerable quick-filter on the Updates view.
2026-05-31 16:39:58 -04:00
Fimeg
6c5c3cb6c0 v0.2.1.3: fix dry-run version targeting, migration 046, helper cgroup access, UI refresh 2026-05-31 11:52:36 -04:00
Fimeg
54bed0711f feat: signing key deprecation UI + endpoint, fix agent mgmt for hashed tokens
- GET /admin/signing-keys lists all keys (primary, accepted, deprecated)
- POST /admin/signing-keys/:key_id/deprecate with primary-key guard
- SigningKeyRoster component at Settings > Security > Key Management
- AgentManagement page updated for hashed registration tokens
- README trust model: key rotation presented as operational feature
2026-05-30 14:09:50 -04:00
Fimeg
b810b10162 security: hash registration tokens at rest, idempotency guard, README trust model
SEC-001: Registration tokens stored as SHA-256 hashes. Migration 046 adds
token_hash column, backfills from plaintext, drops token column. All queries
use hash. Token plaintext shown once at creation (reveal panel in UI), never
retrievable again. Follows the refresh-token pattern.

SEC-005: README "no sanitization" claims corrected — code correctly sanitizes
against log injection (ANSI stripping, control char replacement, truncation).
Wording updated to match reality.

SEC-008: Command creation with idempotency_key uses ON CONFLICT DO NOTHING
instead of blind insert. Prevents duplicate command execution.

Trust model: Ed25519 key rotation documented — signing_keys table supports
multiple concurrent active keys with a sliding window for zero-downtime
rotation. OSV.dev ecosystem coverage updated (apt, dnf added).
2026-05-30 13:12:58 -04:00
Fimeg
0d908ba512 feat: zero-sudo agent, helper self-upgrade, OSV expansion, docker handler rewrite, staging UI
Agent privilege reduction:
- apt discovery unprivileged (sandbox opts like dnf)
- docker commands use group membership, no sudo
- agent self-upgrade delegated to helper via agent-self capability token
- sudoers template stripped to single systemd-run helper line

Helper (Rust):
- agent-self package type: stage, hash-verify, backup, install, chmod, restart
- TOCTOU-safe: copy-then-hash, never hash a path re-read later
- --no-block restart so helper finishes before agent SIGTERM

Server:
- mintAgentSelfToken signs agent-self tokens for manual and bulk update paths
- OSV.dev checks at discovery time (async, deduped) + startup backfill
- apt/dnf added to OSV ecosystem mapping
- docker handler rewritten against DockerQueries (proper image/container split)
- status filter server-side on aggregated packages (HAVING clause)
- dead code removed: UpdatePackage model, UpsertUpdate, ListUpdates

Frontend:
- LiveOperations -> Staging with staged-packages section
- Docker severity from data, not hardcoded
- Updates status filter delegated to server
2026-05-30 12:56:40 -04:00
Fimeg
10a51fa56f feat: surface retry context in unified history
A retried command carries the same action/result as its original, so the
history read as a fresh attempt. The lineage already lived in
agent_commands.retried_from_id — it just was not projected.

GetAllUnifiedHistory now selects is_retry + retried_from_id (both UNION
halves; logs are always false/null); UnifiedHistoryItem carries them; the
handler prefixes the narrative with "Retry — ". ChatTimeline composes its
own command sentences (narrative is only a log fallback), so it gets the
same prefix guarded by entry.is_retry, matching the is_retry/
retried_from_id convention LiveOperations already consumes.

Also drop a leftover heartbeat console.log debug block in Agents.tsx.
2026-05-29 22:04:49 -04:00
Fimeg
d18b0f8a02 feat: agent resilience knobs, shared polling loop, settings restructure
Extract the agent polling loop from main.go into internal/agent/loop.go
so the Windows service and the CLI agent share one code path. The loop
now reads jitter cap and backoff curve from PollingConfig (struct with
merge + file/env defaults) instead of hardcoding 30s/10s/300s. Machine
ID resolution uses the canonical system.GetMachineID() in both the
registration and runtime paths, removing the inline 'unknown-' fallback.
Stuck command retries are parameterized (maxRetries arg) rather than
hardcoded to < 5.

Restructure settings into a uniform hub-of-cards pattern: extract inline
Account Settings into /settings/general, un-orphan SecuritySettings with
working /settings/security/:tab routes. Add fleet-wide polling resilience
tuning (jitter_max_seconds, backoff_base_seconds, backoff_max_seconds)
as operational settings — stored in security_settings, delivered over
GET /api/v1/agents/:id/config, merged into the agent's local config at
runtime with a 15-minute refresh cadence. Frontend includes AgentPolling
page, hook, hub card, and route.
2026-05-28 21:45:04 -04:00
Fimeg
487ffa89ef feat: package-centric updates, version timeline, and registry-gap closure (v0.2.0.7)
Lands the long-dropped in-flight work plus two slices of the pinning-mirror direction.

Registry-gap closure (in-flight, was repeatedly dropped):
- Agent resolves canonical artifact hashes from its own signed repo metadata
  (dnf download + rpm header; apt-cache policy+show) — server no longer serves a
  placeholder dnf URL and says so honestly.
- Server pins the agent-reported closure and mints the capability token at the
  dependency-confirmation boundary; receipt updates package status.

Slice 1 — package detail pane:
- GET /updates/:id/fleet (cross-agent view). Detail pane gains Supply Chain card
  (pinned sha256, published/age, age-gate verdict, resolved closure) and Affected
  Agents card (per-host version delta + status, click-to-pivot).

Package-centric Updates list:
- ListAggregatedPackages rollup (GET /packages): one row per package across the
  fleet — agent/version counts, max severity, vuln + hash-pin rollups, status
  breakdown. List view rewritten to package rows that drill into the fleet view.

Slice 2 — version timeline catalog:
- migration 043 package_versions; idempotent upsert populated at scan, enriched at
  approval (OSV posture, publish date, hash) and at closure pin (per-artifact hash).
- GET /updates/:id/versions + Version Timeline card.

UI: description overflow fix, shared table density px-6->px-4, status label cleanup.
Version: 0.2.0.7 across versions.go, docker-compose, Makefile (Makefile was stale at
0.2.0.3/0.2.0).
2026-05-28 20:25:59 -04:00
Fimeg
f0f18d7320 refactor(agent): scanner orchestrator cleanup + kernel-enforcement wiring + hash-registry follow-ups
Scanner refactor:
- Move Name() onto each scanner; drop scanner_wrappers.go, registry.go,
  scanner_types.go and the duplicate scanner/docker.go (folded into
  orchestrator/docker_scanner.go)
- Add Name() to DNFScanner (was missing — broke orchestrator.Scanner)
- dnf_test.go coverage

Kernel enforcement (Tier 2 scaffold, wired into loop):
- agent/internal/kernel: enforcer, ebpf consumer, windows WDAC stub
- config.KernelEnforcementConfig + defaults/merge, wired in loop.go

Hash registry (Layer 1) follow-ups:
- client GetExpectedHash uses /api/v1/updates/verify-hash
- UpdateHandler takes config; computeAndStorePackageHash uses PublicURL

Server:
- Migration 041: update version_history status constraint
- docker reject path writes "ignored" (matches new constraint)
- queries/filter.go shared filter helper
- updates UI enhancements
2026-05-28 13:31:51 -04:00
Fimeg
2d7911f2d0 chore: bump to v0.2.0.5 + agent↔tracked_software bindings 2026-05-25 19:52:48 -04:00
Fimeg
a0501ac57e revert: remove refetchInterval from useUpdates — wrong direction 2026-05-25 15:42:16 -04:00
Fimeg
7791e51582 fix: cross-invalidation, nonce-failure events, query key typos
- AgentUpdatesEnhanced: ['active-commands'] → ['activeCommands'] (hyphenated key
  never matched the camelCase query key, so invalidation was silently dead)
- useUpdates (install + approve): invalidate ['dashboard-stats'] and
  ['activeCommands'] on success so Dashboard and Live Operations react without
  waiting for their independent poll cycles
- Updates.tsx handleConfirmDependencies: replace window.location.reload() with
  targeted queryClient.invalidateQueries calls (BUG-017 pattern)
- LiveOperations: updateId = cmd.params?.update_id || cmd.id so "View Update
  Details" navigates to the correct update package, not the command record
- useUpdates query: add refetchInterval: 30000 / staleTime: 15000 so agent-side
  completions surface without window-focus or manual refresh
- updates.go ReportLog: emit system_event (agent_update/failed) when an
  update_agent or verify_command command returns result=failed, using
  RenderUpdateLog for operator-facing narrative
2026-05-25 15:07:24 -04:00
Fimeg
76ef5ae0e3 feat: auto-heartbeat at dispatch, event narratives, policy table (migration 038)
Three structural pieces that only make sense as a unit: the dispatch
chokepoint queries the policy table, and the event renderer is consumed by
the same handlers the policy gates guard.

B. Auto-heartbeat at the dispatch chokepoint (agents.go, models/command.go)
  - models.RequiresRapidPolling(commandType): central classification (no
    per-handler opt-in for rapid-polling commands).
  - signAndCreateCommand auto-queues enable_heartbeat (Source=system)
    ahead of any rapid-polling command, unless the agent is already in an
    active heartbeat window.
  - TimeoutService.reconcileAgentUpdates: effectiveUpdateTimeout() reads
    operational.update_stuck_minutes live (no restart needed).

C. Event renderer (services/event_renderer.go NEW)
  - RenderSystemEvent / RenderUpdateLog: single source of operator-facing
    verbiage.
  - Narrative field on SystemEvent / UpdateLog / UnifiedHistoryItem
    (JSON-only, not persisted). Populated in agent_events.go GetAgentEvents
    and updates.go GetAllLogs.
  - ChatTimeline.tsx consults narrative only at the prior fallback line —
    real stdout / package extraction branches untouched.

D. Policy table (migration 038 + security_settings_service helpers)
  - policy.allow_dry_runs (default true): updates.go::InstallUpdate
    returns 403 when false.
  - policy.require_nonce (default true): agent_updates.go::UpdateAgent
    skips nonce validation when false (logged at INFO).
  - policy.auto_heartbeat_enabled (default true): agents.go gates the
    auto-heartbeat side-effect.
  - operational.update_stuck_minutes (default 5): TimeoutService reads
    live for reconcile threshold.
  - GetPolicyBool / GetOperationalInt on SecuritySettingsService.

Forward-only (no policy.allow_downgrade) is ETHOS §2 doctrine, not a knob.
2026-05-25 14:12:23 -04:00
Fimeg
8d441a5b33 fix: address audit CRITICAL issues across installer, sudoers, logging, and service loop
- CRITICAL-008: Fix sudoers templates in sudoers.go + linux.sh.tmpl to match
  actual agent DNF/APT commands. Remove stale dnf refresh subcommand from
  security.go AllowedCommands; align both templates with agent's real flags.
- CRITICAL-004+005 Phase 1: Remove demo-mode lie from windows.go installUpdates().
  Failed installs now return error instead of false success. Add stderr checks
  to wuauclt path. Hardcode GetPendingUpdates() replaced with error noting
  go-ole COM API will be the real implementation.
- CRITICAL-006: Replace 15 lines of fmt.Printf debug noise in getWindowsCPUInfo()
  with 5 structured log.Printf lines (ETHOS [TAG] format). Remove intermediate
  parse-progress lines that had no diagnostic value.
- CRITICAL-007: Extract shared RunPollingLoop exported from agent package so
  the Windows service (service/windows.go) calls the same loop as the console
  agent instead of maintaining a forked copy with 5 duplicated handler functions.
  Delete the 5 dead forked handlers (~500 lines). Add StopCh field for clean
  service shutdown.
- P3-BUG-004: Delete dead deriveKeyFromNonce() and decryptAES256GCM() from
  agent_update.go (~35 lines). Remove now-unused crypto/aes and crypto/cipher
  imports.

Also included: pre-existing uncommitted work on dispatch, agent_updates,
AgentUpdatesModal, and reboot handler.
2026-05-25 12:45:58 -04:00
Fimeg
78d9131a9f v0.2.0.3: wire install flow, restore lost handlers, dedupe agent packages
Server / agent install pipeline:
- Restore dry_run_update, confirm_dependencies, install_updates,
  enable_heartbeat, disable_heartbeat handlers on the agent side (lost in
  the TD-001 god-function refactor at 9da5134e); wire them through
  handlers/dispatch.go so the cross-platform agent loop dispatches them
  alongside scans and update_agent.
- Wire JWT renewal into the polling loop on 401 (RenewToken existed in the
  client but was dead code in loop.go).
- Self-update path now shells through sudo for cp/chmod/systemctl restart,
  matching the redflag-agent user's hardened systemd unit.

Server build orchestrator:
- BuildAndSignAgent now reuses the existing signed package row when the
  on-disk binary's checksum matches the stored one. Previously the server
  re-signed and inserted 4 fresh rows on every boot, leaving dozens of
  duplicate agent_update_packages entries.
- CreateUpdatePackage is now ON CONFLICT (version, platform, architecture)
  DO UPDATE so a fresh build of the same version replaces in place.
- New migration 037: dedupes existing rows (keep newest per tuple) and
  enforces UNIQUE (version, platform, architecture).
- Drop dead verification.go endpoint stub — architecturally broken in a
  pull-only polling model.

Dashboard:
- AgentUpdatesModal filters packages to the selected agents' os_type and
  os_architecture, dedupes by (version, platform, arch), and renders
  platform/arch together so 32/64-bit differentiate visually. Drops the
  platform dropdown (now agent-driven).

Install script template:
- Fix server_public_key + initial_binary.sig ownership so the agent user
  can overwrite them; convert hex key to raw 32 bytes inline.
- Add sudoers entries for the agent's self-update cp/chmod/systemctl path.

Downloads handler resolves ?version=latest to AgentVersion so install
scripts pull a signed package instead of a 404.

Version bumped to 0.2.0.3 across versions.go, docker-compose, Makefile,
downloads.go, security min_agent_version.
2026-05-25 10:02:52 -04:00
Fimeg
97931e6dd3 fix(web): TypeScript errors for Vite 8 + Rolldown 2026-05-24 20:18:16 -04:00
Fimeg
f01c685dd3 fix(web): remove offline-agents alert from AttentionPanel
Offline agent status is already prominent in the main agents list and stats
panel. Duplicating it in the attention feed added noise without urgency signal.
Removed WifiOff import and offline severity rank.
2026-05-24 20:08:58 -04:00
Fimeg
2d79e6748a fix(BUG-003): delete unused AgentUpdates.tsx; remove empty docker dir
AgentUpdatesEnhanced.tsx + AgentUpdatesModal.tsx are the canonical components.
AgentUpdates.tsx was stale dead code. Empty docker/ dir removed.
2026-05-24 20:08:49 -04:00
Fimeg
b2e1a8816a fix(BUG-006): remove duplicate signing key append in Setup.tsx
generateEnvContent was appending REDFLAG_SIGNING_PRIVATE_KEY after
server-side createSharedEnvContentForDisplay had already embedded it,
producing two copies in the setup output.
2026-05-24 20:08:37 -04:00
Fimeg
49ae659b7d fix(VULN-001): bump x/crypto to v0.52.0, x/net; npm audit fix; golang 1.25 2026-05-24 20:07:53 -04:00
Fimeg
9c68a63b29 feat(web): upstream tracking UI knows the new release sources
Surface the gitea/gitlab/bitbucket/git adapters in the
UpstreamTracking page so operators can actually pick them from the
form. Three changes:

- UpstreamSource union in types/index.ts gains 'gitea' | 'gitlab' |
  'bitbucket' | 'git'. The /admin/upstream API already returns these
  in its "sources" array (handler reads Registry.Names()), so the
  dropdown picks them up automatically once the union allows.

- New sourceRefHint(source) returns per-source placeholder + help
  text so the source_ref field documents itself instead of always
  saying "e.g. postgresql". Each entry names the required env vars
  so operators don't 404 their way to discovering REDFLAG_GITHUB_TOKEN.

- New sourceHref(s) replaces the hard-coded ternary that only knew
  repology and endoflife. Sources without a stable public URL pattern
  (gitea/anitya/npm/pypi, where host varies per deployment) return
  null and skip the "Source" link rather than guessing wrong.

COMMON_SEEDS chips deliberately unchanged — those are the "one-click
to a working dashboard" path for new operators, all on endoflife
because EOL coverage is what justifies the chip. The form is the path
for everything else.
2026-05-23 21:41:38 -04:00
Fimeg
bcd67e04bf feat: upstream tracking UI + Attention panel + semver classifier
UpstreamTracking page (/settings/upstream):
- Full CRUD: add form, common-stack one-click chips (postgres,
  nginx, node, python, redis, docker, go, kubernetes, ubuntu, debian),
  table with sync-now / source-link / remove per row
- Drift highlighting: past-EOL rows red, behind-upstream rows amber
- Surfaces last_error and last_synced_at per row
- Wired into App.tsx routes + Settings.tsx quick-action card

AttentionPanel on Dashboard:
- Aggregates offline agents, failed updates, past-EOL software,
  recent drift events into one feed
- Severity-ranked (eol > failed > major > offline > minor > patch)
- Renders nothing when state is clean — calm dashboards stay calm

Semver-aware classifier:
- services/upstream/version.go: ParseVersion + CompareVersions +
  ClassifyDrift; handles messy versions (15.4, v1.27.3,
  1.0.0-rc1+meta, 20231130-1.fc40)
- SemVer convention: release > prerelease (empty suffix wins)
- Replaces lexicographic compare in syncer

Settings page cleanup:
- Drop "System Configuration — coming soon" dead card
- Drop "Implementation Status" yellow-box fluff
- Fix broken Tailwind autoRefresh toggle (dynamic class wouldn't JIT)
2026-05-23 15:18:47 -04:00
Fimeg
86da7471ec feat: install/sync/RL hardening + upstream version sync subsystem
Agent + install:
- linux installer adds redflag-agent to docker group (idempotent;
  unblocks container scanner detection)
- install.sh / linux.sh.tmpl / windows.ps1.tmpl: detect existing
  refresh_token and skip --register (Flow 2: upgrade in place)
- agent_update.go: remove dead post-restart watchdog; add
  CleanupPostUpdateBackup() called after first successful check-in

Server + token model:
- 409 machine-already-registered now returns existing_agent_id,
  hostname, last_seen, remediation guidance
- RevokeAgent admin handler (invalidates refresh_tokens only)
- GetAgentsBoundToToken query + no-cascade invariant test
- Two-axis revocation locked in: registration_token revoke does NOT
  cascade to agent refresh_tokens

Supply chain (Shai-Hulud defense feature 1):
- services/package_age.go: npm + PyPI registry probes,
  EvaluatePackageAgeGate decision matrix (warn/block/off x
  above/below/unknown)
- ApproveUpdate / ApproveUpdates wired to age gate; stores
  package_published_at + supply_chain_age_check in metadata
- security_settings_service: supply_chain category defaults
  (min_package_age_hours=24, gate_enforcement=warn)

Rate limiting UI rewrite:
- Frontend was expecting per-endpoint configs + usage/summary
  routes that don't exist; backend has 6 named categories
- Rewrote RateLimiting.tsx, useRateLimits.ts, api.ts, types,
  Settings overview card to the real shape (Requests + Window in
  seconds + Enabled per category)

Agent Management UI:
- Replaced auto-pick-first-token with explicit dropdown showing
  prefix, label, seats_used/max_seats, expiry
- One-liner panel only renders when a token is selected; no more
  YOUR_REGISTRATION_TOKEN placeholder in copyable command
- Zero-token state surfaces "Generate Registration Token" CTA

Upstream version sync (new subsystem):
- Migration 035: tracked_software + upstream_drift_events
- ReleaseSource interface + Registry + Repology adapter +
  endoflife.date adapter
- Periodic Syncer goroutine with on-demand SyncOne; classifies
  drift severity (minor/major/eol); appends drift events
- /admin/upstream CRUD + drift + sync-now routes
- Dashboard "Stack Drift" panel (count + worst-3 + EOL flag)

Migration 035 runs idempotently on next startup.
2026-05-23 15:12:20 -04:00
Fimeg
bac797da6f ui: replace stale vite.svg reference with RedFlag favicon
🚩
2026-05-22 14:20:26 -04:00
Fimeg
03c72cb438 ui: unify per-scanner package rows into one System Update Scanner row
The agent runs apt/dnf/winget/windows on their own schedules (per-scanner
agent_subsystems rows still exist server-side) but the UI now collapses
them into a single "System Update Scanner" row. Chips on the description
line (APT, DNF, WINGET, Windows Update) light up from
agent.metadata.available_scanners — the real signal from
syncAvailableScanners — rather than the old os_type string heuristic
that sniffed "fedora" / "debian" out of the platform field.

The row is synthesized client-side from the per-scanner backers; no
'updates' DB row needed (the zombie kill landed in 7fb61a36). Toggle,
auto-run, interval, and Scan on the unified row cascade to every backing
per-scanner subsystem that actually exists for the agent.

Widened AgentSubsystem.subsystem type to string — the narrow union was
stale (DB has apt/dnf/winget/windows/storage/system/docker, plus the
synthetic 'updates' aggregate the UI produces).

Removed the os-string heuristic helper and its console.log noise.
2026-05-22 14:05:28 -04:00
Fimeg
c8b0ee1040 refactor: rename aggregator-agent to agent for public release 2026-05-21 13:19:18 -04:00