Watch
1
0
Fork
You've already forked RedFlag
0
Commit graph

496 commits

Author SHA1 Message Date
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
Fimeg
7a154e11ee docs: changelog gets its own life, README tells the truth about v0.2.3.1
changelog entries for v0.2.2.0 (state machine + orchestrator), v0.2.3.0
(OSV batch, closure-wide checks), v0.2.3.1 (vuln is a full stop).
README condensed to point at CHANGELOG.md, gate description updated
from "soon" to what it actually does now.
2026-06-01 15:06:39 -04:00
Fimeg
36923bb509 ops: supply chain section now tells the truth — advisory fails open, the gate doesn't 2026-06-01 09:56:09 -04:00
Fimeg
dc3bfdf493 supply chain gate: a vuln is a full stop now — admin signs for it or it doesn't ship
delivery plumbing that got us there:
- acks clear on result-recorded, not command lifecycle status (no more 34-deep recycling)
- timeouts, cancels, dropped acks/receipts, failed actions all land in history instead of dying on stdout
- one shared closure-cleared predicate so auto-confirm and manual approve can't drift

override waives the vuln call only and gets journaled; signing and hash verification stay non-negotiable.
2026-06-01 09:55:22 -04:00
Fimeg
1b6892ad2d I forgot to commit all the working files - let's start the day off from a clean tree 2026-06-01 08:09:28 -04:00
Fimeg
37a2302628 docs: promote 7zip dependency-resolution shot to the top showcase row 2026-05-31 22:11:00 -04:00
Fimeg
2b02f65dbd v0.2.3.1: bump version (README, versions.go, compose) 2026-05-31 22:07:40 -04:00
Fimeg
91e097f54a 0.2.3.0: README honesty pass + fresh screenshots — version, clone count, and OSV now tells the truth about checking your deps' deps 2026-05-31 22:00:09 -04:00
Fimeg
33c92ba04d 0.2.3.0: auto-confirm now frisks the whole dependency closure for CVEs, not just the package you asked for (the quiet deps are always the ones carrying) 2026-05-31 21:56:35 -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
fe3a759029 0.2.3.0: switch OSV checks to batch endpoint, global concurrency cap
- Replace per-package HTTP requests with /v1/querybatch (100 per POST)
- Process-wide semaphore (4 concurrent batches) shared across all callers
- 300 packages: 3 HTTP calls instead of 300
- 30s timeout for batch requests
- Failure recording unchanged: no checked_at = retry next cycle
2026-05-31 18:04:07 -04:00
Fimeg
e8f69212be 0.2.3.0: fix OSV supply-chain checks — bounded concurrency, persist-driven dedup
- Replace unbounded goroutine fan-out with bounded pool (8 concurrent)
  so 300-package dnf scans no longer timeout every request against api.osv.dev
- Drop in-memory osvDedup sync.Map; gate on persisted supply_chain_checked_at
  so the dedup survives restart and failed checks retry naturally
- On query failure, record the error without checked_at so the package stays
  a candidate for the next cycle (ETHOS: errors are history, assume failure)
- Shared RunOSVChecks in services/ used by both scan path and startup backfill
- Add FreshSupplyChainPackages query for persist-driven freshness lookup
- Bump version to 0.2.3.0
2026-05-31 17:54:09 -04:00
Fimeg
6cae9300c4 v0.2.2.0: lifecycle orchestrator foundation (LIFECYCLE-003)
Add server/internal/orchestrator — a stateless, DB-driven service that advances
the package update lifecycle instead of leaving every transition to an operator
button press. It holds no state of its own: each 60s sweep re-reads from the DB,
so a restart reconciles on the next tick, and every advance rides the
LIFECYCLE-001 guarded transition (idempotent by construction).

- Auto-approval is policy-gated by policy.auto_approve_max_severity (default
  off). Eligible pending packages are approved and their dry-run enqueued
  (-> checking_dependencies). It never advances to installing on its own, never
  runs when allow_dry_runs=false, never approves packages carrying
  supply_chain_vulns, and an unrecognized policy value fails safe to disabled.
