Watch
1
0
Fork
You've already forked RedFlag
0
Commit graph RedFlag/agent
Author SHA1 Message Date
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
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
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
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
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
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
e0844760d8 auth: instancelock, GetRefreshTokenForRenew, FOR UPDATE 2026-05-29 13:08:12 -04:00
Fimeg
e8afcc7994 feat: machine-bound token renewal + refresh-token rotation with reuse detection
Bind /renew to the registered machine so a stolen refresh token can't mint
tokens from another host. Rotate the refresh token on every renewal; replaying
a consumed token whose successor is also consumed revokes the family. Accept-
previous-once grace covers agent crash-before-save. Typed auth errors so the
polling loop renews on 401 and treats refresh/machine failures as terminal.
2026-05-29 10:47:48 -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
ea068c2213 feat: supply-chain capability-token gate (executor, signing, mint/deliver, agent consumer)
Server mints Ed25519-signed capability tokens authorizing one package
operation over a resolved closure; a privileged network-less Rust executor
verifies signature + artifact hashes and performs the op. Replaces the
rs-helper socket-decision daemon's role with signed tokens (trust root off-host).

- helper/: Rust executor keystone (stdin token -> window/bind/keyring/verify_strict
  -> artifact hash -> replay guard -> one op, no shell, env_clear, fail-closed)
- capability/ (mirrored in server + agent): token type, canonical encoder,
  sign/verify; cross-language byte-identity proven by contract tests
- server: SignCapabilityToken, CapabilityMinter (mint at approval after Layer-1
  hash), capability_tokens queries, migration 042, delivery + receipt endpoints
- agent: supplychain consumer (bind-check + allowlist + invoke executor + receipt),
  client methods, polled each check-in via loop.processCapabilityTokens
2026-05-28 14:53:05 -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
1a4425b09f Layer 1: Hash Registry — verify package SHA256 before installation
Server:
- ApproveUpdate() now calls computeAndStorePackageHash() to download artifact,
  compute SHA256, and store in DB
- GET /dashboard/updates/verify-hash endpoint for agents to fetch hashes

Database:
- Migration 040: added expected_sha256 VARCHAR(64) to current_package_state table

Agent:
- HandleInstallUpdates() fetches expected hash from server before install
- DNFInstaller.VerifyHash() downloads and verifies package hash
- APT/Docker/Winget/WindowsUpdate: hash verification stubs (fail-open)
- LRU cache (100 entries) to reduce server load

Security:
- Hash verification happens BEFORE package manager install
- Mismatch blocks installation with error logged
- Fail-open: hash fetch failure doesn't block, but verification failure does
2026-05-26 13:25:54 -04:00
Fimeg
4868a98830 fix: nonce TTL = 2× check_in_interval to kill dispatch race
The agent's validateNonce was hardcoded to 5 min, equal to the default
check-in interval. When queue-to-fetch timing drifts past expiry (e.g.
agent restarted, check-in cycle reset) the nonce expires before the agent
can fetch the command. Age+1s-late → nonce_expired → command rejected.

Changed validateNonce to accept maxAge, computed in HandleUpdateAgent as
2 × cfg.CheckInInterval. The nonce still binds the update to this agent
within a bounded window — same security model, no auto-retry magic.
2026-05-25 15:00:02 -04:00
Fimeg
eac47f8826 fix: add /usr/local/bin to ReadWritePaths, wire heartbeat_source to metadata
ReadWritePaths was missing the install target dir, so self-upgrade cp'd
against a ProtectSystem=strict read-only mount even via sudo. Added
${INSTALL_DIR} to the template and /usr/local/bin to the Go installer
constant.

queueSystemHeartbeat now writes heartbeat_source=system to agent metadata,
mirroring the pattern in TriggerHeartbeat / triggerSystemHeartbeat, so
GetHeartbeatStatus returns the correct source and the dashboard renders
the blue indicator for system-initiated heartbeats.
2026-05-25 14:54:56 -04:00
Fimeg
2acc8b1f1d fix(agent): drop unused handlers import in service/windows.go
Leftover from 8d441a5b RunPollingLoop extraction, which deleted the
forked Windows handler block but left the import. Blocked the Windows
agent cross-compile in docker build.
2026-05-25 13:26:45 -04:00
Fimeg
1a4696f405 bump: v0.2.0.4 and fix service AgentVersion drift
- Bump server AgentVersion + ConfigVersion from 0.2.0.3 to 0.2.0.4
- Remove stale hardcoded AgentVersion="0.1.16" from Windows service;
  use version.Version (injected via ldflags at build time) instead
