Commit Graph

833 Commits

Author SHA1 Message Date
Anso 82aabfe64c feat(stack-view): identity header with health state and action hierarchy (#688)
* feat(stack-view): identity header with health state and action hierarchy

Redesign the stack view header around three questions: what is this, is it
healthy, what does it do. Surface Docker healthcheck state, primary image
tag, and image digest; group actions by frequency.

- Backend: extend /api/stacks/:name/containers with healthStatus (healthy /
  unhealthy / starting / none), Image, and ImageID by inspecting each
  container in parallel.
- Frontend: replace flat CardHeader with breadcrumb, italic serif title,
  colored state pill with pulse, and a mono image/digest line with a
  one-click copy button for the full digest.
- Frontend: action hierarchy - primary cyan Restart/Start, outline Stop and
  Update, and an overflow menu for Rollback, Scan config, and Delete.
- Docs: new Stack header section and updated controlling-a-running-stack
  tables showing primary/secondary/overflow grouping.

* test(e2e): open overflow menu to reach stack Delete action

Destructive actions now live under the stack toolbar overflow menu rather
than as a flat top-level button, so the delete flow must click More actions
before selecting the Delete menu item.
2026-04-18 23:38:56 -04:00
Anso 88ec71fcaa feat(ui): replace Switch with TogglePill per design audit (#687)
* chore: ignore local design-system references

Adds frontend/DESIGN.md and .design-bundle/ to .gitignore so the
design-system reference and the audit handoff bundle stay local
to each contributor's machine.

* feat(ui): replace Switch with TogglePill per design audit

Adds a mono-uppercase ON/OFF pill that replaces the sliding Switch
across every call site. The pill uses role="switch" plus aria-checked,
tints success-green when on and neutral card-tone when off, and holds
a stable 60px min-width so the label flip does not shift layout. It
reinforces the tracked-mono uppercase pattern already used for kicker
labels and table column headers, and echoes the UP/DN status language
of the sidebar stack list.

- Add TogglePill component (frontend/src/components/ui/toggle-pill.tsx)
- Replace 17 Switch call sites across 10 files (settings sections,
  SSO providers, scheduled operations, network topology, resources,
  stack auto-heal)
- Remove unused Switch wrapper and radix primitive
- Drop the @radix-ui/react-switch dependency
2026-04-18 22:15:22 -04:00
Anso bd94ef9e15 feat(sidebar): global multi-node stack search with status (#685)
Sidebar search now fans out to every online node and surfaces matches
from the whole fleet under an "Other nodes" section, with UP/DN status
badges fetched in parallel via the bulk /stacks/statuses endpoint.
Clicking a remote result switches the active node and opens the stack.

Also fixes a cmdk reconciliation crash ("Failed to execute 'appendChild'
on 'Node'") that fired when typing in the search input, by disabling
cmdk's internal filtering since filtering is already controlled.
2026-04-18 19:20:08 -04:00
sencho-quartermaster[bot] aa1f022a64 docs: refresh screenshots (#684)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-18 22:56:28 +00:00
sencho-quartermaster[bot] 47eaffdf93 chore(main): release 0.61.0 (#675)
Co-authored-by: sencho-quartermaster[bot] <275163604+sencho-quartermaster[bot]@users.noreply.github.com>
2026-04-18 18:54:42 -04:00
Anso ad90bd9404 feat(settings): add comfortable/compact density toggle (#683)
Adds a per-device appearance preference that compresses row, cell, and
tile padding across dashboard, settings, audit log, and every shared
table without changing typography or layout structure.

Density is stored in localStorage and applied to the document body as a
class that swaps a set of CSS variables. Components opt in by consuming
the tokens, so the global table primitive scales all seven consumers at
once.

A new Appearance section in the Identity group lets users pick between
Comfortable and Compact via a Combobox, with a helper line that
reflects the current choice.
2026-04-18 18:48:58 -04:00
Anso 591dc75d1e feat(audit-log): signal rail, day-banded stream, anomaly detection (#682)
Add a Stream view to the Audit Log that leads with a four-tile signal
rail (events, actors, failure rate with inline sparkline, peak hour)
and presents the feed grouped by day with severity dots, relative
times, and inline anomaly callouts. The existing Table view is
preserved behind a toggle for power users.

Anomaly flags are computed at read time against strictly prior history
and returned on demand via ?with_anomalies=1:
- unusual_hour: hour outside the actor's central 7-day window
- new_ip: IP unseen for this actor in the last 30 days
- first_seen_actor: no prior history in the 30-day window

New /audit-log/stats endpoint returns the signal-rail aggregates over
24h/7d/30d windows; stats are derived from a single 30-day scan.
2026-04-18 18:24:27 -04:00
Anso 95278843cf feat(schedules): next-24h timeline + merge auto-update into schedules (#681)
* feat(backend): add stack update-preview endpoint for readiness board

Adds GET /api/stacks/:stackName/update-preview that returns per-image
semver diff, bump classification, and a stack-level summary powering
the Auto-Update readiness board.

- New UpdatePreviewService parses compose images, inspects local
  digests, fetches remote digests and tag lists, and finds the
  highest compatible semver tag.
- Major bumps are flagged blocked until human review; unknown bumps
  rank below real semver so they cannot mask a major.
- Rollback target is reconstructed through parseImageRef to preserve
  registry ports and drop the Docker Hub library/ prefix.
- Registry helpers (httpGet, auth token, digest, tag list, ref parse)
  are extracted into registry-api.ts and shared with ImageUpdateService.
- 28 Vitest cases cover parse, selection, bump math, digest rebuilds,
  blocked policy, and rollback target construction.

* feat(schedules): next-24h timeline, merge auto-update crud, add readiness board

Replace the flat task table with a Timeline view as the default, showing the
next 24 hours of scheduled work across four lanes (Restart, Update, Scan,
Prune) with a live now rail and per-firing pills. The All tasks tab preserves
the existing CRUD surface.

Merge Auto-update Stack into Schedules as a first-class action and replace the
standalone Auto-Update Policies view with a per-stack Readiness board that
surfaces version diffs, risk tags, changelog previews, and rollback targets
sourced from the stack update-preview endpoint.
2026-04-18 17:48:02 -04:00
Anso 0bf061a745 feat(settings): group sections, add ⌘K search, scope breadcrumb (#680)
* feat(settings): group sections, add ⌘K search, scope breadcrumb

Restructures the Settings Hub sidebar into four labelled groups
(Identity, System, Alerts, Advanced), adds a ⌘K command palette for
section search, and surfaces the active scope (global vs node-scoped)
in the content breadcrumb.

- New `settings/registry.ts` centralises group/item metadata, tier gates,
  glyph assignments, visibility rules, and keyword hints consumed by both
  the sidebar and the command palette
- Cyan 2px left rail + gradient on the active sidebar item; mono-uppercase
  group headers; tier chips inline for locked items
- Scoped ⌘K handler via onKeyDownCapture on DialogContent so the hub no
  longer hijacks the global sidebar shortcut while open
- ScrollArea gains an opt-in `block` prop so the Nodes management table
  can overflow horizontally without Radix's default `display: table`
  wrapper clipping action buttons
- Docs reference updated with the grouped sidebar, scope breadcrumb, and
  ⌘K walkthrough plus refreshed screenshots

* refactor(settings): drop duplicate section headers, redesign system limits, always-visible tier chips

- Remove redundant section titles in every settings page; the dialog header now owns the title and description
- Rework System Limits into a compact row panel with inline-edit chips (warn state, focus ring) and an ON/OFF toggle pill
- Show tier chips on sidebar and command palette whether locked or unlocked, so Skipper/Admiral scope is always legible
- Keep right-aligned action buttons on pages that had a title+button header (Users, Labels, Nodes, API Tokens, Registries)

* fix(settings): seed NumberChip draft on edit instead of via effect

ESLint rule react-hooks/set-state-in-effect flagged the sync effect that
mirrored the external value into local draft state. Replace it with a
startEdit handler that seeds draft from value at click time, so the
button path always reads value directly and no cascading render is
triggered on prop change.

* fix(settings): restore heading role and clean sidebar accessible names

- Wrap the settings dialog title in an h2 so screen readers and E2E locators see a heading again after the in-section headers were removed
- Mark the sidebar glyph aria-hidden so the button's accessible name is just the item label (fixes anchored name matchers)
- Align the MFA E2E helper with the renamed Account section heading
2026-04-18 16:17:24 -04:00
Anso ec7620675e feat(app-store): editorial hero, category rail, security scan signal per tile (#679)
* feat(app-store): editorial hero, category rail, security scan signal per tile

Rework the App Store view into an editorial layout with a 180px category
rail, a featured-template hero, and compact tiles that surface star counts
and vulnerability scan status at a glance. The deploy sheet splits into
Essentials (one-click deploy) and Advanced (full port/volume/env control)
tabs.

Backend enriches /api/templates with scan_status, scan_cve_count, and a
featured flag computed from the highest-star template with recorded stars.
Scan lookups use a single batched SQL query to avoid N+1 round-trips.

* fix(app-store): split firstSentence helper into its own module

The firstSentence helper lived alongside the TemplateLogo component, which
violates react-refresh/only-export-components — a file that exports a
component must not also export non-component values, or Fast Refresh cannot
establish an HMR boundary. Move the helper into appstore/util.ts and update
the two import sites.
2026-04-18 12:55:50 -04:00
Anso 5f6fdfcba8 feat(resources): lead with reclaimable disk banner and per-tab landings (#678)
Replaces the stacked bar + legend with a reclaim-first layout: an amber
hero banner surfaces the total reclaimable bytes and breakdown (unused
images, stopped containers, dangling volumes) with a one-click review &
prune CTA; a three-tile treemap replaces the stacked bar with proportional
areas for Sencho-managed, External, and Reclaimable; and the Volumes tab
gets a two-card landing highlighting the largest volumes by size and
recently changed ones.
2026-04-18 03:00:45 -04:00
Anso c3b06f4b13 feat(fleet): add aggregate masthead and local-vs-remote topology (#677)
Introduce a status masthead above the node grid summarising fleet-wide
CPU, memory, container, and alert counts with a coloured rail that
reflects overall health. Pin the local node at the top of the grid with
a cyan accent rail and a Local badge so it is never confused with a
remote. Add a Topology view toggle that plots the local node on the
left with remotes radiating out, and colour-codes connector lines by
link health.
2026-04-18 02:06:56 -04:00
Anso 748ba46669 feat(dashboard): status masthead, unified gauges, stack health sparklines (#676)
* feat(dashboard): status masthead, unified gauges, stack health sparklines

Rework the home dashboard around a single cyan-railed status masthead
that carries the health state word, node meta, and reasons inline. The
resource block collapses into one strip with a CPU hero sparkline,
memory and disk gauge bars, and a network tile whose sparkline is built
from per-container byte-counter deltas. Stack health becomes an
8-column mono table with row tinting, an uptime column sourced from
the oldest running container's creation time, and a per-stack 10-minute
CPU sparkline. Historical charts pick up a cyan gradient and an amber
peak marker. A shared Sparkline primitive backs all of the above.

* docs(dashboard): refresh screenshot for phase B layout

* fix(dashboard): anchor sparkline bucketing to latest metric timestamp

The cpuHistory, netHistory, and cpuPeakLabel memos called Date.now()
inside useMemo, which violates react-hooks/purity: the rule fires
because re-renders can produce different bucket boundaries from the
same inputs. Derive a historyEndAt anchor from the newest metric
sample in the polled series and thread it through to ResourceGauges.
2026-04-18 01:36:12 -04:00
Anso 7ec189dc35 feat(design-system): adopt cyan as data color, add Instrument Serif, introduce label roles (#674)
- Repoint --chart-1..3 to brand/warning/destructive so cyan leads as the
  primary data color, amber marks peaks, rose marks errors. --chart-4/5
  fall back to neutral greyscale tones tuned per theme.
- Add --data-line-width (1.25px) and --data-peak tokens so sparklines
  across dashboard, stack table, and readiness views share one source
  of truth instead of re-deriving per component.
- Introduce five semantic label role aliases (env, role, owner, risk,
  custom) mapped to existing hues. Raw ten-hue palette stays in place
  for existing consumers; new labels should consume roles.
- Replace Georgia serif with Instrument Serif and add --font-display
  alias in both :root and .dark; expose font-display via @theme inline
  so Tailwind utility classes can use it. Load the family from Google
  Fonts alongside Geist + Geist Mono.
2026-04-18 00:23:43 -04:00
Anso 7d644ce994 chore: add .worktrees to gitignore (#670) 2026-04-18 00:05:40 -04:00
sencho-quartermaster[bot] 1c5653a973 docs: refresh screenshots (#673)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-18 03:00:16 +00:00
sencho-quartermaster[bot] 4c79093abd chore(main): release 0.60.0 (#672)
Co-authored-by: sencho-quartermaster[bot] <275163604+sencho-quartermaster[bot]@users.noreply.github.com>
2026-04-17 22:58:20 -04:00
Anso 5bb4b01953 feat: auto-heal policies for unhealthy containers (#671)
* feat(db): add auto_heal_policies and auto_heal_history schema and CRUD

Adds two new SQLite tables (auto_heal_policies, auto_heal_history) to
DatabaseService.initSchema() and exposes CRUD methods: getAutoHealPolicies,
getAutoHealPolicy, addAutoHealPolicy, updateAutoHealPolicy,
deleteAutoHealPolicy, recordAutoHealHistory, getAutoHealHistory,
incrementConsecutiveFailures, resetConsecutiveFailures, setPolicyEnabled.
Also adds AutoHealPolicy and AutoHealHistoryEntry TypeScript interfaces.

* feat(events): track health-status duration and expose state accessors

- Add healthStatus and unhealthySince fields to InternalContainerState
- onHealthStatus now records unhealthySince timestamp on first transition
  to unhealthy, and clears it when the container recovers or restarts
- onStart resets both fields so a restarted container begins from 'starting'
- Add listContainerStates() and getContainerState() public accessors for
  use by the upcoming AutoHealService evaluator

* fix(auto-heal): key allowlist in updateAutoHealPolicy, cascade delete, extract ContainerHealthSnapshot

* feat: add AutoHealService evaluator singleton

Polls every 30 s, matches containers to enabled policies via Compose
labels, and restarts containers that have been unhealthy beyond the
configured threshold. Enforces cooldown, per-hour rate cap, and
recent-user-action suppression; auto-disables policies after repeated
consecutive failures. Also adds DockerEventManager.getService() accessor
required by the evaluator.

* fix(auto-heal): prune stale restartTimestamps, guard undefined policy id

- Prune restartTimestamps entries for containers no longer running after
  each container list fetch, preventing unbounded map growth from dead
  container IDs.
- Guard against policies with undefined id at the start of the per-policy
  loop; warn and skip rather than proceed with a non-null assertion.
- Extract handleAutoDisable private helper to bring executeHeal under 30
  lines and isolate the auto-disable side-effect sequence.
- Move ContainerInfo type to module scope.

* feat: add auto-heal API routes and wire AutoHealService lifecycle

Registers five REST endpoints under /api/auto-heal/policies (list, create,
patch, delete, history) with requirePaid + requireAdmin guards and Zod
validation. Wires AutoHealService.start()/stop() into the server startup
and graceful-shutdown blocks alongside MonitorService.

* test: add AutoHealService and DatabaseService auto-heal unit tests

- 15 unit tests for AutoHealService.shouldHeal covering all decision branches
  (healthy state, duration threshold, user-action suppression, cooldown,
  rate limiting, and correct skipReason values)
- 13 integration tests for DatabaseService auto-heal CRUD: policy round-trip,
  stack-name filter, partial update, cascade delete, history ordering/limit,
  consecutive failure counters, and setPolicyEnabled toggle

* fix: log AutoHealService shutdown errors consistently

* fix(api): requireAdmin-first guard order and try/catch on auto-heal routes

* feat(ui): add StackAutoHealSheet component

* feat(ui): add Auto-Heal context menu item to EditorLayout

* fix(ui): StackAutoHealSheet label, token, a11y, and useEffect fixes

- Rename 'All services in stack' to 'All services' in combobox options and placeholder
- Replace text-green-600 with text-success design token in actionColorClass
- Add htmlFor/id pairs to all four numeric form inputs for accessibility
- Inline fetch logic into useEffect, removing stale closure risk and eslint-disable comment
- Remove now-unused fetchPolicies and fetchServices standalone functions
- Update 'Auto-disable after' label to 'Auto-disable after (failures)' for clarity
- Add toast.error in policy fetch failure path; services fetch silently skips as before

* docs: add auto-heal-policies feature documentation

* test(e2e): add auto-heal policies CRUD spec

* fix(docs): correct auto-heal-policies nav position in docs.json
2026-04-17 22:45:06 -04:00
sencho-quartermaster[bot] 1efa91a990 docs: refresh screenshots (#669)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-18 02:07:58 +00:00
sencho-quartermaster[bot] bc91c754c8 chore(main): release 0.59.0 (#668)
Co-authored-by: sencho-quartermaster[bot] <275163604+sencho-quartermaster[bot]@users.noreply.github.com>
2026-04-17 22:06:16 -04:00
Anso 8e7a567f69 feat: pilot agent outbound-mode for remote nodes (#667)
* feat: pilot agent outbound-mode for remote nodes

Adds a second mode for managing remote nodes: the agent dials an outbound
WebSocket tunnel to the primary, so the remote host no longer needs an
inbound port, a reachable URL, or its own TLS certificate. Works behind
NAT, residential routers, and corporate firewalls.

The primary multiplexes HTTP and WebSocket requests over a single tunnel
via a hybrid JSON + binary frame protocol, bridged through a per-tunnel
loopback server so existing proxy and upgrade handlers route pilot-mode
nodes identically to proxy-mode ones.

Enrollment uses a single-use 15-minute pilot_enroll JWT exchanged for a
long-lived pilot_tunnel credential on first connect. Proxy mode continues
to work unchanged and both modes are supported side-by-side.

* test(e2e): switch to proxy mode before asserting api_url field

Remote nodes default to Pilot Agent mode, which hides the api_url input.
The SSRF-validation tests need proxy mode, so the helper now selects
Distributed API Proxy after picking Remote type before asserting the
field is visible.

* fix(e2e): wire Combobox id prop so node-mode selector resolves

The Combobox trigger button had no id, leaving its Label orphaned and
making getByRole name-based lookups fail. Adding id to the primitive,
passing id="node-mode" from NodeManager, and updating the E2E helper to
use #node-mode fixes both the a11y regression and the CI timeout.
2026-04-17 20:31:43 -04:00
sencho-quartermaster[bot] 0a536ae653 docs: refresh screenshots (#666)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-17 18:44:56 +00:00
sencho-quartermaster[bot] 7d390bd54d chore(main): release 0.58.1 (#660)
Co-authored-by: sencho-quartermaster[bot] <275163604+sencho-quartermaster[bot]@users.noreply.github.com>
2026-04-17 14:43:14 -04:00
Anso e25d43642b chore(security): a11y polish on scan comparison and history (#665)
Add aria-label to the Scan history entry button, aria-labels to the
pagination chevrons, and aria-live polite + aria-label on the page
indicator so screen readers hear "Page 2 of 5" instead of "2 slash 5"
when navigating the comparison sheet and scan history list.
2026-04-17 14:24:14 -04:00
Anso 08b63f216e docs(security): troubleshooting entries for scan comparison (#664)
Add a node-local-scope sentence to the comparison intro, document the
cross-image Shared relabeling, and add three troubleshooting entries:
Compare disabled, unexpected results (cross-image and truncation), and
older scan missing from Scan history.
2026-04-17 14:23:56 -04:00
Anso d654266b3e chore(security): add diagnostic log to scan compare handler (#663)
Emit a [Compare:diag] entry gated on developer_mode with the scan ids,
effective tier, input and bucket sizes, suppression count, and
truncation flag. Gives operators a trace to correlate with user reports
of unexpected compare results without adding hot-path cost.
2026-04-17 14:23:38 -04:00
Anso e0f2e230a6 fix(security): relabel unchanged bucket for cross-image comparisons (#662)
When comparing scans from two different image refs, same-CVE+package
matches are not necessarily the same finding. Relabel the pill and
row status from 'Unchanged' to 'Shared' in that case to convey that
the caveat in the warning banner applies.
2026-04-17 14:23:23 -04:00
Anso 2fce1d3baf fix(security): server-driven pagination for scan history (#661)
Scan history fetched a fixed 200 most-recent rows and paginated them
client-side, so older scans silently fell off mature nodes where
baselining is most valuable. The list now fetches one page at a time
via offset, with status=completed and imageRefLike filters applied
server-side. Search input is debounced to avoid per-keystroke fetches.
2026-04-17 14:06:23 -04:00
Anso c211f655c3 fix(security): signal when scan comparison is truncated (#658)
Compare endpoint loads up to 1000 findings per scan. When a scan exceeds
this cap, the response now includes truncated=true and row_limit, and
the comparison sheet surfaces a banner so users understand the diff may
be incomplete. Also exposes total_vulnerabilities on scanA/scanB for UI
use and logs a warning when truncation occurs.
2026-04-17 13:53:53 -04:00
Anso f4e3c267cd test(security): add scan comparison coverage (#656)
Backend supertest suite for GET /api/security/compare covers tier gating,
input validation, cross-node isolation, diff partitioning by
vulnerability_id::pkg_name, suppression application, and cross-image
comparison. Frontend vitest + React Testing Library scaffolding with
component tests for ScanComparisonSheet (loading, error recovery,
cross-image warning, filter pills, reload on id change) and
SecurityHistoryView (mount fetch, selection cap, oldest-first baseline
ordering, tier gating).
2026-04-17 13:44:40 -04:00
sencho-quartermaster[bot] fe8abaac55 docs: refresh screenshots (#657)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-17 17:42:10 +00:00
sencho-quartermaster[bot] d2c6c26fe8 chore(main): release 0.58.0 (#655)
Co-authored-by: sencho-quartermaster[bot] <275163604+sencho-quartermaster[bot]@users.noreply.github.com>
2026-04-17 13:40:17 -04:00
Anso 29ed0524c1 feat(security): severity-aware scheduled scan notifications (#654)
Enrich scheduled vulnerability scan completion notifications with
per-severity CVE counts so recipients can triage from the message body
alone. Expose the scan action in the schedule creation UI, require an
explicit node_id, and harden fire-and-forget alert dispatches so a
failing webhook cannot crash the scheduler.

Notification body now reports scanned/skipped/failed counts plus
critical/high/medium totals aggregated across fresh and cached scans,
reflecting the current node posture rather than only what was newly
scanned on this run.
2026-04-17 11:08:46 -04:00
sencho-quartermaster[bot] f9dc86bbfd docs: refresh screenshots (#653)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-17 12:20:09 +00:00
sencho-quartermaster[bot] 5ec9d50b41 chore(main): release 0.57.0 (#647)
Co-authored-by: sencho-quartermaster[bot] <275163604+sencho-quartermaster[bot]@users.noreply.github.com>
2026-04-17 08:18:25 -04:00
Anso 12bbf86dc4 feat(security): export scan results as SARIF 2.1.0 (#652)
Adds a new SarifExporter service that builds a SARIF 2.1.0 document
from the stored scan findings (vulnerabilities, secrets, misconfigs).
Rule IDs are namespaced to avoid collisions in a flat result list.
Suppressions carry through as SARIF suppressions[] entries so GitHub
code scanning and Defender for Cloud see the same accepted status
shown in the UI.

Exposed via GET /api/security/scans/:id/sarif, admin + paid-tier
gated to match the SBOM export precedent. A SARIF button appears
in the scan sheet next to SBOM and CSV for paid tiers.
2026-04-17 08:17:36 -04:00
Anso a95bf1ff33 feat(security): secret and misconfiguration scanning (#651)
Extends Trivy scans with secret detection in image filesystems and
misconfiguration scanning for Compose stacks. Adds tabs to the scan
drawer for vulnerabilities, secrets, and misconfigs. Secret matches
are redacted server-side (first 8 chars + ellipsis) before storage.

- TrivyService: --scanners vuln,secret for images; trivy config for stacks
- DB: scanners_used/secret_count/misconfig_count cols; secret_findings,
  misconfig_findings tables; cache key scoped by scanners
- Routes: POST /security/scan accepts scanners array (requirePaid when
  secret requested); POST /security/scan/stack; GET .../secrets and
  .../misconfigs (paid-tier reads)
- UI: tabs in VulnerabilityScanSheet; scan-options dropdown on images;
  Scan config button on stack header
2026-04-17 07:57:08 -04:00
Anso 732fc95415 feat(security): fleet-replicated CVE suppression list (#650)
Operators can accept known-benign findings once and have Sencho filter
them out of scan drawers, comparison views, and other read surfaces.
Suppressions replicate from the control instance to every remote node.

* New cve_suppressions table with a COALESCE-based unique index so NULL
  scope slots collide the way users expect
* Admin + paid-tier CRUD routes; writes are rejected on replicas
* Read-time filter enriches vulnerability details and compare payloads
  without mutating stored counts
* Settings > Security panel for managing rules, per-CVE suppress action
  in the scan drawer, dimmed rows with a shield-off indicator
* Vitest unit tests for the filter (glob, expiry, specificity) and
  route tests (auth, tier, replica, UNIQUE conflict)
2026-04-17 05:16:34 -04:00
Anso 708d15b2b3 feat(fleet): replicate scan policies across managed nodes (#649)
Scan policies now propagate from the control Sencho instance to every
registered remote. The control is the source of truth; replicas render
rules read-only with a managed-by-control banner. Pushes fire on every
policy write, record per-node success and failure on a new
fleet_sync_status table, and use node_proxy Bearer tokens exclusively
so only sibling Senchos can apply incoming sync payloads. Policy scope
now travels as a string identity (api_url or a local sentinel) so
node-scoped rules evaluate correctly on each target.
2026-04-16 23:57:08 -04:00
Anso 8ee0c0c476 feat(security): scan comparison UI (#648)
Side-by-side vulnerability scan comparison with two entry points:

- Compare button plus inline baseline picker inside the scan drawer.
- New Scan History page reachable from the Resources Hub, grouping
  completed scans by image with a checkbox selection flow.

The comparison sheet shows a severity delta ribbon, Added/Removed/Unchanged
filters, and a paginated CVE table. Cross-image comparisons are allowed
but flagged with a warning. Compare access is gated to Skipper and
Admiral tiers; the underlying /security/compare endpoint is unchanged.
2026-04-16 23:15:36 -04:00
Anso e660d2a658 feat(scheduler): notify on scheduled scan completion (#646)
Scheduled scan tasks now dispatch a completion alert through the
existing notification system: info when every image scanned cleanly,
warning when one or more images failed. The alert includes the task
name and the run's scanned/cached/failed summary so operators do not
need to open the task history.
2026-04-16 22:55:30 -04:00
sencho-quartermaster[bot] 3c7539d3b4 docs: refresh screenshots (#645)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-17 01:35:31 +00:00
sencho-quartermaster[bot] 3d112969cf chore(main): release 0.56.0 (#644)
Co-authored-by: sencho-quartermaster[bot] <275163604+sencho-quartermaster[bot]@users.noreply.github.com>
2026-04-16 21:33:51 -04:00
Anso 61bac08027 feat(security): one-click managed Trivy install (#643)
* feat(security): one-click managed Trivy install

Add a Vulnerability Scanner card to Settings, Security with install,
update, uninstall, and auto-update controls (Admiral-only). The installer
downloads a verified Trivy release into the existing data volume at
/app/data/bin/trivy and defaults the cache to /app/data/trivy-cache, so
no host mounts or extra env vars are required. Detection probes the
managed path, a TRIVY_BIN override, and the host PATH, distinguishing
managed vs host installs. A daily scheduled check surfaces available
Trivy updates, installs them automatically when opted in, and dedupes
notifications per version.

* fix(frontend): silence react-hooks/set-state-in-effect in useTrivyStatus

The initial status fetch and managed-source update check both call
setState from the effect body. Match the existing pattern used in
useDashboardData / SSOSection and disable the rule at the call site.
2026-04-16 21:29:44 -04:00
sencho-quartermaster[bot] 759776792d docs: refresh screenshots (#642)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-17 00:36:24 +00:00
sencho-quartermaster[bot] baf3123efa chore(main): release 0.55.1 (#641)
Co-authored-by: sencho-quartermaster[bot] <275163604+sencho-quartermaster[bot]@users.noreply.github.com>
2026-04-16 20:34:43 -04:00
Anso dc8370f5a4 fix(security): harden Trivy scan lifecycle, logging, and docs (#639)
* fix(security): harden Trivy scan lifecycle, logging, and docs

- Call TrivyService.initialize() at startup so capability state is
  accurate before first request; add periodic re-detect to the scheduler
  so newly installed Trivy binaries light up without a restart.
- Add markStaleScansAsFailed sweep (+ idx_vuln_scans_status index) to
  recover any scan row left in_progress after a crash or timeout; sweep
  runs before the paid-tier gate so every tier self-heals.
- Split scanImage persistence into beginScan/finishScan so the manual
  scan route owns a single code path and can return a scanId synchronously
  while work continues asynchronously.
- Validate image refs on /api/security/scan and /sbom via new utility;
  defense-in-depth against shell-metacharacter payloads.
- Dispatch a warning-level alert when a post-deploy scan fails so the
  operator has a user-visible path to the failure instead of a silent log.
- Share DIGEST_CACHE_TTL_MS and severity ordering across service and
  route layers; remove dead invalidateDetection().
- Add [Trivy:diag] logging gated behind developer_mode for support
  diagnostics; production logs unchanged.
- Frontend: defensive toast fallback chain, sr-only SheetDescription,
  and a truncation badge when the 500-item detail fetch is capped.
- Tests: extend trivy-service and vulnerability-db suites; add
  image-ref and severity unit tests.
- Docs: expand vulnerability-scanning troubleshooting with recovery,
  re-detect, and diagnostic-log guidance; link Dockerfile comment to
  trivy-setup.

* fix(security): drop unnecessary escape in image-ref forbidden-char regex
2026-04-16 20:32:38 -04:00
Anso f8eb1b4e88 chore(deps): bump protobufjs transitive to patch GHSA-xq3m-2v4x-88gg (#640) 2026-04-16 20:12:47 -04:00
Anso ce2fb1a5a0 ci: auto-scaffold release blog post every 5th tag (#638)
Adds a workflow that fires on every v* tag push, computes the window
since the latest release post's anchor version in the sencho-website
repo, and (when the window reaches 5) opens a draft PR in
sencho-website with a fully pre-filled scaffold: grouped
ChangelogSection blocks parsed from CHANGELOG.md, version field set to
the new anchor, and a checklist body covering intro, screenshot, docs
link, and reading time. No auto-publish; the human writes the
narrative.

Uses the same sencho-token-app GitHub App as release-please, with its
installation extended to include sencho-website.
2026-04-16 16:16:04 -04:00
sencho-token-app[bot] 1e97acf281 docs: refresh screenshots (#637)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com>
2026-04-16 19:06:19 +00:00