- Stuck-state recovery: checking_dependencies past 30m re-enqueues the dry-run
  up to twice then fails the package; installing past 60m fails it;
  pending_dependencies past 24h logs a stale count without changing state.
- Synchronous fast-path: ReportUpdates fires OnPackagesDiscovered() so
  auto-approve runs at scan time, serialized with the timer via TryLock.

Supporting changes: exported TransitionByID/TransitionByPackage and
GetPackagesInStatus/BumpRetryCounter on UpdateQueries; GetPolicyString on the
settings service; EnqueueDryRun extracted from the manual dry-run endpoint and
shared with the orchestrator. Unit tests cover policy gating, requeue-then-fail,
and the install timeout.
2026-05-31 17:10:25 -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
fcabaefe82 agent: terminal backoff for dead credentials instead of retry loop
When ErrRefreshTokenInvalid or ErrMachineMismatch fires, the agent now
waits 10 minutes between poll attempts instead of exponential backoff.
These are permanent states — no amount of retrying fixes a dead
refresh token or a machine_id mismatch. The agent stays alive and
visible, waiting for operator intervention through the dashboard.
2026-05-30 15:21:06 -04:00
Fimeg
b017cbe222 top banner: replace stale NOT YET ANNOUNCED with sponsor hook 2026-05-30 14:29:21 -04:00
Fimeg
1eb1f9458f add Sponsorship & Consulting section to README 2026-05-30 14:24:47 -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
018c0a4104 fix trust model: signing key rotation is manual, not automatic
Old key stays is_active=true after SetPrimaryKey with no TTL or auto-deprecate.
README now says what actually exists: operator promotes new key, then explicitly
deprecates the old one. No sliding-window automation yet.
2026-05-30 13:33:13 -04:00
Fimeg
d74498a19d publish supply-chain gate plan, honest status section in README
RAF/SUPPLY_CHAIN_GATE_PLAN.md unblocked — the architectural thesis for the
capability-token model. Updated to reflect agent-self upgrade path, OSV
expansion, and current verification state.

README Status section rewritten: "implemented and locally exercised, not
production-proven" replaces the misleading "working in production" header.
Honest gaps listed (GATE-002, CRITICAL-004).
2026-05-30 13:25:12 -04:00
Fimeg
2b55410323 add 90-day TTL to trust model, unblock OPERATIONS.md for public
Trust model: refresh-token rotation paragraph now states the 90-day expiry.
OPERATIONS.md (operator runbook) whitelisted in .gitignore — useful for
anyone deploying RedFlag.
2026-05-30 13:20:47 -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
393821ab59 bump v0.2.1.0 -> v0.2.1.1, add CHANGELOG.md 2026-05-30 12:56:49 -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
177b1e3b02 docs: note the supply-chain gate is in live testing — soon 2026-05-29 22:04:49 -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
70ce0c71e9 feat: helper distribution pipeline + dnf discovery/resolve fixes for live gate
Make the capability gate runnable as installed. The helper is now a
first-class signed artifact distributed through the same pipeline as the
agent binary: built in the server image, signed at startup, listed in the
signed release manifest, served over GET /api/v1/helper/:arch with
X-Content-Signature, and Ed25519-verified + provisioned at install time
(binary root:root 0755, keyring, replay-guard dir, agent_id).

dnf discovery runs unprivileged: SandboxOpts redirect log/cache to an
agent-writable temp dir, so the agent holds zero dnf sudo (only the helper
invocation line). Removed the dead dnf discovery sudoers grants.