- Delete orphaned polling-loop body that survived the CRITICAL-007
  refactor (the dead code between lines 178-360)
2026-05-25 12:56:35 -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
07872e02c9 fix: unify cross-platform command dispatch
dispatch.go::DispatchCrossPlatformCommand centralizes all scan + update_agent
routing. loop.go and service/windows.go now call through it instead of
maintaining duplicate switches. Windows service switch retains only
windows-specific cases (collect_specs, dry_run_update).
2026-05-24 20:08:31 -04:00
Fimeg
32c80debfb fix(ISSUE-005): add log sanitizer; integrate into SecurityLogger
sanitize.go: ANSI strip, control-char scrub, per-field 4KB cap, total 16KB
cap, JSON validation. SecurityLogger.writeToFile now routes all string fields
through SanitizeForLog before writing.
2026-05-24 20:08:23 -04:00
Fimeg
37d9731f21 fix(CRITICAL-003): convert all timestamps to UTC/TIMESTAMPTZ (migration 036)
Migration 036 converts every TIMESTAMP column to TIMESTAMPTZ across 20+ tables,
interpreting existing values as UTC. Server handlers, queries, services, and
agent files converted from time.Now() to time.Now().UTC() consistently.
Monotonic-paired time.Now() sites preserved where needed.
2026-05-24 20:08:10 -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
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
7fb61a36ba fix: remove updates subsystem resurrection paths
The legacy "updates" virtual subsystem was deprecated (scheduler.go:159
skips it), but five paths kept re-creating the row in agent_subsystems:

- agent migration detection flagged missing "updates" subsystem as a
  missing security feature, prompting the executor to re-add it to the
  local config on every startup migration
- server install-config template, scanner-timeout list, and intervals
  map all kept "updates" alive in the config artifact sent to agents

Removed at all five sites. Scheduler skip logic, per-scanner mapping
helper (subsystems.go:236), and update-report data path remain — they
are not subsystem-row creators.

Historical migration 024_disable_updates_subsystem left intact.
2026-05-22 13:33:50 -04:00
Fimeg
0f6af8c505 fix: add missing migration import to agent validator
validator.go references MigrationDetection and AgentFileInventory from
the parent migration package but was missing the import, causing the agent
build to fail since the aggregator-agent rename in c8b0ee10.
2026-05-22 09:12:45 -04:00
Fimeg
7abe331bf8 command-lifecycle v2: add received state, disk-persisted dedup, authenticated download, timeout reconciler
Migration 033 adds the 'received' status to agent_commands so the server can
distinguish "agent confirmed receipt" from "sent but may be lost in flight."
Stuck-command re-issuance now excludes received commands — the TimeoutService
handles the longer timeout for those (default 30m) vs the per-poll re-issuer
(sent/pending at 5m).

The agent side: disk-persists executed command IDs to survive restart (closes
the in-memory-only dedup gap), reports received_command_ids on each check-in so
the server transitions sent→received before issuing new work, and authenticates
binary downloads with JWT+X-Machine-ID (was unauthenticated http.Get — would
401 in production).

TimeoutService extended with reconcileAgentUpdates: clears is_updating when
current_version matches updating_to_version (success), or after a 15m threshold
(timeout, with system_event) so the dashboard never shows "updating" forever.
isVersionUpgrade replaced with utils.IsNewerVersion (no panic on 2-part
versions, no false-reject on 4-part).

MarkCommand* failures elevated from [WARNING] to [ERROR] + should_retry
response hint so agents know to re-deliver results (silent drops were ETHOS #1
violations).

Fixes: build broken on public since eac8a012 (command.go accidentally emptied).
2026-05-22 08:45:16 -04:00
Fimeg
3c510d51c0 chore: add windowsupdate COM interface stubs and update third-party licenses 2026-05-21 13:48:42 -04:00
Fimeg
c8b0ee1040 refactor: rename aggregator-agent to agent for public release 2026-05-21 13:19:18 -04:00