mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 20:00:08 +00:00
2911ccfe2bbd8cf618d12b75c8bc9465a954700a
1603 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2911ccfe2b |
fix: request registry tokens with the target repository scope (#1478)
* fix: request registry tokens with the target repository scope The image-update detector authenticated to registries by reusing the scope echoed in the registry's GET /v2/ ping. That ping carries no repository context, and ghcr.io answers it with a placeholder scope (repository:user/image:pull), so the token was requested for the wrong repository and rejected. Every ghcr.io-backed image (including lscr.io, which delegates auth to ghcr.io) then failed its manifest lookup and was reported as "Registry unreachable", while Docker Hub and quay.io kept working. Always request a pull scope for the repository being checked rather than the echoed placeholder. Also report the actual failure cause: getRemoteDigestResult now distinguishes an authentication failure, a rate limit (with retry-after), a missing image, a registry error, and a genuinely unreachable registry, instead of collapsing every failure into "Registry unreachable". getRemoteDigest stays a digest-or-null wrapper so the update-preview path is unchanged, and listRegistryTags shares the same token path so it now resolves on ghcr.io/lscr.io too. * fix: neutralize control characters in the registry digest error log The error-path console.error in getRemoteDigestResult interpolated the image ref and the caught error message, both of which originate from compose-authored input. Route them through sanitizeForLog so a crafted image string or upstream error text cannot forge multi-line log entries (log injection). The returned reason and the digest logic are unchanged. |
||
|
|
628400ac19 |
fix: explain why a failed pre-deploy scan blocks a deploy (#1477)
When the pre-deploy gate could not scan or evaluate an image (a compose parse error, a scan failure, an invalid image reference, or an evaluation error), it pushed a synthetic violation with zero counts and no reason. The block dialog then showed "0 critical, 0 high" with no explanation and only Close or admin bypass, so an operator could not tell why the deploy was blocked or what to fix. The synthetic violation now carries the failure reason in an error field, which flows through the existing 409 block payload. The block dialog renders that reason under a "Could not be scanned" label instead of a misleading zero-count row, and shows a recovery hint pointing at the fix-and-deploy-again path. |
||
|
|
000a592388 |
fix: copy the full finding set when reusing a cached scan for the deploy gate (#1476)
The pre-deploy gate reuses a cached scan for the same image digest within 24h. The cache-hit path copied only the first 1000 detail rows while keeping the cached scan's full aggregate total, so the persisted preflight scan stored fewer detail rows than its total_vulnerabilities. The gate's integrity check in evaluateImageRisk treats that mismatch as untrustworthy and fails closed on every active KEV or fixable input, blocking a deploy with no actual matching finding and bypassing honored suppressions. The cache-hit copy now reads the complete detail set (getAllVulnerabilityDetails) so the persisted scan keeps stored details equal to total_vulnerabilities, matching a fresh scan and letting the gate evaluate the real findings. |
||
|
|
89a13f51e9 |
fix: keep security posture accurate for secret-only scans and any-severity KEVs (#1475)
The Security overview and exploit-intel surfaces picked the latest scan per image without restricting to scans that ran the vulnerability scanner, and counted known-exploited (KEV) findings only among Critical/High. Two effects: - A newer secret-only node scan became the latest scan for an image and clobbered its Critical/High/fixable/KEV posture to zero, which could read a false Secure state. - A Medium or Low severity KEV that the pre-deploy gate blocks on produced zero overview and exploit-intel rows, so the page disagreed with the gate. Posture queries now select the latest vulnerability-bearing scan per image, the image summary sources its vulnerability counts from that scan via a LEFT JOIN while still counting secret and misconfiguration findings from the latest scan overall, and knownExploited is counted from a dedicated any-severity KEV query that mirrors the gate. |
||
|
|
6256131dc6 |
fix: render node-update changelog notes as formatted markdown (#1474)
The Changelog tab in the Node Updates sheet rendered GitHub release notes as raw markdown inside a preformatted block, so headings, bullet lists, and issue/commit links showed as literal markup and were hard to read. Render the notes with a small react-markdown wrapper styled to the design tokens (raw HTML is intentionally not rendered, keeping it safe). Also: - Replace the bare loading spinner with a content-shaped skeleton. - Add a graceful empty state when no notes are available, with a link to the online changelog. - Add a "View on Sencho" link next to "View on GitHub". - Stop the release-notes fetch from re-firing on every failure by tracking whether a fetch has settled, so a null result lands on the empty state instead of looping; Recheck resets it to force a fresh fetch. |
||
|
|
1de49f8b1a |
fix: name matched risk inputs in policy scan banner and alerts (#1473)
The pre-deploy gate names the inputs that matched a scan policy (a known-exploited CVE, a fixable Critical/High, or a severity threshold), but the informational post-scan surfaces still framed every violation as a severity ceiling. The scan detail banner read "blocks severities at or above X, highest severity is Y" and the scheduled-scan alert read "<severity> exceeds <maxSeverity>", which is wrong for a KEV- or fixable-only policy that never gated on severity. Persist the matched reasons on the policy evaluation, carry them on the scheduled-scan violation, and render them on the banner so every policy surface names the input that actually matched. Evaluations persisted before this change carry no reasons: the parser defaults the field to an empty array and the banner falls back to a plain violation notice. |
||
|
|
d9b7911f12 |
fix: distinguish failed image-update checks from "up to date" (#1470)
* fix: distinguish failed image-update checks from "up to date" The image-update detector collapsed every failure (registry unreachable, missing auth, rate limit, unresolved local digest) into hasUpdate:false and dropped the captured reason, so a failed check was indistinguishable from a current image and never raised a notification, even while a manual stack update still pulled a newer image. Detection now records a tri-state per stack (ok / partial / failed) with the failure reason, exposed via a new GET /api/image-updates/detail (the boolean GET / is unchanged so fleet aggregation is unaffected). A fully-failed check preserves the last known has_update, so a transient outage neither erases a real update nor flaps the notification state. The sidebar shows a muted "couldn't check" indicator with the reason on hover, and the Update board lists stacks whose check failed in a "could not be checked" advisory. Detector hardening: the manifest digest lookup issues HEAD first (falling back to GET) so it no longer draws down Docker Hub's anonymous pull-rate budget, and local RepoDigest matching is normalized so official library/* images resolve their digest instead of falling through to a silent "no update". * fix: preserve confirmed updates through partial checks; tighten failure surfacing Address review findings on the tri-state image-update detection: - A partial check (some images errored) no longer erases a previously confirmed update; only a fully-ok check can lower has_update, so a single image's registry blip cannot drop the stack's update and re-fire the notification on recovery. Adds a regression test. - The image-level catch stores getErrorMessage(e) rather than raw String(e), since that value surfaces verbatim in the sidebar tooltip and readiness advisory. - useImageUpdates and the readiness detail fetch now log unexpected non-ok responses instead of silently leaving stale state. - Remove an unused checkFailedCount derivation (the row indicator is driven by the checkStatus prop). - Reword the recordStackCheckFailure docstring and the HEAD-first comment. |
||
|
|
e3b3c3b857 |
fix: export the full vulnerability list to CSV (#1472)
The scan detail sheet fetches a capped page of vulnerabilities for display, then told operators to "Export CSV for the complete list". The CSV writer only serialized the rows already in memory, so for a scan with more findings than the page cap the CSV silently dropped everything past the cap: the recovery path the notice promised did not exist. Export now pages past the API's per-request cap and serializes every row when the loaded set is short of the total, falling back to the in-memory rows when they are already complete. The CSV action shows a spinner and disables while the export runs. |
||
|
|
ca496c89dc |
fix: name matched risk inputs in policy block messages (#1471)
The auto-update, bulk-label, scheduler, and blueprint deploy block messages hardcoded "image(s) exceed <max_severity>", which is wrong under the risk-first policy model: a block can be driven by a known-exploited (KEV) or fixable Critical/High input while the severity threshold was never the trigger. In those cases the message named a severity ceiling the policy did not enforce. Route all four message paths through a shared summarizeBlockReasons helper (the same reason text the deploy-gate 409 response and the block dialog already use), so every surface names the inputs that actually matched. Falls back to a generic phrase when no reason was recorded. |
||
|
|
5e2194f4a3 |
fix(dependency-map): stop flagging env-var bind mounts as missing volumes (#1468)
* fix(dependency-map): stop flagging env-var bind mounts as missing volumes
The Fleet Map "Missing dependencies" anomaly fired false positives for
services whose volumes use env-var-interpolated bind sources such as
${BACKUPS_PATH}:/backups. The compose parser classified the source as a
named volume because the ${VAR} token contains no slash, then the runtime
presence check found no matching volume and flagged it.
A compose named-volume key can never contain $, so any source with an env
var is a bind path whose value is unresolvable at parse time. Exclude it
from named-volume classification.
Closes #1464
* docs(dependency-map): clarify the env-var volume guard comment
Note that the $ check also covers the $$ literal-dollar Compose escape, and
state the named-volume key charset that makes the guard safe. No behavior change.
|
||
|
|
26d557a701 |
feat: purge scan data for deleted images and stacks (#1467)
Vulnerability scan rows were never cleaned up when their image was removed from Docker or their stack was deleted, so the Security Overview (including the Top exploit-risk findings card) kept surfacing findings for artifacts that no longer exist. Scan results now reflect what is still on the host: - Deleting a stack immediately purges its stack:<name> compose-config scan. - A background reconciliation in the monitor janitor removes scans whose image is gone from the node, or whose stack folder no longer exists. It is fail-safe: a scan is only removed when its artifact is positively known to be gone, the Docker image list is read with a timeout (skipped on failure), and stack scans are reconciled only when the stack list is non-empty. - An opt-out "Remove scans for deleted images and stacks" setting (on by default, per-node) lets operators retain scan history for removed artifacts. Scan deletes remove child findings explicitly, since SQLite foreign-key cascade is not enabled on the connection. |
||
|
|
eaf0642d88 |
fix: reject atomic restore when a checksummed backup file is missing (#1466)
Restore verification iterated the backup directory listing, so a file recorded in the .checksums manifest but absent from the backup slot was never checked. The orphan removal then deleted the live file and the copy restored nothing, reporting success while leaving the stack unrecoverable. Walk the manifest instead of the directory listing: a recorded file that the slot no longer holds now aborts the restore before any file is touched, alongside the existing corrupt-content check. Also fail backup creation when a managed file exists but cannot be read (non-ENOENT), instead of silently omitting it and producing an incomplete backup with the same failure mode. |
||
|
|
1c82e3e1d4 |
fix: contain file-explorer writes and browse reachable out-of-base binds (#1465)
The file-explorer editor save resolved a dangling symlink leaf to the link path and wrote through it with a plain writeFile, which followed the link and created a file outside the bind/stack root. Reject a resolved leaf that is itself a symlink (mirroring the managed-stack guard) and promote the save through the atomic stage-and-rename helper, so the editor save matches its documented atomicity and can never leave a partial file or land outside the root. Bind-root discovery reported every source outside the compose base as unreachable without probing it, so a config directory mounted into both the app and the Sencho container was wrongly non-browsable. Probe the declared source as Sencho actually sees it; dangerous host roots, docker-socket mounts, and managed-area overlaps stay blocked, and the dangerous classification also reads the literal declared source so it holds across platforms. |
||
|
|
315e8b6379 |
feat: add node update alerts with changelog tab and skip-version handling (#1463)
* feat: add node update alerts with changelog tab and skip-version handling - Add node_update_available notification category with blue/brand bell dot - Route node_update_available notifications to Fleet -> Node updates sheet - Add Changelog tab to NodeUpdatesSheet with GitHub release notes - Add per-node skip-version persistence (node_update_skips table) - Skip hides update CTA on node card and sheet; re-surfaces on newer version - Skipped nodes excluded from Update all backend filter - Add pulsating dot indicator on Changelog tab when updates available - Always-visible View changelog action in notification row bottom - Admin-only for all mutating controls (skip, unskip, update) - Backend tests for skip-version semantics (15 tests) - Update fleet-view.mdx, remote-updates.mdx, and OpenAPI spec * fix: address audit findings - nested button, stale changelog, semver normalization, mobile intent - Move View changelog button outside routable button (sibling element) - Fix aria-label for node_update_available notification rows - Support ?recheck=true on release-notes endpoint - Invalidate release notes cache on forced recheck - Store normalized semver (semver.valid strips v prefix) - Skip fleetUpdatesIntent on mobile (desktop only) - Add v-prefix normalization test * fix: restore View changelog on same line as timestamp, opposite sides The button is always visible at the bottom right of the notification card, on the same row as the timestamp (just now), using justify-between layout. * fix: update tests for node_update_available category and release-notes fetch - Backend: monitor-service tests now expect node_update_available instead of system - Frontend: NodeUpdatesSheet tests mock release-notes API call to prevent undefined then() * fix: resolve ci lint failures |
||
|
|
0384c47d1e |
feat: add posture reasons and review queue to Security overview (#1462)
Add structured posture reasons derived alongside the posture verb in securityPosture.ts so the masthead and Overview tab can answer why the page is red, what to do first, and what clears it. Backend: - derivePostureReasons() returns blocker, review, and info reasons from the same SecurityPostureFacts used by deriveSecurityPosture() - deriveSecurityPosture() depends on derivePostureReasons() internally - Exposure split: public exposure with KEV, fixable, or EPSS >= 0.1 is a blocker; exposure without any of those is a review item - Fully dismissed exposed images produce no posture reason - postureReasons and primaryAction returned by the overview endpoint Frontend: - ReviewQueueCard on the Overview tab with per-row CTAs for blockers - Action summary in masthead subtitle and desktop primary CTA button - Card gated on posture not being Unknown - Backward compatible with older remote nodes |
||
|
|
e9c262ae6a |
feat: split Host Alerts into Host Alerts, Container Alerts, and Stacks guardrails (#1461)
* feat: split Host Alerts into Host Alerts, Container Alerts, and Stacks guardrails Move global_crash from Host Alerts to new Monitoring > Container Alerts section. Move health gate and env deploy guardrails from Host Alerts to Infrastructure > Stacks > Deploy Guardrails subsection. Host Alerts now contains only host threshold settings (CPU, RAM, disk, alert suppression, and the master host_alerts_enabled toggle). Stacks gains a Deploy Guardrails subsection (node-scoped, admin-gated) alongside the existing Workflow controls (browser-local). Dashboard Crash detection row now routes to Container Alerts. * docs: update crash detection toggle description to match new Container Alerts section |
||
|
|
7320a86579 |
feat: add cron scheduling mode for image update checks (#1460)
* feat: add cron scheduling mode for image update checks Adds a cron scheduling mode alongside the existing fixed-interval dropdown in Settings > Automation > Image update checks. Users can now set a 5-field cron expression (e.g. "0 3 * * 1") for precise time-of-day scheduling of registry polls. - Backend: ImageUpdateService gains mode/cronExpression fields and cron-based nextDelayMs() using the existing cron-parser dependency. PUT /api/image-updates/interval extended with transactional writes and server-authoritative cron validation matching the Scheduled Operations contract. Nicknames like @daily are supported. - Frontend: UpdatesSection gains a SegmentedControl toggle and cron text input with cronstrue-powered live description. The frontend does advisory validation only; backend 400s are surfaced inline. SettingsPrimaryButton used for explicit "Save schedule" action. - No cron jitter (the user chose a specific time). Interval mode keeps existing ±10% jitter. - Tests: 15 new backend tests covering valid cron, invalid cron, 6-field rejection, nickname support, backward compat, runtime fallback, and transactional writes. - Docs: auto-update-policies.mdx, alerts-notifications.mdx, and openapi.yaml updated with new scheduling mode. * fix: add mode and cronExpression to UpdatesSection test fixtures The existing tests failed because the mock status object was missing the new required fields (mode, cronExpression) added with cron scheduling support. Without them, status.mode was undefined, causing uiMode to never match 'interval' and the Select combobox to not render. * fix: prevent SegmentedControl from stretching full-width in SettingsField The flex-col container defaults items to align-self: stretch, making the Interval/Cron toggle bar span the full card width. Add self-start so it sizes to its content. |
||
|
|
3bf677af6c |
feat: add syntax highlighting to .env editor tab (#1459)
Apply Monaco built-in ini language mode to the .env editor tab, matching the syntax highlighting already used by the FileViewer, diff previews, and Git source diffs for env content. - EditorView.tsx: change env tab language from plaintext to ini - Add EditorView.test.tsx: 3 cases asserting ini/yaml/files tabs - Docs: update editor.mdx to reflect env tab has highlighting |
||
|
|
a698aaa926 |
feat: add per-stack project env file selection for Docker Compose (#1457)
* feat: add per-stack project env file selection for Docker Compose
Allow users to configure an ordered list of env files per stack that serve
as the project environment file(s) for Docker Compose ${VAR} interpolation.
The selected files are passed via repeated --env-file flags during all
compose commands.
Backend:
- Add stack_project_env_files table (node-scoped, ordered)
- Extend authoredComposeEnvFileArgs to emit --env-file for configured files
- Add GET/PUT /stacks/:name/project-env-files and /candidates endpoints
- Update resolveStackEnvSources to use configured files as interpolation source
- Update resolveAllEnvFilePaths to merge injection + interpolation sources
- Add discoverStackLocalEnvFiles for candidate discovery
- Extend backupStackFiles and snapshotStackFiles for project env files
- Add project-env-files capability to CapabilityRegistry
Frontend:
- Add project env file selector to EnvironmentPanel (capability-gated)
- Update EditorView banner to generic "project environment file" language
- Add project-env-files capability to capabilities.ts
Issue: #1454
* fix: add realpath validation, clear all stale backup files, reject nested paths
- authoredComposeEnvFileArgs: use fsPromises.realpath + isPathWithinBase
for symlink escape defense at use time
- backupStackFiles: clear ALL non-marker files from backup slot before
writing, not just PROTECTED_STACK_FILES (handles stale old.env)
- PUT project-env-files: reject paths containing / or \ (root-level
only, matching Compose auto-discovery behavior)
* fix: add getStackProjectEnvFiles to compose-service mock
The new authoredComposeEnvFileArgs calls getStackProjectEnvFiles
on the DatabaseService singleton. The compose-service mesh-override
tests mock that singleton without the new method, causing 6 failures.
Add getStackProjectEnvFiles: () => [] (empty = fall back to legacy
behavior, which is what these tests exercise).
* fix: add getStackProjectEnvFiles to remaining service mocks
The new authoredComposeEnvFileArgs calls getStackProjectEnvFiles,
which is missing from the mock in compose-images.test.ts (6 failures)
and image-update-service.test.ts (proactive fix).
* fix: apply inline path-injection barrier at fs sink for CodeQL
The PUT project-env-files route resolved paths via isPathWithinBase
before calling fsp.stat, but CodeQL does not credit a containment check
separated from the sink. Apply the canonical inline barrier pattern
(path.resolve + startsWith at the sink) used throughout the codebase.
* fix: resolve stackDir from the same canonical root as safePath
Prevents a containment bypass when the compose base directory is
a symlink: stackDir was previously joined from the unresolved
baseDir while the inline barrier used path.resolve(baseDir),
which could differ for symlinked paths. Now both stackDir and
safePath are resolved from a single canonical root, then each is
containment-checked against it.
* fix: remove unused isPathWithinBase import
The inline path-injection barrier refactor replaced isPathWithinBase
with an inline startsWith check at the fs sink, so the import is now
unused and fails ESLint no-unused-vars.
|
||
|
|
b7dd9dc1b0 |
feat: add ON/OFF toggle for host threshold alerts (#1456)
* feat: add ON/OFF toggle for host threshold alerts Add host_alerts_enabled setting (default ON) as a master switch for CPU, RAM, and disk host threshold evaluation. When OFF, the four threshold controls in Settings > Host Alerts are disabled and MonitorService skips the systeminformation calls and alert dispatch entirely, while clearing stale suppression state so re-enabling starts fresh. The dashboard Configuration Status card shows "Off" when host threshold alerts are disabled. Crash capture, health gate, deploy guardrails, stack alert rules, and the Docker janitor are all unaffected. * fix: exit NumberChip edit mode when externally disabled When the host threshold alerts master toggle is turned OFF while a NumberChip is in edit mode, force-exit edit mode so the chip renders the greyed-out button state consistently with the other chips. |
||
|
|
f1f64ec7f6 |
feat: show container name in structured log output (#1452)
* feat: show container name in structured log output Prepend a normalized container name prefix to each line in ComposeService.streamLogs() so both the structured log viewer and the raw terminal identify which container produced each entry. - Backend: prepend displayName (normalized via normalizeContainerName) before LogFormatter.process() in sendOutput and flushBuffer. - LogFormatter: refactor process() to handle both prefix-first and timestamp-first input orders via a while-loop; widen PREFIX_REGEX to accept dotted service names. - Frontend: add containerName to LogRow, extract prefix in parseLine, render as an inline mono chip in the message column, and include the name in downloaded logs (omitting the bracket prefix when null). - Tests: 14 new tests across log-formatter, compose-service streamLogs, and StructuredLogViewer chip rendering + download formatting. * fix: guard LogFormatter loop to at most one prefix and one timestamp The while-loop refactored for order-agnostic prefix/timestamp parsing could continue matching beyond the intended single prefix and timestamp. A log line like "redis | 2024-...Z api | started" would falsely colorize "api |" as a second container prefix in raw terminal output. Add prefixFound/timestampFound boolean guards so the loop stops after one prefix and one timestamp, regardless of input order. * feat: per-service color alternation for log container chips Add an Appearance setting that lets users switch between unified cyan and per-service label-token colors for the container name chips in the structured log viewer. - Extract HUE_VARS and hashLabel() from NodeLabelPill into a shared utility at frontend/src/lib/label-colors.ts. - Add useLogChipColorMode hook (browser-local localStorage, sencho.log-chip-color-mode key, unified by default). - Add SegmentedControl in Settings > Appearance > Display. - Apply inline label-token styles via style attribute in per-service mode; keep current text-brand/80 bg-brand/10 classes in unified mode. - 14 new tests across label-colors, hook, and viewer chip rendering. |
||
|
|
ba1be3cc4e |
fix: skip Docker Compose $$ escaped variables in Anatomy parser (#1450)
The Anatomy view's interpolation regex did not skip Compose's $$ escape
syntax, so $${VAR} (a literal, not a reference) was incorrectly flagged as
a missing variable. Add the (?<!\$) negative lookbehind that the backend
parser already uses, matching its behaviour.
|
||
|
|
7982251dc6 |
feat(scheduler): add helper text and risk badges to scheduled action picker (#1449)
* feat(scheduler): add helper text and risk badges to scheduled action picker Add a concise helper text and risk level badge to every scheduled action in the create/edit modal. The six risk levels (Safe, Read-only, Interruptive, Runtime change, Removes containers, Destructive) map to the four existing design-system tones and render as a small dot+label chip next to the helper text, following the same pattern as SeverityBadge. Fix an ambiguous mobile label: update + target_type: fleet now resolves through resolveTaskAction and renders 'update node stacks' instead of the misleading 'update fleet'. Add exact helper-text and risk-level assertions for all 10 actions, plus component tests for default modal state, action-switch scenarios, and mobile update+fleet rendering. * docs: update stale scheduled-operations alt text for changed helper text |
||
|
|
79f840ab6e |
fix: clear structured log viewer rows on stack switch (#1448)
The structured log viewer accumulated log rows across stack switches because the useEffect cleanup closed the old WebSocket but never cleared the committed rows state. Reset rows, row IDs, and auto-follow at the top of the effect before connecting to the new stack. The level filter is intentionally preserved across switches. Closes #1444 |
||
|
|
cc78873e65 |
feat(scheduler): group schedule action picker by operator intent (#1446)
* feat(scheduler): group schedule action picker by operator intent Reorganize the New Schedule action picker from a flat dropdown to a category-grouped list (Lifecycle, Updates, Security, Maintenance, Backups). - Extend Combobox component with optional group field on ComboboxOption, rendering grouped sections with non-interactive headers when groups are present. Flat rendering is unchanged for all other callers. - Reorder SCHEDULED_ACTIONS by category group and update seven action labels per the operator-intent spec. - Add DEFAULT_SCHEDULED_ACTION_ID constant so picker order and form defaults are independently controllable. - Wire grouped actionOptions into ScheduledOperationsView. - Update all label references in docs and tests. - Add Combobox grouping tests, registry order test, and default-constant test. * fix(scheduler): correct Combobox grouping for interleaved groups, docs labels - Replace last-group-append with Map-based group partitioning so interleaved or mixed-group options land in the correct group. - Add interleaved-groups test and restore non-interactivity test. - Update stale "Start Stack" references to "Start / Bring Up Stack" in doc action-label contexts. - Update action-picker alt text to describe the new grouped order. |
||
|
|
3a22f59057 |
feat(security): surface Compose internet-reachability exposure in posture (#1442)
* feat(security): surface Compose internet-reachability exposure in posture Builds a per-stack per-service exposure descriptor from the rendered effective Compose model, cached at deploy/update time, and joins it into the Security action posture. A service is publicly exposed when it publishes a port on a non-loopback host IP or uses host networking. The exposure cache lives in a new stack_exposure table, refreshed inside ComposeService.deployStack and updateStack (covering all funneled paths: manual, scheduler, mesh, templates, labels, App Store, Git, webhooks). Cleanup runs on stack delete, blueprint withdrawal, and node delete. The overview route intersects the exposed image set with the existing per-image suppression-aware Critical/High tally, so a clean public nginx does not escalate posture. The scan sheet shows a "Published service" or "Internal only" evidence badge per image. * fix(test): provide fresh auto-close proc for exposure spawn in stall tests Two deployStack idle-stall tests used mockSpawn.mockReturnValue(proc) which returned the same already-closed process for the new config spawn added by the exposure refresh. The renderConfig promise hung waiting for a close event that had already fired. The fix uses mockImplementation to return the controlled proc for the first spawn (up) and a fresh auto-closing proc for the second spawn (config via refreshExposureCache). * fix(security): tighten loopback detection, clarify exposure semantics, drop internal-only badge - Expand isLoopback to cover full 127.0.0.0/8 range (127.0.0.2 etc) - Clarify that exposure is configured (Compose model), not live topology - Remove "Internal only" badge: false is not proof of non-exposure when other stacks using the same image may lack a cached descriptor |
||
|
|
db8bb70b7d |
fix(scheduler): reject 6-field cron in Scheduled Operations (#1435)
* fix(scheduler): reject 6-field cron in Scheduled Operations Create and edit validation parsed cron with cron-parser, which accepts both 5- and 6-field expressions, while the form, presets, and docs all describe a 5-field cron. Because the scheduler ticks once per minute, a leading seconds field can never improve precision, so a 6-field expression was silently accepted but never honored on its stated schedule. Add a field-count guard on both sides: the API rejects 6-field input at create and edit with a clear message, and the form surfaces the same error inline and disables save. Cron nicknames such as @daily still pass. Document the five-field requirement in the cron reference. * chore: merge main into scheduled cron validation * fix: avoid logging policy bypass actor in debug output |
||
|
|
bc8c051962 |
feat(scheduler): consistent action targeting in Scheduled Operations (#1431)
Give every scheduled action an explicit, predictable target model (Action then Node then Stack then Options then Schedule): - System Prune now exposes a Node picker and requires a node, so it can no longer run silently on the default node. - Vulnerability Scan and System Prune list local nodes only; both run on the hub-local Docker daemon and reject remote nodes on the backend. - Restart Stack service discovery loads services from the selected node via fetchForNode instead of the active or local node. - Fleet Snapshot shows a read-only "Scope: Entire fleet" summary. Backend gains a shared local-node guard and prune node validation on create and update, plus an executor-level remote-node guard, so the frontend and backend validation now agree for every action. |
||
|
|
0af7ad1df2 |
refactor(scheduler): drive scheduled-action metadata from a shared registry (#1428)
Scheduled-operation action metadata was duplicated across the backend route validator, the DatabaseService action union, the desktop action picker, the Timeline lanes, and the mobile labels/tones. Adding or renaming one action meant editing all of them. Introduce one registry per package as the single source within that package: - backend/src/services/scheduledActionRegistry.ts owns the action list and target-type validation; routes/scheduledTasks.ts and DatabaseService import from it (BackendScheduledAction type, VALID_ACTIONS, validateActionTarget). - frontend/src/lib/scheduledActions.ts owns the UI metadata (labels, short labels, categories, tones, target/node/stack/service flags, helper text) and drives the create-flow picker, the All Tasks label, the Timeline lanes, and the mobile schedule view. Timeline lanes now group by semantic category (Lifecycle, Updates, Security, Maintenance, Backups) sourced from the registry. The update-fleet UI alias is made explicit via a backendAction field. Backend validation stays authoritative; parity tests on each side keep the action sets in lockstep. |
||
|
|
330f9f1acd |
fix(security): fix Security page table layouts and exploit-risk pagination (#1434)
Image scan sheet: make the finding table the single bounded scroll region (SystemSheet noScroll + flex-fill) so it no longer clips at the bottom on shorter viewports, and give the tables a phone min-width so they scroll horizontally instead of cutting off the right-hand columns. Scan history table: add the same horizontal scroll on phones. Overview "Top exploit-risk findings": render as a paginated table with column headers and top-right pagination, modeled on the dashboard stack-health table. Key the rows by rank position so recurring CVE/scan pairs no longer collide and duplicate rows when paging through. Overview severity-by-exploitability chart: label the axes (EPSS exploitability and CVSS severity) and stop the card from stretching to a taller neighbour, removing the dead space beneath the chart. |
||
|
|
6527bc971b |
feat(security): gate deploys on exploitation risk, not just severity (#1432)
Scan-policy deploy gates can now block on a known-exploited CVE (CISA KEV) and on a fixable Critical/High finding, in addition to an optional severity threshold. New policies default risk-first (KEV and fixable on, severity off); existing policies keep their severity-only behavior. CVSS stays captured for context but is never the sole basis for a block, and a finding whose exploitability cannot be confirmed is treated as risky rather than safe (incomplete scan detail fails closed on KEV/fixable inputs). The decision logic is shared between the pre-deploy gate and the informational post-scan banner via a pure helper, so the two never disagree. Block messages and the block dialog now name the conditions an image matched. Backend and frontend gates move together, the new inputs replicate across the fleet, and a blocking policy with no active input is rejected on both sides. |
||
|
|
bb4ddde35a |
feat(sidebar): surface partial status for multi-container stacks (#1426)
Bulk stack-status aggregation collapsed a stack to "running" as soon as any container was up, so a multi-container stack with crashed containers showed a green UP pill and the degradation was invisible from the sidebar. Add a crash-aware "partial" state: a stack is partial when at least one container is running and at least one has genuinely failed (exited with a non-zero code, dead, or crash-looping). Cleanly finished one-shot containers (exit 0) and clean restart-policy cycling do not count, so an app with a completed init job stays UP. The exit code is read from the container Status string, so no extra inspect calls are needed. Render partial as an amber PT pill with a hover tooltip showing the running/total count, fold it into the Down filter (needs-attention), and treat it as running for context-menu lifecycle actions so operators keep stop/restart/update. The dashboard stack-health table, cross-node search rows, and the command palette all pick up the new state through the shared status surfaces. |
||
|
|
2eafee3594 |
fix(networking): treat host-network services as host-exposed in summaries (#1430)
The exposure summaries derived a stack's exposure solely from the declared published-port list, so a service running with network_mode: host (which publishes every container port on the host but declares no ports:) was under-reported as less exposed than it actually is. Capture network_mode in the lightweight dependency parser, add an isHostNetwork predicate, and treat a host-network service as exposed and publishing across the Fleet networking summary, the Stack Dossier export, and the Networking panel, matching how the Compose Doctor already flags host networking. |
||
|
|
2ed01641c8 |
fix(preflight): suppress node-state checks when the Docker snapshot is unavailable (#1423)
When the Docker daemon is unreachable the node snapshot collection fails and returns empty sets. The preflight rules read those empty sets as "resource absent", so a stack referencing an external network or volume got false "not found" blockers while real host-port and container_name conflicts went undetected. Add a nodeStateAvailable flag to the preflight context, mirroring the existing sourceReadable gate. The six node-state rules now suppress themselves when the snapshot could not be collected, and a single info advisory reports that the node-state checks were skipped so a clean pass during an outage is not mistaken for full coverage. |
||
|
|
96b3c49359 |
fix(deploy): verify atomic-deploy backup integrity before restore (#1422)
* fix(deploy): verify atomic-deploy backup integrity before restore Atomic deploy and the Rollback action restore a stack from a backup of its compose file and .env. A backup truncated or corrupted at write time (out of disk, interrupted copy) was copied back silently, overwriting a working stack with bad content. The backup now writes a .checksums manifest holding a SHA-256 of each backed-up file, and a restore re-hashes every file and compares it before touching the stack. A mismatch aborts the restore with a clear error and leaves the live files unchanged. Backups without a manifest, and files with no recorded checksum, are restored unverified so a rollback is never blocked by missing integrity data. * fix(deploy): guard backup source reads with an inline path barrier The integrity change reads each managed file from the stack directory before hashing it. Static analysis flags those reads because the source path derives from the user-provided stack name and the containment check lived in a helper it does not trace. Re-establish containment inline at each read sink, resolving against the compose base and confirming the path stays within it, mirroring snapshotStackFiles. Behavior is unchanged for valid stacks; the guard only rejects a path that escapes the compose directory, which validation already prevents. * test(deploy): assert compose stays put when the backup .env is corrupt Strengthen the .env-corruption test so it also mutates the live compose.yaml and asserts it is left untouched, proving the integrity abort halts before any file is copied back rather than relying on the backup happening to match. Also note on the test hash oracle that it matches production for UTF-8 text fixtures. |
||
|
|
401980ffa3 |
feat(resources): render anonymous volume names readably in the volume browser (#1429)
Anonymous Docker volumes carry a raw 64-character hex name, which is correct data but unreadable in the volume browser crumb, sheet title, and table row. Detect 64-hex names and show a short prefix (e.g. 079dfda49f2c…) in the table cell and sheet title, with the full name available on hover and an "anonymous" chip so the truncation is self-explanatory. The volume browser sheet adds a band that keeps the full hash visible and copyable. Named volumes still display in full. The name is passed verbatim to the API; only the rendered label changes. |
||
|
|
2c70e11485 |
feat(scheduler): flag one-shot tasks in the Scheduled Operations table (#1433)
A one-shot task (delete after successful run) self-deletes only on a successful run; a failed one-shot is kept so it can be retried or debugged. The table gave no signal for this, so a lingering failed one-shot was indistinguishable from a stuck recurring task. Add a "One-shot" chip to the status cell for every delete-after-run task, with a tooltip that explains the keep-on-failure behavior when the task has failed. |
||
|
|
5143350771 |
chore(deps): bump the all-npm-frontend group (#1441)
Bumps the all-npm-frontend group in /frontend with 10 updates: | Package | From | To | | --- | --- | --- | | [@xyflow/react](https://github.com/xyflow/xyflow/tree/HEAD/packages/react) | `12.11.0` | `12.11.1` | | [cronstrue](https://github.com/bradymholt/cRonstrue) | `3.14.0` | `3.21.0` | | [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.20.0` | `1.21.0` | | [motion](https://github.com/motiondivision/motion) | `12.40.0` | `12.41.0` | | [recharts](https://github.com/recharts/recharts) | `3.8.1` | `3.9.0` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.9.3` | `26.0.0` | | [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.0.2` | `6.0.3` | | [globals](https://github.com/sindresorhus/globals) | `17.6.0` | `17.7.0` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.61.1` | `8.62.0` | | [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.0.16` | `8.1.0` | Updates `@xyflow/react` from 12.11.0 to 12.11.1 - [Release notes](https://github.com/xyflow/xyflow/releases) - [Changelog](https://github.com/xyflow/xyflow/blob/main/packages/react/CHANGELOG.md) - [Commits](https://github.com/xyflow/xyflow/commits/@xyflow/react@12.11.1/packages/react) Updates `cronstrue` from 3.14.0 to 3.21.0 - [Release notes](https://github.com/bradymholt/cRonstrue/releases) - [Changelog](https://github.com/bradymholt/cRonstrue/blob/main/CHANGELOG.md) - [Commits](https://github.com/bradymholt/cRonstrue/compare/v3.14.0...v3.21.0) Updates `lucide-react` from 1.20.0 to 1.21.0 - [Release notes](https://github.com/lucide-icons/lucide/releases) - [Commits](https://github.com/lucide-icons/lucide/commits/1.21.0/packages/lucide-react) Updates `motion` from 12.40.0 to 12.41.0 - [Changelog](https://github.com/motiondivision/motion/blob/main/CHANGELOG.md) - [Commits](https://github.com/motiondivision/motion/compare/v12.40.0...v12.41.0) Updates `recharts` from 3.8.1 to 3.9.0 - [Release notes](https://github.com/recharts/recharts/releases) - [Changelog](https://github.com/recharts/recharts/blob/main/CHANGELOG.md) - [Commits](https://github.com/recharts/recharts/compare/v3.8.1...v3.9.0) Updates `@types/node` from 25.9.3 to 26.0.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@vitejs/plugin-react` from 6.0.2 to 6.0.3 - [Release notes](https://github.com/vitejs/vite-plugin-react/releases) - [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.3/packages/plugin-react) Updates `globals` from 17.6.0 to 17.7.0 - [Release notes](https://github.com/sindresorhus/globals/releases) - [Commits](https://github.com/sindresorhus/globals/compare/v17.6.0...v17.7.0) Updates `typescript-eslint` from 8.61.1 to 8.62.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.62.0/packages/typescript-eslint) Updates `vite` from 8.0.16 to 8.1.0 - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/create-vite@8.1.0/packages/vite) --- updated-dependencies: - dependency-name: "@xyflow/react" dependency-version: 12.11.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-npm-frontend - dependency-name: cronstrue dependency-version: 3.21.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-npm-frontend - dependency-name: lucide-react dependency-version: 1.21.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-npm-frontend - dependency-name: motion dependency-version: 12.41.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-npm-frontend - dependency-name: recharts dependency-version: 3.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-npm-frontend - dependency-name: "@types/node" dependency-version: 26.0.0 dependency-type: direct:development update-type: version-update:semver-major dependency-group: all-npm-frontend - dependency-name: "@vitejs/plugin-react" dependency-version: 6.0.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-npm-frontend - dependency-name: globals dependency-version: 17.7.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-npm-frontend - dependency-name: typescript-eslint dependency-version: 8.62.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-npm-frontend - dependency-name: vite dependency-version: 8.1.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-npm-frontend ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
94406a8280 |
chore(deps): bump the all-actions group across 2 directories with 3 updates (#1440)
Bumps the all-actions group with 2 updates in the / directory: [actions/checkout](https://github.com/actions/checkout) and [softprops/action-gh-release](https://github.com/softprops/action-gh-release). Bumps the all-actions group with 1 update in the /.github/actions/start-app directory: [actions/cache](https://github.com/actions/cache). Updates `actions/checkout` from 6.0.3 to 7.0.0 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/df4cb1c069e1874edd31b4311f1884172cec0e10...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0) Updates `softprops/action-gh-release` from 3.0.0 to 3.0.1 - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/b4309332981a82ec1c5618f44dd2e27cc8bfbfda...718ea10b132b3b2eba29c1007bb80653f286566b) Updates `actions/cache` from 5.0.5 to 6.0.0 - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/27d5ce7f107fe9357f9df03efb73ab90386fccae...2c8a9bd7457de244a408f35966fab2fb45fda9c8) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-actions - dependency-name: softprops/action-gh-release dependency-version: 3.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-actions - dependency-name: actions/cache dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-actions ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
08e46d6bf3 |
chore(deps): bump the all-npm-backend group in /backend with 8 updates (#1439)
Bumps the all-npm-backend group in /backend with 8 updates: | Package | From | To | | --- | --- | --- | | [axios](https://github.com/axios/axios) | `1.18.0` | `1.18.1` | | [cron-parser](https://github.com/harrisiirak/cron-parser) | `5.5.0` | `5.6.0` | | [semver](https://github.com/npm/node-semver) | `7.8.4` | `7.8.5` | | [systeminformation](https://github.com/sebhildebrandt/systeminformation) | `5.31.7` | `5.31.10` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.9.3` | `26.0.0` | | [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.61.1` | `8.62.0` | | [@aws-sdk/client-ecr](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-ecr) | `3.1070.0` | `3.1075.0` | | [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1070.0` | `3.1075.0` | Updates `axios` from 1.18.0 to 1.18.1 - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.18.0...v1.18.1) Updates `cron-parser` from 5.5.0 to 5.6.0 - [Release notes](https://github.com/harrisiirak/cron-parser/releases) - [Commits](https://github.com/harrisiirak/cron-parser/compare/v5.5.0...v5.6.0) Updates `semver` from 7.8.4 to 7.8.5 - [Release notes](https://github.com/npm/node-semver/releases) - [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/node-semver/compare/v7.8.4...v7.8.5) Updates `systeminformation` from 5.31.7 to 5.31.10 - [Release notes](https://github.com/sebhildebrandt/systeminformation/releases) - [Changelog](https://github.com/sebhildebrandt/systeminformation/blob/master/CHANGELOG.md) - [Commits](https://github.com/sebhildebrandt/systeminformation/compare/v5.31.7...v5.31.10) Updates `@types/node` from 25.9.3 to 26.0.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `typescript-eslint` from 8.61.1 to 8.62.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.62.0/packages/typescript-eslint) Updates `@aws-sdk/client-ecr` from 3.1070.0 to 3.1075.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-ecr/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1075.0/clients/client-ecr) Updates `@aws-sdk/client-s3` from 3.1070.0 to 3.1075.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1075.0/clients/client-s3) --- updated-dependencies: - dependency-name: axios dependency-version: 1.18.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-npm-backend - dependency-name: cron-parser dependency-version: 5.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-npm-backend - dependency-name: semver dependency-version: 7.8.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-npm-backend - dependency-name: systeminformation dependency-version: 5.31.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-npm-backend - dependency-name: "@types/node" dependency-version: 26.0.0 dependency-type: direct:development update-type: version-update:semver-major dependency-group: all-npm-backend - dependency-name: typescript-eslint dependency-version: 8.62.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-npm-backend - dependency-name: "@aws-sdk/client-ecr" dependency-version: 3.1075.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-npm-backend - dependency-name: "@aws-sdk/client-s3" dependency-version: 3.1075.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-npm-backend ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
2539709d74 |
chore(deps-dev): bump the all-npm-root group with 3 updates (#1438)
Bumps the all-npm-root group with 3 updates: [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli), [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) and [@playwright/test](https://github.com/microsoft/playwright). Updates `@commitlint/cli` from 21.0.2 to 21.1.0 - [Release notes](https://github.com/conventional-changelog/commitlint/releases) - [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md) - [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.1.0/@commitlint/cli) Updates `@commitlint/config-conventional` from 21.0.2 to 21.1.0 - [Release notes](https://github.com/conventional-changelog/commitlint/releases) - [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md) - [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.1.0/@commitlint/config-conventional) Updates `@playwright/test` from 1.61.0 to 1.61.1 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.61.0...v1.61.1) --- updated-dependencies: - dependency-name: "@commitlint/cli" dependency-version: 21.1.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-npm-root - dependency-name: "@commitlint/config-conventional" dependency-version: 21.1.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-npm-root - dependency-name: "@playwright/test" dependency-version: 1.61.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-npm-root ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
fdd43003c6 |
chore(deps): bump node from 9c0e1e5 to a2dc166 (#1437)
Bumps node from `9c0e1e5` to `a2dc166`. --- updated-dependencies: - dependency-name: node dependency-version: 26-alpine dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
5fdef516be |
chore(deps): bump golang from f1ddd9f to 3ad5730 (#1436)
Bumps golang from `f1ddd9f` to `3ad5730`. --- updated-dependencies: - dependency-name: golang dependency-version: 1.26.4-alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
b1630788ba |
feat(security): prioritization-led Overview charts (posture + exploit intel) (#1427)
* feat(security): rework Overview charts around posture and exploit intel Replace the three severity-variation charts (severity donut, top exposed images, findings by type) with prioritization views, keeping the risk trend for context: - Action posture: bars of fixable / known-exploited / needs-review / accepted / not-affected with a known-exploited headline, derived from the existing overview facts (no new fetch). - Top exploit-risk findings: ranked actionable Critical/High by KEV, then EPSS, then CVSS, each row opening its scan. - Severity x exploitability: a CVSS-by-EPSS quadrant that separates scary-but-not-exploitable from act-first. The two intel panels are fed by a new bounded GET /security/overview/exploit-intel (latest-scan Critical/High, suppression-filtered, KEV/EPSS joined at read time) and degrade to clear empty states until exploit intel is fetched and images are rescanned. * fix(security): drop the redundant SECURITY kicker from the desktop masthead The desktop nav strip already names the page, so the masthead's "SECURITY" label above the posture word was redundant. Make PageMasthead's kicker optional (pages that pass one render unchanged) and omit it on the Security masthead. The mobile masthead keeps its kicker, since on the phone layout it is the page identity and there is no nav strip. * docs(security): describe the prioritization-led Overview charts Update the Security overview docs for the reworked chart set (risk trend, action posture, top exploit-risk, and the severity-by-exploitability quadrant) and note the exploit-risk charts populate once exploit intelligence is enabled. * fix(security): move the scanner-detections note into a masthead info icon Replace the standing "scanner detections show vulnerable components..." caption below the masthead (desktop and mobile) with an info affordance next to the scanned-images count in the masthead subtitle. Declutters the overview while keeping the disclaimer one hover away. * fix(security): apply "assume it's automatable" to exploit-risk ranking Absence of exploitability evidence must not be treated as low risk. Rank the top exploit-risk list by tier (known-exploited > known-high EPSS > unknown EPSS > known-low EPSS) so an unrated finding outranks one with evidence of low likelihood; label unrated findings "EPSS n/a"; and reword the quadrant footnote so excluded findings read as unrated rather than lower risk. |
||
|
|
f794702171 |
feat(security): action-posture Security dashboard with exploit intel and triage (#1424)
* feat(security): reframe masthead as action posture, not worst-CVE severity Derive the Security masthead from an action posture (Action needed / Monitoring / Secure / Unknown) instead of raw scanner severity, and label the raw Critical/High counts as scanner detections. "Secure" now means nothing is actionable right now, never a claim that no vulnerabilities exist; Unknown covers a missing scanner or a node with no completed scan. Phase-1 bootstrap: "actionable" is approximated from the overview facts that already exist (fixable findings, secrets, misconfigs); a later phase moves the bucketing to the backend. * feat(security): derive overview action posture from triaged facts Add deriveSecurityPosture as the single bucketing function and extend /security/overview with posture facts (fixableCriticalHigh, dangerousCompose, accepted, rawCritical/rawHigh, plus knownExploited/publiclyExposed placeholders that later phases populate) and the derived posture verb. Suppression- and acknowledgement-aware counts come from one bounded read-time pass over the latest-scan Critical/High findings, grouped per image so the existing read-time filters apply unchanged. The pass is capped and flags posturePartial, so a large node degrades gracefully instead of scanning every detail row. The masthead now prefers the backend posture and keeps the local bootstrap only as a fallback for older remote nodes reached through the proxy. * feat(security): capture Trivy finding enrichment (status, CVSS, vendor, purl, layer) parseTrivyOutput now keeps the per-finding fields Trivy already returns and we previously discarded: Status (fixed / will_not_fix / end_of_life / ...), CVSS (score + vector, preferring the NVD source then falling back), vendor severity, package URL, package path, and layer digest. Persisted on vulnerability_details via additive nullable columns (guarded ALTER), bound null when absent, and carried through the cached-scan reconstruction path. These fields separate scary from exploitable and feed the action posture and the per-finding evidence tags. Field paths verified against Trivy's documented image-scan JSON; covered by parse and insert/read round-trip tests. * feat(security): add CVE exploit-intel service (CISA KEV + FIRST EPSS) Add CveIntelService, a daily background cache of CISA KEV membership and FIRST EPSS scores stored in a new cve_intel table and joined to findings at read time by CVE id (never frozen onto scan rows, so a CVE entering KEV later lights up on scans already stored). EPSS is fetched only for CVE ids present in stored findings, batched; both feeds are best-effort and keep the last cache on failure, so the Security page degrades gracefully offline. Wired into startup/shutdown like the other background services. The overview now counts known-exploited Critical/High findings, and KEV membership escalates posture to Action needed even when no fix is available. A per-instance "Exploit intelligence" toggle on the scanner setup surface lets air-gapped or firewalled hosts disable the outbound fetch; the daily tick keeps running but skips the fetch body when it is off. * feat(security): show per-finding evidence tags (KEV, EPSS, vendor status, CVSS) The vulnerabilities endpoint joins read-time exploit intel (KEV membership and EPSS score) onto each finding by CVE id, and the scan sheet renders evidence tags beside each CVE: known-exploited, EPSS probability, vendor will-not-fix / end-of-life, and the CVSS score. Severity becomes one signal among several so an operator can tell scary from exploitable, with no invented composite score. * feat(security): evolve CVE suppressions into triage decisions Layer a triage status and optional OpenVEX justification onto CVE suppressions. Statuses: needs review / affected / not affected / accepted risk / fixed / false positive / ignored. Dismissing states (not affected, accepted, fixed, false positive, ignored) stop a finding from driving the action posture; needs review and affected stay actionable and are surfaced as counts. Existing rows default to "accepted" (the prior suppress behavior), so nothing changes for them. The overview now reports needsReview / notAffected / accepted as distinct facts derived from the triage status. The decision replicates across the fleet (snapshot + replicated-insert carry status + justification) so a replica's posture matches the control node. The inline suppress dialog gains a triage decision selector; the read-time filter surfaces the status and justification on every finding. * feat(security): export fleet triage decisions as OpenVEX (Admiral) Add an OpenVEX exporter that turns the instance's CVE triage decisions into a standard VEX document (not_affected / fixed / affected / under_investigation, with justifications), and a GET /security/vex/export endpoint to download it. Authoring fleet VEX is a governance capability, so it is gated to Admiral (paid) plus admin, mirroring the SARIF export gate; the Suppressions panel shows an Export VEX action only on Admiral. * docs(security): document action posture, evidence tags, exploit intel, and triage Update the Security page and CVE suppressions docs for the action-posture masthead (scanner detections vs product posture), per-finding evidence tags (KEV / EPSS / CVSS / vendor status), the exploit-intelligence toggle (CISA KEV + FIRST EPSS) on scanner setup, triage decisions layered on suppressions, and OpenVEX export of fleet triage decisions. * test(security): match intel hosts exactly in CveIntelService test Route the fetch stub and its call assertions by exact hostname (www.cisa.gov / api.first.org) instead of a domain substring check. Resolves the js/incomplete-url-substring-sanitization code-scanning alerts on the test's URL routing; behavior is unchanged. |
||
|
|
4c47c47a27 |
docs: caveat interpolated secrets in structural Compose fields (#1425)
The effective-model read surfaces (Networking, Dossier/Anatomy, Storage,
and Compose Doctor) avoid environment, label, and command values, but
docker compose config resolves any ${VAR} interpolation before the model
is parsed, leaving no provenance. A secret interpolated into a structural
field (a bind path, network name, published port, or extra_hosts entry)
is therefore returned resolved. That value is already readable at the
same stack:read scope through the stack's files, so this documents the
caveat rather than changing behavior.
- Reword the "secret-safe / never shows a secret value" claims on the
Networking, Dossier, Storage, and Doctor docs pages, and add a
canonical note to the Environment and Secrets Guardrails page steering
secrets to environment:/env_file: injection.
- Make the matching code comments honest in effectiveAnatomy,
composeNetworkInspector, effectiveModel (extra_hosts), and the
effective-anatomy route.
|
||
|
|
b753d2d5e0 |
fix(deploy): preserve compose.override.yml when Mesh is enabled (#1420)
When a single-file stack is opted into Sencho Mesh, the deploy builds an explicit `docker compose -f <base> -f <mesh override>` list. Passing any explicit -f disables Compose's automatic discovery of compose.override.yml (and the docker-compose.override variants), so a user's hand-authored override was silently dropped from the effective deploy once Mesh was on. Resolve the user's override file (first existing variant, with the same stack-name and symlink-containment guards as the base compose file) and insert it between the base and the mesh override, so it layers exactly as Compose's implicit discovery would, with the mesh override still taking precedence. A transient read failure during the lookup degrades to "no override" rather than failing the deploy; a stack-name or containment-guard rejection still aborts. Multi-file Git-source stacks and non-mesh deploys are unaffected. |
||
|
|
b2713b8a4d |
refactor(self-update): isolate helper run-args builder and lock argv path safety (#1419)
Extract the docker run argv construction for the self-update helper container from triggerUpdate() into a pure, exported buildSelfUpdateRunArgs, mirroring the existing buildSelfUpdateComposeCmd helper. The emitted argv is unchanged; the extraction makes the mount and flag ordering, the dedup rules, and the subpath-skip guard unit-testable. These operator-set paths (the compose working dir, the /app/data host path, and forwarded bind-mount sources) are passed as discrete argv elements to execFile, which spawns docker without a shell, so a path containing shell metacharacters stays inert data and cannot alter the recreate command. New tests lock the exact argv ordering and the dedup behavior against regression. |
||
|
|
7c5ba1cf68 |
fix(build): patch bundled Docker CLI golang.org/x/net to v0.55.0 (#1421)
The Docker CLI binary baked into the image (cli-builder stage, built from docker/cli v29.4.1's vendored tree) shipped golang.org/x/net v0.53.0, which the image scan flags for six HIGH advisories: CVE-2026-25680, -25681, -27136, -39821, -42502, and -42506 (x/net/html parsing denial of service and an x/net/idna Punycode issue), all fixed in v0.55.0. Drop docker/cli's committed vendor/ tree, bump golang.org/x/net to v0.55.0 with `go get`, and build the CLI in module mode (-mod=mod) so the patched module is resolved from the module proxy instead of the stale vendored v0.53.0. This stage now fetches modules at build time rather than building fully offline. `go mod tidy` is intentionally avoided because docker/cli ships a vendor.mod manifest rather than a standard go.mod, so a full tidy does not run cleanly against it. |
||
|
|
bf18fbbb9a |
test(audit-log): assert PAID_REQUIRED code on Community-blocked stats and export (#1417)
The /stats and /export routes run the paid-license guard before the permission guard, so a Community admin's rejection comes from the paid gate. The existing tests only checked the 403 status, which a permission gate would also produce, so they did not prove which gate fired. Assert res.body.code === 'PAID_REQUIRED' on both so each test pins the rejection to the paid gate, matching the assertion already used in the secrets suite. |