dnf artifact resolution: dnf5 pulls the matching .src.rpm from COPR-style
repos alongside the binary, which made singleRPMInDir refuse as ambiguous,
dropping the closure to empty and failing the mint closed ("no resolved
closure stored"). Filter source rpms before the ambiguity check so it pins
the one install artifact.

Drop the Fedora "updates" repo from the dnf security-severity heuristic;
it is not security-specific. Delete orphaned installer/sudoers.go (no
callers; emitted a contradictory unit). Migration 036: remove embedded
BEGIN/COMMIT that closed the runner's own transaction early.
2026-05-29 22:04:38 -04:00
Fimeg
7715a98d29 refactor: NeedsSupplyChainCheck → decoupled into OSV gate, server fetch, and capability gate functions
- NeedsSupplyChainCheck: unchanged, OSV.dev eligibility (npm/pypi only)
- CanServerFetchArtifact: server can download from public registries (npm/pypi)
- NeedsCapabilityGate: ecosystems that route through capability tokens (dnf, apt, npm, pypi)
- computeAndStorePackageHash now uses CanServerFetchArtifact
- usesCapabilityExecution now uses NeedsCapabilityGate
2026-05-29 18:18:47 -04:00
Fimeg
3448c4c990 fix: route agent-sourced ecosystems through mintResolvedClosure at approval time
Previously, ApproveUpdate logged mint_skipped for dnf/apt because
computeAndStorePackageHash returns empty (server cannot download those
artifacts). But the agent already resolved and reported the full closure
with per-artifact hashes via ReportDependencies → pinReportedClosure.

Now when artifactHash is empty, the handler calls mintResolvedClosure
which reads the stored closure from the dry-run phase. Server-fetched
ecosystems (npm/pypi) use the existing artifactHash path unchanged.
2026-05-29 18:05:11 -04:00
Fimeg
156e66214c docs: bump v0.2.0.7 → v0.2.1.0, update CLAUDE.md and changelog 2026-05-29 17:50:49 -04:00
Fimeg
2698b680d2 refactor: shrink Installer interface to discovery-only, gate mutation behind type assertions
Remove Install/InstallMultiple/Upgrade/UpdatePackage from the Installer
interface. Mutation for gated ecosystems (dnf/apt) is refused with a
[SECURITY] error directing to the capability-token path. Non-gated
ecosystems (winget, windows_update, docker_image) type-assert to the
concrete type for UpdatePackage/Upgrade/InstallMultiple.

HandleInstallUpdates: gate dnf/apt; type-assert switch for non-gated.
HandleConfirmDependencies: gate dnf/apt; type-assert switch with
InstallMultiple for dependency batches.
2026-05-29 17:38:20 -04:00
Fimeg
fd7cd6affb fix: use apt instead of apt-get in ecosystem config and sudoers — apt-get is deprecated 2026-05-29 17:35:09 -04:00
Fimeg
4b4a58504b refactor: delete mutation methods from dnf/apt installers, remove SecureCommandExecutor 2026-05-29 17:34:01 -04:00
Fimeg
652affb025 refactor: rewire APTScanner.Scan + APTInstaller.DryRun to DiscoveryRunner
Replace raw exec.Command calls in the scanner and the SecureCommandExecutor
calls in DryRun with DiscoveryRunner, matching the pattern already applied
to DNF (5a27f7b0). Mutation methods (Install, InstallMultiple, Upgrade,
UpdatePackage) are unchanged — Task 4 will delete them.
2026-05-29 17:28:27 -04:00
Fimeg
83e19d572a fix: nil pointer guard in scanner and DryRun when DiscoveryRunner.Run returns nil result 2026-05-29 17:25:10 -04:00
Fimeg
5a27f7b055 refactor: rewire DNFScanner.Scan + DNFInstaller.DryRun to DiscoveryRunner
Replace raw exec.Command (scanner) and SecureCommandExecutor (installer dry-run)
with the unified DiscoveryRunner chokepoint. DryRun no longer manages its own
temp dir — DiscoveryRunner handles sandbox compatibility per ecosystem config.
2026-05-29 17:20:42 -04:00
Fimeg
5651199f16 fix: remove exit-code-100 normalization from DiscoveryRunner — caller concern 2026-05-29 17:18:54 -04:00
Fimeg
0d325bb365 feat: DiscoveryRunner + EcosystemConfig — unified discovery chokepoint 2026-05-29 17:13:48 -04:00
Fimeg
181657db79 readme: tags do not imply stability 2026-05-29 13:19:22 -04:00
Fimeg
e0844760d8 auth: instancelock, GetRefreshTokenForRenew, FOR UPDATE 2026-05-29 13:08:12 -04:00