* 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.
* feat: add Custom OIDC provider and move SSO to Community tier
Add a generic Custom OIDC provider that works with any spec-compliant
OIDC identity provider (Keycloak, Authentik, Authelia, Zitadel, KanIDM,
Pocket ID, etc.) via standard discovery. Supports configurable claim
mapping for User ID, Username, and Email fields to handle non-standard
providers.
Move all SSO functionality (LDAP and OIDC) from the Admiral tier to the
Community tier so every user has access to identity provider integration.
Backend: add oidc_custom to AuthProvider type, extend SSOService with
claim mapping fields and env-var seeding, add oidc_custom to route
validation, remove requireAdmiral guards from SSO config endpoints.
Frontend: add Custom OIDC card with Display Name, Issuer URL, and claim
mapping fields to SSOSection; add KeyRound icon on login page; remove
AdmiralGate wrapper and lock icon from SSO settings nav.
Tests: update tier guard expectations, add oidc_custom authorize/config/
provisioning tests and claim mapping coverage. All 992 tests pass.
Docs: add Custom OIDC configuration reference, provider-specific setup
examples, troubleshooting section, and updated screenshots.
* fix: settings dialog close button overlap and combobox styling
Reposition the close button in Settings Hub above the scroll area so
it stays fixed when content scrolls. Increase dialog height to
accommodate the growing number of setting sections.
Fix combobox trigger styling to match Input component tokens
(border-glass-border, bg-input) and eliminate the gap between trigger
and dropdown list (top-full -mt-px). Apply the same fixes to
multi-select-combobox for consistency.
Add items-start to the Scopes/Default Role grid so the combobox
aligns with the adjacent input field. Add showClose prop to
DialogContent for consumers that need custom close button placement.
Update SSO doc screenshots at 1920x900.
* feat(convert): harden /api/convert endpoint with auth, validation, and tests
Applies authMiddleware to the docker run to compose endpoint, validates
that the payload is a non-empty string within an 8192 character budget,
rejects inputs containing null bytes, and wraps composerize in a
try/catch that surfaces a 422 with a clear message when the library
cannot produce a services block. Adds a Vitest suite covering the auth
gate, happy path, common flag coverage, boundary and null byte
placement variants, and malformed command handling.
* feat(editor): add From Docker Run tab to create stack dialog
Introduces a third tab in the Create New Stack dialog that accepts a
docker run command, calls the converter endpoint, and previews the
returned compose YAML before writing it to a new stack directory. Uses
the defensive toast pattern, clears the stale preview when the input
changes, and rolls back the empty stack directory if saving the
converted YAML fails so the user never ends up with an orphan stack.
* docs(stack-management): document docker run to compose converter
Adds a Convert from a docker run command section to the stack
management page covering how to use the new tab, the list of supported
flags, and troubleshooting for unparseable inputs. Screenshots show the
empty tab, a successful conversion with the compose preview, the
resulting stack in the editor, and the error toast surfaced when the
input cannot be converted. Appends a matching entry to the
troubleshooting page.
* feat(auth): add TOTP two-factor authentication with backup codes
Adds RFC 6238 time-based one-time password support to every tier,
integrated with the existing password and SSO login paths.
Backend:
- New MfaService wrapping otplib with a plus or minus 1 step tolerance,
base32 secret generation, and hashed single-use backup codes (bcrypt).
- user_mfa and mfa_used_tokens tables in DatabaseService. The second
table is a DB-backed replay blacklist, purged on a 60s interval.
- authMiddleware now recognizes an mfa_pending scope. A token carrying
that scope is rejected on every route except the MFA challenge and
logout, so no API surface is reachable before the second factor
clears.
- /api/auth/login issues only a short-lived mfa_pending cookie when the
user has MFA enrolled. /api/auth/login/mfa consumes that cookie,
verifies the code (or backup code), and swaps in a real session.
- /api/auth/mfa/* routes for status, enrol/start, enrol/confirm,
disable, backup-code regenerate, and SSO-bypass opt-in.
- Admin recovery path: POST /api/users/:id/mfa/reset clears the target's
MFA state, bumps token_version, and writes an audit log entry.
- CLI emergency fallback: backend/src/cli/resetMfa.ts is wired via
`npm run reset-mfa <username>` and also exported for tests.
- SSO flows (LDAP and OIDC) gate on user_mfa.sso_enforce_mfa before
issuing a session; default behaviour keeps the SSO path frictionless.
- Per-user lockout after 5 consecutive failed codes (15 min).
Frontend:
- AppStatus gains an mfa-challenge branch driven by /api/auth/status.
- New MfaChallenge screen, MfaEnrollDialog (QR plus manual secret plus
backup codes), MfaDisableDialog, MfaBackupCodesDialog.
- Account section shows a Two-factor authentication card with enrol,
regenerate, disable, and the SSO-enforce toggle (shown only when SSO
providers are configured).
- Users section gains a Reset 2FA action for admins.
Docs:
- New user guide at features/two-factor-authentication.mdx.
- New admin guide at operations/two-factor-admin.mdx.
- SSO page cross-links to the 2FA doc.
* fix(mfa): drop unused TEST_PASSWORD import and stale eslint disable
* fix(mfa): simplify e2e openAccountSettings helper to match working pattern
* fix(mfa): make e2e suite self-contained and always clean up
Test #2 called loginAs() before the MFA challenge step, which waited for
the dashboard indicator that never appears once the previous test enrolled
the user. That timeout skipped the rest of the serial block, including
the disable step, leaving MFA enabled and breaking every later spec.
Two fixes:
- Tests #2 and #3 now navigate directly to the login page instead of
piggybacking on loginAs, which only handles the password-only path.
- A new afterAll hook unconditionally disables MFA via the API using two
unused backup codes, so the DB is reset even if a test fails midway.
* fix(e2e): use backup code for mfa recovery to avoid totp replay race
The final recovery step in the backup-code replay test previously
generated a fresh TOTP to sign back in. When the timing landed inside
the same 30-second window that test #2 consumed, the server's replay
blacklist correctly rejected it, producing a ~50% flake rate. Backup
codes are single-use and sidestep the replay window, so the recovery
becomes deterministic.
* fix(e2e): drive mfa disable test through the challenge screen
Test #4 called loginAs after test #3 left MFA enabled, but loginAs
waits for the dashboard indicator and does not handle the challenge
screen, so it timed out. Drive the login manually, satisfy the
challenge with a backup code, and use a backup code for the disable
step too to avoid any TOTP replay-window race against earlier tests
in the serial block.
* refactor(git-sources): extract GitSourceFields from GitSourcePanel
Pure extraction of the repo/branch/path/auth/apply-mode form fields into a
reusable controlled component so the upcoming Create Stack from Git flow can
render the same form in the Create Stack dialog. No behavior change.
* feat(git-sources): create a stack from a Git repository
Add a From Git tab to the Create Stack dialog so users can name a new
stack, point it at a repo + branch + compose path, and have the compose
fetched, validated, written to disk, and linked in one shot. Optional
deploy-after-create runs the initial bring-up when requested.
Backend: new POST /api/stacks/from-git route gated by stack:create.
GitSourceService.createStackFromGit() fetches and validates before
touching disk, then creates the stack, writes the compose (and .env if
sync is enabled), and seeds the git source row with the fetched commit
so future pulls produce a clean diff. Runs under the per-stack lock so
a concurrent webhook cannot race the create. Deploy failure is
non-fatal and surfaced to the caller.
Frontend: the existing Create Stack dialog is now tabbed, with Empty
keeping the original single-field flow unchanged.
* test(git-sources): cover create-from-git endpoint and e2e flow
Service tests verify createStackFromGit seeds the last_applied columns
on success, writes the env file when sync is enabled, refuses an
invalid apply-matrix without fetching, rejects invalid compose
without leaving orphan state, and rolls back the on-disk stack dir
when a post-create step fails.
Route tests cover auth, missing stack_name, invalid stack name,
http:// rejection, oversized repo_url, and the 409 collision guard.
E2E adds a Create-stack-from-Git block covering tab visibility,
client-side HTTPS check, backend .git/config rejection, and a
happy-path fetch against a public demo repo (skipped on network
failure).
* docs(git-sources): document create-stack-from-git tab
Add a new section near the top describing the From Git tab in the
Create Stack dialog: what it does, the Deploy after create checkbox,
and the four failure modes (name collision, unreachable repo,
invalid compose, deploy-after-create failure).
* fix(git-sources): harden validation, RBAC, concurrency, and deploy recovery
Tightens the surface area around the Git source feature:
- Enforce HTTPS-only repo URLs server-side (regex was permissive).
- Add stack:read permission check on git-source reads and filter the
list endpoint by callable permission.
- Validate stack names before permission checks on mutation routes so
scoped lookups never see unvalidated input.
- Cap repo_url / branch / compose_path / env_path / token lengths and
require the stack directory to exist before upsert.
- Wrap pull() in the per-stack mutex to eliminate the pull/delete race
that could orphan pending data.
- Block .git/ path components in compose_path / env_path so a
misconfigured clone cannot leak repo metadata.
- Return {applied, deployed, deployError?} on deploy failure instead of
throwing, and surface deployError as a warning toast so the user can
retry deploy without re-pulling.
- Always clean the stack_git_sources row on stack delete even when the
file deletion step fails.
- Add shadow-card-bevel to the pending alert and metadata card per the
design system.
- Handle the new 403 response on the panel fetch gracefully.
- Add diagnostic logging gated on developer_mode (isDebugEnabled) across
fetch / pull / apply / webhook paths with credential scrubbing.
* test(git-sources): expand coverage for hardening and route validation
- New route-level suite covers HTTPS enforcement, required fields,
max-length caps on repo_url / branch / compose_path / env_path /
token, the stack-existence 404 guard, and GET authz.
- Service tests cover the .git metadata guard on compose and env
paths (including nested and substring-containing "git"), pull and
apply rejections when no source is configured or pending is
cleared, the sha-mismatch branch, and the deploy-failure return
shape that now carries deployError.
- E2E adds three server-side contract assertions: PUT against a
missing stack returns 404, http:// is rejected with 400, and
.git/config is rejected as compose_path.
* docs(git-sources): document deploy-failure recovery path
Adds a Troubleshooting entry explaining that when apply succeeds but
the subsequent deploy fails, the compose content is already on disk
and the user can retry deploy from the stack editor without
re-pulling.
* docs(git-sources): add configuration, diff, pending, and webhook screenshots
* fix(network-topology): harden with edge-case fixes, logging, and test coverage
Fixes stale data on node switch (topology now refreshes when active node changes),
adds manual refresh button to toolbar, and guards against containers with empty
Names arrays by falling back to a 12-char short ID. Adds operational and diagnostic
logging around topology fetches, and introduces 25 unit tests covering happy paths,
system network filtering, stack resolution, container deduplication, edge cases,
and error handling.
* docs: refresh screenshots
* fix(container-exec): harden with security fixes, validation, and test coverage
- Enforce admin role at WebSocket upgrade for container exec sessions
- Validate container is running before creating exec
- Fix bash-to-sh fallback (move .start() inside try/catch)
- Register container-exec as a capability for fleet visibility
- Add standard and diagnostic logging for exec lifecycle
- Fix false Admiral license claim in API docs
- Fix design system violations in BashExecModal (hardcoded colors)
- Remove duplicate legacy xterm dependencies
- Add 18-test suite covering auth, validation, fallback, and cleanup
* fix(container-exec): use correct Duplex type for exec stream
The stream variable was typed as NodeJS.ReadWriteStream, which lacks
.destroy(). Dockerode's Exec.start() returns stream.Duplex per its
type definitions. This caused tsc to fail while Vitest (which skips
full type checking) passed.
Rate-limit OIDC callback route and clear state cookie on all paths.
Validate GitHub API responses and add server-side config validation.
Sync SSO user roles on every login respecting seat limits.
Add LDAP connection timeout, bind DN warning, and multiple entry logging.
Replace Select with Combobox and apply card design tokens in SSOSection.
Expose OIDC scopes field in settings UI.
Fix hardcoded colors to use design system tokens.
Add standard and diagnostic logging throughout SSO flow.
Add tests for role sync, seat limits, LDAP escaping, and config validation.
Remove unused login-form.tsx template.
Update docs and screenshots for SSO feature.
* fix(audit): harden audit log with summary fixes, design compliance, and test coverage
Replace stale /compose/* route summaries with current /stacks/* patterns,
add ~30 missing route summaries for container ops, labels, fleet updates,
SSO, templates, and auto-update. Fix X-Forwarded-For extracting full
proxy chain instead of first client IP. Pre-sort pattern table at module
load for O(1) lookup instead of sorting per request.
Add Calendar and DatePicker UI components (react-day-picker v9 + date-fns)
replacing browser-native date inputs. Align AuditLogView with design system:
Combobox instead of Select, design token colors, strokeWidth 1.5, card bevel,
tabular-nums on all numeric cells.
Add diagnostic logging gated behind developer mode for audit middleware,
query, and export endpoints. Extract getAuditSummary to testable utility.
Add 32 tests covering summary resolution, DB round-trips, API permissions,
export formats, and middleware integration.
* fix(audit): use correct Chevron prop types for react-day-picker compatibility
Security fixes:
- Map deploy-only API tokens to deployer role (not admin)
- Add token versioning to invalidate sessions on password/role changes
- Reject deleted users immediately in auth middleware (no 24h JWT grace)
- Use DB role instead of JWT role so changes take effect instantly
- Block password setting on SSO-provisioned users
- Check proxy variant in scoped permission resolver
Cleanup and logging:
- Remove orphaned role assignments when stacks/nodes are deleted
- Add standard logging for login, user CRUD, role assignments, password changes
- Add diagnostic logging gated behind developer_mode setting
- Extract issueSessionCookie helper (DRY across 5 JWT signing sites)
Frontend:
- Replace Select with Combobox in UsersSection (design system compliance)
- Add strokeWidth={1.5} to Lucide icons
- Hide password fields for SSO-provisioned users
Testing:
- Add 42-test RBAC suite covering user CRUD, token versioning, scoped
assignments, permissions endpoint, password management, seat limits,
last-admin protection, and orphan cleanup
Extract duplicated snapshot capture functions from index.ts and
SchedulerService.ts into a shared module (snapshot-capture.ts). Fix
audit log route patterns that used singular 'snapshot' instead of
plural 'snapshots'. Apply design system to FleetSnapshots component
(card styling, strokeWidth, font-mono, tabular-nums, ScrollArea).
Add safePage pagination, loading toast for creation, and fix the
restore dialog race condition with a controlled AlertDialog. Add
diagnostic logging gated behind Developer Mode. Add tests for
restore endpoint, admin role enforcement, and edge cases. Update
docs with troubleshooting section and refresh screenshots.
* fix(scheduler): harden auto-update policies with cascade deletes, error reporting, and UI fixes
- Fix orphaned task runs on policy/node deletion with transaction-wrapped cascade deletes
- Make manual trigger non-blocking (202 Accepted) to prevent proxy timeouts
- Distinguish registry check failures from clean "no update" results via structured ImageCheckResult
- Trim whitespace-only policy names in both frontend and backend validation
- Add strokeWidth={1.5} to action icons per design system
- Add sr-only DialogDescription for Radix accessibility
- Replace Select with Combobox for frequency picker
- Wrap run history sheet content in ScrollArea
- Support concurrent Run Now indicators via Set-based state
- Abort stale stack fetches on node switch with AbortController
- Add standard and diagnostic logging to SchedulerService and ImageUpdateService
- Add tests for cascade deletes, image checking, and scheduler edge cases
- Add troubleshooting section to auto-update docs
* fix(tests): resolve lint errors in image-update-service tests
Remove unused mock variables (mockGetImage, mockGetDocker) and unused
ImageCheckResult type import. Replace CommonJS require('yaml') with
ESM import to satisfy no-require-imports rule.
* chore(deps): bump Docker CLI to 29.4.0 and Compose to v5.1.2
Resolves Trivy CVE-2026-32282 (Go stdlib symlink follow in Root.Chmod)
by upgrading to releases that ship Go 1.25.9. Compose v5.1.2 also bumps
grpc to 1.80.0, resolving CVE-2026-33186.
* chore(security): accept CVE-2026-32282 in .trivyignore, update stale refs
Go stdlib symlink-following in Root.Chmod (CVE-2026-32282) affects both
Docker CLI 29.4.0 (Go 1.26.1) and Compose v5.1.2 (Go 1.25.8). Fix
requires Go 1.25.9 or 1.26.2; no upstream static binary ships a patched
runtime yet. The vulnerable code path requires a chroot context with
attacker-controlled filesystem, which does not apply to our usage.
Also updates version references from v5.1.1/v29.3.1 to v5.1.2/v29.4.0
for existing CVE entries, and notes that Compose v5.1.2 resolved
CVE-2026-33186 (grpc bumped to 1.80.0) for the compose binary.
- Extract log parsing utilities (normalizeContainerName, parseLogTimestamp,
detectLogLevel, stripControlChars, demuxDockerLog) to shared module,
eliminating duplication between polling and SSE endpoints
- Fix timestamp regex to accept timezone offsets (+HH:MM/-HH:MM), not just Z
- Replace any[] with typed GlobalLogEntry interface
- Add SSE heartbeat (30s) to prevent reverse proxy timeouts
- Add X-Accel-Buffering: no header for nginx SSE compatibility
- Replace swallowed catch blocks with console.warn diagnostics
- Fix SSE not reconnecting on node switch (missing dep in effect array)
- Add settings change event so devMode/pollRate updates apply without remount
- Add log level filter (ALL/ERROR/WARN/INFO) to toolbar
- Replace overflow-auto div with ScrollArea for design system compliance
- Add strokeWidth={1.5} to all toolbar icons
- Include stack name in download format
- Surface fetch errors with inline banner
- Add 39 unit tests for all log parsing utilities
- Update docs with level filter documentation and refreshed screenshot
Add authMiddleware to all 13 fleet endpoints that were previously
accessible without authentication. Add NaN validation for parseInt
params, stackName validation on snapshot restore, and description
length cap on snapshot creation. Clean up updateTracker entries on
node deletion to prevent memory leaks.
Replace hardcoded colors with design system tokens, swap Select for
Combobox, replace overflow-y-auto with ScrollArea, fix card styling
(shadow-card-bevel, border tokens). Fix stale container data by
always refetching on stack expand with a loading guard against
concurrent requests.
Add operational logging for state-changing fleet operations and
diagnostic logging gated behind Developer Mode. Add 20 fleet tests
covering auth enforcement, input validation, tier gating, and
snapshot CRUD lifecycle.