Conversation
Review-driven corrections to the v1.5.2 docs/api batch (040d490), found by two adversarially-verified review passes. Targets v1.6 (no standalone v1.5.2). - 🐛 fix(api): define NotificationOutboxEntry/NotificationOutboxResult schemas — both were referenced by $ref but never declared, making the assembled OpenAPI document invalid (Swagger UI/Redoc/codegen failed on the unresolved refs) - ✅ test(api): add a recursive guard that fails on ANY dangling #/components/schemas/* $ref, so this class of bug can't regress - 🐛 fix(api): emit the canonical /api/v1 base in every pagination _links (container list, watch, security vulnerabilities, update-operations, audit) to match the migrated spec - 🐛 fix(api): correct registryWebhookSignature headers in the spec — drop x-gitlab-token (never checked), add x-registry-signature + x-harbor-signature (both accepted by the handler) - 🐛 fix(api): revert LogoutResponse.logoutUrl to type:string (the handler omits the field, it never emits null) - 🐛 fix(api): correct the remote-trigger 409 description (rollback-container only), document the outbox GET 500, and the scan-all 400 scanner-not-configured case - ✅ test(api): add spec-structure tests for /api/v1/log/components, /auth/status, /api/v1/app auth posture, RegistryWebhookResponse, and the full /api/v1/webhooks/registry responses - 📝 docs(api): correct the registry webhook header list in api/index.mdx; drop legacy dd.trigger.include from the edited eligibility row; document the now-reachable GET /api/v1/containers/backups in CHANGELOG
- 🐛 fix(ui): cancelUpdateOperation now POSTs /api/v1/operations/:id/cancel (was the deprecated /api/ alias slated for removal in v1.6.0, which would have 404'd) - 🐛 fix(ui): ConfigView webhook panel and copyable curl example show /api/v1/webhook/* to match the published docs
…anches - 📝 docs(readme): point the docs-refresh note and roadmap row at v1.6 (the standalone v1.5.2 lane was dropped — folded into v1.6) - 📝 docs(contributing): add the qlty-smells advisory row and renumber the pre-push pipeline table to match lefthook.yml - ✅ test(web): cover the docs-link-rewriter bare /docs/assets and single-quoted-attribute branches
- suppressHydrationWarning on the 7 homepage .reveal wrappers (the pre-hydration reveal bootstrap mutates them, causing a hydration mismatch) - og:image + twitter:image on /compare, /compare/[slug], and the Trivy security advisory (page-level openGraph was replacing the layout's and dropping the share image) - dockge compare: i18n row reflects shipped 17-locale support (was stale 'Planned'/competitor; both ship i18n now → tie)
- discord-icon.tsx: official Discord mark, mirrors github-icon API - site-config: discordUrl invite - footer: Discord social link between GitHub and Docs - README: Community section pointing to Discord + GitHub Issues
First commit of #242 table unification. Adds an isMobile prop (default false, non-regressive): the first non-icon column pins sticky-left during horizontal scroll, interactive rows are floored at a 44px touch target, and the 8px resize handle is hidden on touch. 3769 ui tests green at 100% coverage. Refs: #242
Removes the DataCardGrid/DataListAccordion render branches and the view-mode switcher binding from the 5 simple data views; DataTable now renders unconditionally. Also drops AuditView's ?view= URL-query mode mechanism (now a no-op). useViewMode/DataCardGrid/DataListAccordion source files are deleted in a later commit. 3763 ui tests green at 100% coverage, typecheck clean. Refs: #242
Removes the card/list render branches and view-mode switcher from the 4 remaining non-container views. AgentsView drops its non-table EmptyState (DataTable empty slot covers it); SecurityView keeps its breakpoint-driven isCompact column swap; TriggersView's inline test button moves into the DataTable actions column. 3763 ui tests green at 100% coverage, typecheck clean. Refs: #242
Removes the ~427-line DataCardGrid/DataListAccordion render block and the containerViewMode switcher from ContainersGroupedViews/ContainersView/ContainersListContent; the column picker is now always shown, and the template-context type + its typecheck guard drop containerViewMode. Dead helpers (getContainerViewMemo, getGroupByKey) removed. 3747 ui tests green at 100% coverage, tsc clean. Refs: #242
Removes DataCardGrid.vue and DataListAccordion.vue (now-unused card/list surfaces) plus useViewMode.ts and auditViewMode.ts, each with its spec. Cleans up the main.ts global registrations, the preferences index export, the ComponentImports/main registration assertions, the data-view stubs, and rewrites the lifecycle spec off useViewMode. 3674 ui tests green at 100% coverage, tsc clean. Refs: #242
The table/cards/list switcher is dead now that every view is table-only. Removes the modelValue/viewModes props, the update:modelValue emit, defaultViewModes, viewModeName/viewModeLabel, and the switcher button group; keeps the count display, filter toggle, badge, slots, and filter panel. Test stubs simplified to match. 3669 ui tests green at 100% coverage, tsc clean. Refs: #242
Every view is table-only now, so viewMode/mode are dead. Bumps CURRENT_SCHEMA_VERSION 7->8, removes containers.viewMode and views.*.mode from the schema and DEFAULTS (security/agents keep their sort fields; the mode-only views collapse to empty stubs), and strips the dead migration branches (SIMPLE_VIEW_MODE_KEYS, migrateSimpleViewModePreferences, migrateAuditViewPreference, the mode logic in migrateSortableViewPreference/migrateContainersPreference/sanitizeContainers). Legacy dd-*-view-v1 keys stay in LEGACY_KEYS so they're still cleaned up, just not migrated. mergeDefaults drops any persisted mode field, so no explicit v7->v8 transform is needed. 3657 ui tests green at 100% coverage, tsc clean. Refs: #242
After schema v8 dropped every view-mode field, the ViewMode type, VIEW_MODES set, and isViewMode guard have zero consumers. Removes them from schema.ts/validators.ts and the preferences index re-export, with their validators.spec/index.spec tests. 3651 ui tests green at 100% coverage, tsc clean. Refs: #242
The v1.5.1 brand refresh (#439) moved the website to the cropped whale "headshot" icon but left the app UI on the old full-body whale. Ship the same icon set in ui/ so the product matches getdrydock.com. - Replace ui/public favicon.ico, favicon-96x96.png, apple-touch-icon.png and both web-app-manifest PNGs with the website's headshot assets - Remove the stale favicon.svg (browsers preferred it over the PNGs, so it kept rendering the old mark) and its <link>; the website ships no SVG - Add a ?v=2 cache-buster to the icon links so existing installs re-fetch instead of serving the aggressively cached old favicon - Match the website manifest's "any maskable" PWA icon purpose Ref: #439
…ast (#466) The SPA history-mode fallback returned index.html for every unmatched path, including a request for a content-hashed /assets/*.js bundle that a prior build referenced but the upgrade deleted. Browsers won't execute a module script served as text/html, so a page cached from the old build (often by a reverse proxy that ignores the HTML's Cache-Control: no-store) white-screened, and caching proxies could poison the asset URL with HTML. - 🐛 fix(api): return a clean 404 (no-store) for /assets/* misses in the UI catch-all instead of the SPA shell (app/api/ui.ts) - ✅ test(api): cover the 404 branch + the index.html fallback branch (app/api/ui.test.ts — 100% coverage on ui.ts) - 📝 docs(server): add a "Reverse proxy caching (blank page after an update)" section with browser/proxy cache guidance + per-proxy notes Doesn't self-heal an already-blank page (clearing the browser/proxy cache is still the fix for that), but stops drydock turning a missing bundle into a silent white screen. Ref: #466
C8-fix for #242 responsive DataTable — corrects the a11y/touch defects shipped in 4b11bd8: - logical inset props (start-0/end-0) so the sticky identity + actions columns render correctly in RTL (ar) - z-stack fix: header sticky cell z-20 out-stacks body sticky cell z-10 - 48px min touch row height (Material 3), overscroll-x-contain (Safari back-nav), scope=col on all header cells - new per-theme --dd-sticky-separator token (>=3:1, WCAG 1.4.11) replacing the sub-threshold grey box-shadow; avoids reusing the 1.58:1 --dd-border-strong Follow-up (not in this commit): AppIconButton size=toolbar renders 32px — clears WCAG 2.5.8 min (24px) but under the 44px target.
…ainer width - ✨ feat(ui): card mode renders each row as a stacked card (title from the first non-icon column, priority-or-order subtitle, dt/dd body) reusing the existing cell/actions/full-row slots — no per-view changes needed - ✨ feat(ui): native sort select + direction toggle replace the clickable table header in card mode, emitting the same update:sortKey/update:sortAsc - 🌐 i18n(ui): add sharedComponents.dataTable.sortBy + sort direction labels - ✅ test(ui): 41 new card-mode specs (breakpoint boundaries, slot reuse, sort control edge cases, virtual-scroll spacers, 44px touch targets, logical-properties-only RTL guard) Card mode triggers on DataTable's own container width (ResizeObserver), deliberately separate from the 768px window-based isMobile prop. Refs: #242
- ✨ feat(ui): first @media print rules in the app — cards keep break-inside: avoid so a card never splits across pages, the card-mode sort chrome (select + direction toggle) is hidden on paper, and the table's horizontal scroll container becomes overflow: visible so off-screen columns print instead of being clipped - ✅ test(ui): source-assertion specs for the print block (house pattern from the column-sizing standard) + DOM assertions for the two new hook classes (dd-data-table-scroll, dd-data-table-card-sort-bar) Refs: #242
… composition - ✨ feat(ui): new DataTableColumn fields — cardTitle forces a column to be the card title (table mode unaffected); cardPriority drives subtitle promotion (highest positive wins, ties by declared order) and body demotion (negative = out of the card; data stays reachable via the DetailPanel). Deliberately distinct from the auto-hide priority field, whose semantics run the opposite direction (higher = dropped first) — Containers pipes those values through, so reusing them would have promoted registry/uptime as the subtitle - 🐛 fix(ui): NotificationsView card title was the bare enabled ToggleSwitch (first non-icon column) — name is now flagged cardTitle, triggers is the subtitle - 🐛 fix(ui): AuditView card title was the raw timestamp — containerName is now the title, action the subtitle - ✨ feat(ui): density annotations — Containers subtitle = update kind with server/registry/uptime demoted; Watchers demotes cron; Agents subtitle = status with docker/os demoted; Servers subtitle = status; Security wide branch subtitle = critical - 🐛 fix(ui): Dashboard recent-updates widget mobile columns had empty labels, rendering blank dt labels in card mode — now reuse the desktop i18n keys - ✅ test(ui): 27 new specs across DataTable, useColumnVisibility, and eight view suites, incl. regression proof that the auto-hide priority field no longer influences card composition Refs: #242
- ✨ feat(ui): new useFocusTrap composable — saves the previously focused element on activation, focuses the container (tabindex=-1), cycles Tab/Shift+Tab within focusable descendants, and restores focus on deactivation or scope disposal when the saved element is still connected. Built on a post-flush watchEffect because watch with immediate+post still runs its first call synchronously at setup, before template refs bind - ✨ feat(ui): DetailPanel activates the trap only when open on mobile — the desktop panel is a non-modal sticky sidebar that stays open across row clicks, so trapping or moving focus there would break keyboard row-browsing (matches the existing conditional aria-modal) - 🐛 fix(ui): mobile panel offset used physical right-0 — now logical end-0 so the overlay anchors correctly in RTL locales - ✅ test(ui): 15 composable specs (activation, restore, disconnected restore target, wrap both directions, no-focusables, disposal) + 6 DetailPanel specs incl. desktop must-not-steal-focus guards Refs: #242
…only User column pickers hide columns from the table, but card mode (<640px container) deliberately ignores the hidden set — mobile has no picker to un-hide a column, so every non-demoted column must stay reachable there. - ✨ feat(ui): hiddenColumnKeys prop filters the table pipeline at normalizedColumns, so flex width redistribution excludes hidden columns and the template loops need no changes; full-width/spacer colspans follow the filtered count - ✨ feat(ui): card computeds now read an unfiltered normalized set (cardResolvedColumns) with a card-side first-non-icon fallback, so the card title stays stable even when its column is table-hidden - ✅ test(ui): 10 specs — th/td/colgroup omission, width redistribution, sticky-column reflow when the first non-icon column is hidden, colspan shrink, card-mode precedence (hidden column reappears in cards), and composition with cardPriority demotion (each mechanism in its own mode) Refs: #242
…ble, picker)
- ✨ feat(ui): defaulted views.{security,watchers,servers,audit,agents}
.hiddenColumns preference fields — persisted as the HIDDEN set (not the
visible set, unlike Containers) so columns added in future releases stay
visible for existing users; no schema version bump needed, mergeDefaults
backfills; sanitizeViews clamps garbage, unknown keys, and required keys
- ✨ feat(ui): useViewColumnVisibility composable — per-call state seeded
from preferences with deep-watch write-back, required-column toggle
no-op, hiddenCount scoped to the current column set, reset
- ✨ feat(ui): shared DataTableColumnPicker component modeled on the
Containers picker (config icon trigger + hidden-count badge, teleported
flip-aware popover, Escape/outside-click close), hidden below 640px —
no picker on mobile since card mode ignores hiding; required rows never
emit toggle
- 🌐 i18n(ui): sharedComponents.columnPicker keys (tooltip, heading,
reset, badge tooltip)
- ✅ test(ui): 41 specs across sanitize clamps, composable persistence,
and picker interaction; preferences .ts files stay gate-enforced at 100%
Refs: #242
…and Audit - ✨ feat(ui): each view marks its identity column required, derives pickerColumns from tableColumns, persists hiding via useViewColumnVisibility, and renders DataTableColumnPicker in the filter bar's extra-buttons slot - ♻️ refactor(ui): SecurityView's compact (<1024px window) branch no longer swaps to a two-column set — tableColumns always returns all 7 and the severity columns are force-hidden via the hiddenColumnKeys union instead, so card mode finally sees the full set (critical subtitle annotation engages on mobile); the picker hides in compact so users can't toggle columns the override would re-hide - 🐛 fix(ui): SecurityView spec mocked useBreakpoints with plain objects instead of refs, so template auto-unwrap never applied and every isCompact v-if was constantly truthy in tests — dormant infra bug, now real refs - ✅ test(ui): sync guards pinning each view's column keys to VIEW_TABLE_COLUMN_KEYS, picker render/toggle/persistence per view, Security compact-union and picker-absence cases Refs: #242
… path - ♻️ refactor(ui): delete the bespoke picker (in-memory visible-set ref, non-teleported popover, hand-rolled positioning) in favor of DataTableColumnPicker + useViewColumnVisibility - ✨ feat(ui): picker choices now persist to preferences.views.agents.hiddenColumns — the bespoke picker had no persistence and reset on every reload - ♻️ refactor(ui): the compact (<1024px window) collapse to required columns now flows through the hiddenColumnKeys union instead of pre-filtering, so card mode sees the full 7-column set; the in-name compact badge row is unchanged - 🗑️ remove(ui): dead agentsView.list.toggleColumns/columnPickerHeading keys across all 17 locales (key-parity gate forbids orphans) - ✅ test(ui): sync guard, required flag, persistence, compact union/picker-absence, badge-row cases; useBreakpoints mock switched to real refs (same dormant auto-unwrap bug class as SecurityView.spec) Refs: #242
Containers pre-filtered its columns before DataTable saw them, so picker-hidden and width-auto-hidden columns vanished from mobile cards — where no picker exists to bring them back. - 🐛 fix(ui): tableColumns now maps the full column catalog and hiding flows through DataTable's hiddenColumnKeys prop (picker-hidden ∪ width-auto-hidden), which card mode ignores by design; the uptime ticker stays gated on picker-visibility so opt-out users pay no cost - ♻️ refactor(ui): ContainersListContent adopts the shared DataTableColumnPicker; bespoke popover, showColumnPicker plumbing, and global click/scroll handlers deleted; new resetColumns restores the shipped default set (opt-in uptime stays hidden) - 🗑️ remove(ui): activeColumns (zero consumers) and the +N auto-hidden badge (picker-hidden count is the actionable signal); dead containerComponents.listContent picker keys purged from all 17 locales - ✅ test(ui): hiddenColumnKeys cases (picker-only, auto-only, union, opt-in column), reset, wiring assertions across five containers specs Refs: #242
- 🗑️ remove(ui): dataFilterBar.viewMode/viewModeLabel/viewModes.* (the switcher died in a76780f) and dataCardGrid/dataListAccordion keys (components died in d3329a0) from sharedComponents - 🗑️ remove(ui): dead per-view card/list label sets (audit, security, registries, servers, triggers, watchers, agents) — every removal backed by a zero-consumer grep incl. template-literal t() sweeps - 🌐 i18n(ui): securityView.card.total kept (live in SecurityDetailPanel); all 17 locales updated in lockstep, -884 lines Refs: #242
Closes the round-7 review finding on #550 plus two adjacent test-hygiene issues found by an adversarial pass over the same surface, so the whole batch lands in one review round. ## Changes **Version cache reset (CodeRabbit finding on #550, `app/api/portwing-ws.test.ts:324`)** - `injectDrydockVersionForTesting` now accepts `undefined` to clear the module-level `_drydockVersion` cache (the `!== undefined` guard in `drydockVersion()` makes it naturally re-prime from `getVersion()`). - The welcome-version test resets the cache before `vi.mocked(getVersion).mockClear()`, so it stays order-independent: an earlier inject or welcome frame can no longer leave the cache primed and let the test pass without exercising the canonical `getVersion()` path. **Same bug class one describe block down** - The injector suite ended by "restoring" the cache to a literal `'1.5.0'` — leaving every later test in the file primed with a stale magic value, the exact order-dependency this PR fixes. It now resets to `undefined` like the new idiom. **Duplicate test scenario in the orchestration suite** - `keeps an errored broken reference on the fast path and preserves its digest` (repair-branch digest stickiness) and `repairs a broken reference for an errored container on the fast path` (fast-path eligibility) ran byte-identical arrange/act; the newer test asserts a strict superset. Folded into one test whose name now carries both intents. Coverage verified 100% across all metrics at directory scope after the merge. **Comment accuracy in `container-processing.ts`** - The catch-block comment implied release-notes enrichment can reject after a fresh result; `enrichContainerWithReleaseNotes` swallows its own errors and never rejects, so the guard is defense-in-depth. The comment now says so instead of overstating the live path. ## Verification - `npx vitest run api/portwing-ws.test.ts watchers/providers/docker/container-processing.test.ts watchers/providers/docker/docker-image-details-orchestration.test.ts` — 158/158 pass. - Directory-scope coverage: `docker-image-details-orchestration.ts` and `container-processing.ts` both 100% branches/lines/functions/statements. - Full pre-push gate (biome, qlty, coverage, build) green on push. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Changelog - ✨ Added support for clearing the cached Drydock version with `undefined`. - 🔧 Changed API tests to reset the version cache, preventing order-dependent behavior. - 🔧 Clarified the defensive result-restore comment in Docker container processing. - 🗑️ Removed a duplicate errored fast-path repair test and consolidated digest assertions. ## Concerns - Verify the targeted test suite and pre-push checks remain green. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…rors (#559) ## Summary - CI flake: `docker.test.ts` asserts the exact message `Scanner worker timed out after 10ms`, but `waitForTimeout(timeoutMs)` bakes whatever value it's armed with into the `DockerScannerTimeoutError` message. The `run()` call site at the container-start phase arms the timer with `remainingTimeoutMs` (configured `timeoutMs` minus elapsed setup time), so if even ~1ms elapses before that timer arms, the message reads e.g. `"9ms"` instead of the configured `"10ms"`. Failed twice on `main` run 29657499692. - Root-cause fix: `waitForTimeout` now takes an optional `reportedTimeoutMs` (defaults to `timeoutMs`) used only in the error message, while `setTimeout` still arms with the real remaining budget. The call site that passes `remainingTimeoutMs` now also passes `runOptions.timeoutMs` as the reported value, so the displayed timeout always matches what was configured — decoupled from how much budget was actually left when the timer armed. - Added a deterministic regression test (`reports the configured timeout even when the deadline is armed mid-flight`) that mocks `Date.now` to force a non-zero elapsed-setup remainder before the container-start phase arms its timer, then asserts the rejection message still reads `Scanner worker timed out after 10ms`. This is test-hygiene for the rc.3 line — no runtime behavior change beyond the error message text (the timer still fires after the same remaining budget as before). ## Test plan - [x] `npx vitest run security/backends/docker.test.ts` — 63/63 passed - [x] `npx vitest run --coverage security/backends/` — `docker.ts` at 100% lines/branches/functions/statements in `coverage/coverage-summary.json` - [x] `npx biome check security/backends/docker.ts security/backends/docker.test.ts` — clean - [x] Full local pre-push gate (biome, qlty, qlty-smells, workflow-tests, typecheck, coverage, build) — all green <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Changelog - 🐛 Fixed scanner timeout errors to report the configured timeout instead of the reduced remaining budget. - 🔧 Updated `waitForTimeout` to separate timer duration from reported timeout. - ✨ Added a deterministic regression test covering elapsed setup time. - ✅ Docker tests, coverage, formatting, and pre-push checks pass. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…ession cookie, CSP, CI secret scans (#567) ## What Nine security-hardening findings from the 2026-07-20 internal security review, as 12 atomic commits plus CHANGELOG entries: - **SSRF** (`app/triggers/providers/http`, Hass webhook URL handling, registry/icon fetches): guarded DNS lookup blocks cloud-metadata/link-local resolution on every request (override: `allowmetadata=true`), and redirects can't hop into blocked address space (DNS-rebinding + cross-host redirect protection). - **WebSocket origin validation** (`app/api/ws-upgrade-utils.ts`): full scheme/host/port origin comparison; `X-Forwarded-Proto`/`X-Forwarded-Host` honored only with trust proxy enabled; socket transport used otherwise. - **Anonymous auth fails closed on upgrades** (`app/authentications/providers/anonymous`): the warn-and-serve grandfather path is gone — no auth configured (or anonymous unconfirmed) now refuses to start, same as fresh installs. `DD_ANONYMOUS_AUTH_CONFIRM=true` is the explicit opt-in. **Behavior change for upgraders running open dashboards — called out in the CHANGELOG.** - **Store permissions**: `/store` created `0700` (Dockerfile), `dd.json` `0600`, `umask 077` in the entrypoint and store process so Loki autosave temp/replacement files never widen. - **Application-scoped session cookie**: `drydock.sid` replaces the Express default `connect.sid`. **One-time sign-out on upgrade.** - **Icon CDN pinning** (`app/api/icons/providers.ts`): dashboard-icons/selfh.st/simple-icons pinned to exact revisions/versions instead of floating refs. - **Website CSP** (`apps/web`): static `'unsafe-inline'` header CSP replaced by a request-scoped nonce CSP built in `src/proxy.ts` per Next.js guidance; `experimental.sri` stays off (the existing guard test still enforces that) and the vercel.json CSP header is removed in favor of the nonce policy. New `content-security-policy.test.mjs` covers the builder. - **CI secret scanning**: checksum-pinned Gitleaks in `ci-verify.yml` with an exact-baseline `.gitleaksignore` (427 historical entries) and `scripts/scan-secrets.sh` for local parity. - **Dependency-test repairs**: the demo/scripts lockfile security tests now accept patched upgrade ranges instead of pinning to vulnerable-era versions. ## Why Internal security review follow-up (report kept local by policy). Targets the v1.6.1/rc.3 train via `dev/v1.6` — nothing here touches the rc.2 soak or the GA promotion candidate. ## Tests Full pre-push gate green on this exact branch (cut from `origin/dev/v1.6`, cherry-picked 1:1 from the review line): app and ui coverage gates at 100% on all four metrics, builds, biome, qlty, workflow tests. New coverage: forwarded-protocol WS branches, fail-closed upgrade auth, SSRF lookup guards, store-permission enforcement, CSP builder. ## Notes for review - The anonymous-auth fail-closed change and the session-cookie rename are the two user-visible behavior changes; both have explicit CHANGELOG entries with migration guidance. - `vercel.json` loses its static CSP intentionally — the nonce policy from `proxy.ts` supersedes it (a static header can't carry a per-request nonce). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Changelog - 🔒 Security: Hardened SSRF protection with DNS re-resolution, metadata/link-local address blocking, pinned DNS results, and disabled redirects (`maxRedirects: 0`). - 🔒 Security: Strengthened WebSocket `Origin` validation by checking both effective protocol + host with TLS/trust-proxy awareness. -⚠️ Breaking: Anonymous authentication now requires explicit `DD_ANONYMOUS_AUTH_CONFIRM=true` and fails closed during upgrade without confirmation. -⚠️ Breaking: Renamed the session cookie to `drydock.sid`, updating CSRF and OpenAPI security scheme configuration and related tests. - 🔒 Security: Enforced restrictive filesystem permissions for the store (directory/file modes), Docker `/store` permissions (`0700`), and Docker entrypoint-created files via `umask 077`; store permission repair now avoids the TOCTOU window by handling `ENOENT` only. - 🔒 Security: Replaced moving icon CDN references with pinned versions/immutable upstream revisions for all affected providers. - 🔒 Security: Implemented request-scoped nonce CSP handling for the website (proxy-injected CSP + per-request nonce), removed global static CSP header behavior, and ensured nonce-safe JSON-LD rendering via a shared `JsonLd` component. - ✨ Added: CI “secrets” security job using checksum-pinned Gitleaks with full-history + tracked-tree scanning, repository-local script-driven invocation, and a historical baseline ignore configuration. - 🔧 Changed: Updated dependency-security tests to accept patched semver upgrade ranges; expanded SSRF/WebSocket test fixtures for axios/proxy/trust-proxy correctness; tightened lockfile vulnerability tests to scan nested package paths. - 🐛 Fixed: Updated and repaired security/format/workflow/integration tests to match hardened behavior (store perms, cookie rename, pinned icons, CSP/nonce propagation, SSRF DNS guards, and CI secrets scanning assertions). ## Concerns - Large, line-specific `.gitleaksignore` baseline may be brittle as repo files evolve—ensure it stays aligned with the pinned scanning commands and modes. - Confirm all runtime clients/integrations tolerate the `drydock.sid` cookie rename (including any external reverse proxies or browser automation). - Validate CSP nonce propagation in all paths (dev vs prod, caching/prefetch, and proxy matcher exclusions) and ensure no leftover static CSP header conflicts. - Review DNS guard correctness across IPv4/IPv6, `all: true` lookups, and connection-time resolution failures; ensure metadata/link-local detection is complete for every resolved address. - Ensure store permission enforcement + repairs behave correctly under all failure modes (only `ENOENT` swallowed; all other errors rethrown) and that `umask` is correctly captured/restored. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…te identity changes (#568) ## What Fixes the #565 report: a manual per-container "check for updates" restarted the maturity soak countdown, which reads to the user as their maturity settings being wiped (the config itself was never touched — only the clock). Root cause: `getUpdateLifecycleTimestamp`'s restart branch gated on `Container#resultChanged`, which compares `tag`, `suggestedTag`, `digest`, AND `created`. The last two are display metadata that can drift between scans (a manual recheck bypasses the registry poll cache), so a cosmetic wobble re-stamped `updateDetectedAt` to now and the countdown snapped back to the full window. Registries with trusted publish dates (Docker Hub/GHCR) mask this via the publishedAt floor; everyone else is fully exposed. Fix: new `hasCandidateIdentityChanged()` comparing only `tag` + `digest`; the lifecycle restart branch uses it directly. `resultChanged`/`hasResultChanged` are untouched — the trigger-notification `changed` flag in `container-processing.ts` must stay sensitive to `suggestedTag`/`created` changes for notification content. Intentional behavior is preserved: a genuinely new candidate (tag or digest change) still restarts the soak. ## Why Diagnosed against the live module (drove `updateContainer` through the exact recheck sequence): identical results leave the clock alone; a `suggestedTag`-only or `created`-only difference reset it every time. Pre-existing since the v1.5.x maturity gate (restart branch from v1.5.1-rc.1, comparison set from v1.5.0) — not an rc.2 regression. Targets the v1.6.1/rc.3 train. ## Tests Unit coverage for `hasCandidateIdentityChanged` (undefined handling both directions, each field diff, metadata-only diff → false) plus three store-level regression tests on `updateContainer`: metadata-only recheck preserves `updateDetectedAt`; tag change resets; digest change resets. Full pre-push gate green; `app/model/container.ts` and `app/store/container.ts` at 100% on all four metrics. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Changelog - 🐛 Fixed maturity soak countdowns restarting when only display metadata changes. - ✨ Added `hasCandidateIdentityChanged()` to compare update candidate identity using only `tag` + `digest`. - 🔧 Updated lifecycle timestamp logic to restart only on candidate identity (`tag`/`digest`) changes (leaving existing `resultChanged` behavior for trigger notifications intact). - ✨ Added unit + store regression tests covering metadata-only changes, tag/digest changes, and undefined handling. - 🔒 Security: Updated `brace-expansion` minimum safe version (5.0.7) and related e2e override validation. - 🔧 Dependency: Bumped pinned `js-yaml` override (4.2.0 → 4.3.0) and `brace-expansion` override (5.0.6 → 5.0.7). - Ensure maturity restart logic uses the correct “raw update” `result` shape consistently across persistence/rechecks. - Verify `hasCandidateIdentityChanged()` undefined/missing-field semantics align with actual runtime inputs. - Confirm trigger notification paths still rely on existing `resultChanged` behavior as intended. - Run/verify unit + store tests and e2e security suite with the updated dependency overrides. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…delta (#569) ## What Docs/copy freshness pass for the v1.6.1 delta (the #567 security batch, the #566 display batch, and the #568 maturity-clock fix). 30 findings from an adversarially-verified audit workflow (8 surface auditors, every finding independently re-verified against the shipped code before it counted; 1 rejected in verification), applied as one coherent edit per doc region: - **Anonymous-auth fail-closed (A1)**: the authentications page (callout, behavior matrix, and the "Upgrading from v1.3.x" section), the security guide, the README banner + quickstart line, and DEPRECATIONS.md all still promised upgraders a warn-and-serve grace period. Rewritten everywhere: upgrades now fail closed exactly like fresh installs; `DD_ANONYMOUS_AUTH_CONFIRM=true` is the opt-in. - **Session cookie rename (A4)**: `connect.sid` → `drydock.sid` one-time sign-out documented in DEPRECATIONS.md, the upgrading section, and the README banner. - **SSRF hardening (A2)**: the HTTP trigger page now documents the guarded DNS lookup, redirect refusal, and the `ALLOWMETADATA` escape hatch. - **WebSocket origin checks (A3)**: server config, FAQ, quickstart reverse-proxy warning, `api/log.mdx`, and `api/container.mdx` now state that origin validation covers scheme/host/port and that forwarded headers are honored only with `DD_SERVER_TRUSTPROXY`. - **UI vocabulary (B3/B4/B5)**: watchers page and FAQ updated for the pin-glyph/"Current" pinned treatment; eligibility + API docs document the new additive `clockSource`/`clockStartAt` blocker fields; the UI page covers the column-picker auto-hidden annotation. Wording grounded in the actual #566 locale strings, not paraphrased. - **Website quickstarts actually start**: the shared `DockerRunSnippet` (all 7 comparison pages) and the FAQ quickstart now include the auth opt-in env var — without it the one-liner produces a container that refuses to start on the v1.6 line. Also rides along: `protobufjs` pinned to 7.6.5 (CVE-2026-59877 / GHSA-j3f2-48v5-ccww, third fresh advisory today) in app (new override) and e2e (existing override bumped) — it began failing the qlty osv-scanner gate mid-push. Deliberately skipped: `content/docs/current/updates/index.mdx` (strictly per-release curated notes; rc.3 highlights belong to the actual cut) and `UPGRADE-NOTES.md` (version-gated to 1.4.6–1.5.x by `scripts/append-upgrade-notes.mjs`; v1.6 content there would leak into 1.5.x release notes). ## Why The security batch merged with zero docs changes, leaving the auth docs actively contradicting shipped behavior. Docs on `dev/v1.6` publish at the GA dev→main merge, so these corrections go live exactly when the behavior does. ## Tests Full pre-push gate green. apps/web script tests 43/43; MDX parse validated via sync-docs + fumadocs-mdx; biome clean. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Changelog - 🔒 Documented fail-closed anonymous authentication for both fresh installs and upgrades; require `DD_ANONYMOUS_AUTH_CONFIRM=true` (and clarified `/health` vs API response behavior). -⚠️ Security documented “no deprecation window” enforcement: removed upgrade grandfather path when anonymous auth isn’t configured/confirmed; upgrades stop serving/fail closed. -⚠️ 🔒 Documented `connect.sid` → `drydock.sid` session cookie rename and one-time sign-out on upgrade. - 🔒 Documented SSRF protections for guarded DNS lookup, redirect refusal (3xx never followed), and `ALLOWMETADATA` escape hatch. - 🔒 Documented WebSocket `Origin` validation (scheme/host/port) and clarified reverse-proxy requirements via `DD_SERVER_TRUSTPROXY` + forwarded headers (`X-Forwarded-Proto`/`X-Forwarded-Host`), including log-stream connection failures when misconfigured. - ✨ Documented maturity-clock details for `maturity-not-reached` (clock source + ISO-8601 start timestamp payload) and UI countdown rendering behavior. - ✨ Updated UI copy/annotations for column visibility and “hidden to fit” behavior. - ✨ Updated watcher UI description for fully-pinned tags to reflect `updateInsight` chip styling + persistent pin glyph/tooltips. - 🔧 Added `DD_ANONYMOUS_AUTH_CONFIRM=true` to website quickstarts and updated Docker run snippet wording. - 🔧 Pinned `protobufjs` to `7.6.5` in both app and e2e dependencies. - 🔧 Updated doc identity/release tests to assert current fail-closed semantics for `content/docs/current` while preserving legacy v1.5 behavior. ## Concerns - Confirm `DD_SERVER_TRUSTPROXY` docs explicitly require forwarding `X-Forwarded-Proto` and `X-Forwarded-Host` for scheme/host matching. - Validate that the documented fail-closed behavior matches the actual endpoints: API 401 behavior vs `/health` 503 reporting. - Ensure the “redirects never followed” documentation aligns with the actual redirect-following disablement and how `ALLOWMETADATA` is evaluated. - Verify cookie rename handling is documented consistently across all relevant upgrade/install docs (including one-time sign-out semantics). - Check that WebSocket origin validation requirements cover both dashboard log viewer and container log streaming endpoints (upgrade vs steady-state behavior). <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…ph, one-clock maturity panel (#566) ## What The display-honesty batch: renames and removes the update-status vocabulary that #498 and #556 reported as unreadable, based on a peer-tool UX research pass (WUD, Diun, Watchtower, Cup, Dockge, Arcane, Portainer, Komodo, Home Assistant, Renovate/Dependabot) and an owner-approved design. - **"Digest" → "Digest update"** in the Update column (Cup's web-UI string, Arcane's popover title). A digest-only change now reads as what it is: an update. - **NEW/MATURE badges removed.** The freshness fact moves into the update-kind badge's tooltip ("Detected 3 days ago"). Idle state gets no chrome; `UpdateMaturityBadge.vue` deleted. - **"Pinned" leaves the Update column.** It was an update status pretending to be a tag property (only rendered when a newer family version existed — the exact inconsistency reported in #498). Pinned-ness is now a persistent pin glyph on the Tag cell (existing `pin` icon-map entry, not the padlock — that's a live action affordance), with a tooltip carrying the `dd.tag.family=loose` remedy. Up-to-date pinned rows read "Current" like everything else. - **Maturity panel keeps exactly one clock.** The policy sentence names the clock the gate actually measures against ("Candidate published {date} — {n} more days until the {minDays}-day minimum", detection-date fallback), and the duplicated countdown collapses to "{countdown} · unlocks {date}". - **Backend rider:** `resolveMaturityClock()` in `app/model/maturity-policy.ts` (behavior-preserving refactor of `getMaturityStartMs`) now exposes `clockSource`/`clockStartAt` as additive optional fields on the maturity blocker's `details`, and both UI call sites (`container-mapper.ts`, `useContainerPolicy.ts`) consume the backend verdict instead of re-deriving maturity-blocked from `updateDetectedAt` alone — three drifting formulas become one. - **Column picker honesty:** breakpoint-auto-hidden columns stay checked but get a muted "{column} — hidden to fit" annotation, and the "+1" toolbar badge tooltip names the hidden columns instead of a bare count. - **Unknown-kind badge:** the silent unknown-update-kind fallback now renders a neutral "Unknown" badge. - **Bouncer "Blocked" → "Security hold"** (display string only; the maturity/hard-block wording is untouched). i18n: net 12 keys added / 7 removed / 12 values changed, English catalogs only per convention, plus stale values for the *repurposed* keys dropped from all 16 non-English locales so they fall back to English until Crowdin retranslates (otherwise German would render the old badge text "NEU" as a tooltip). ## Why Direct follow-up to the rc.2 field feedback in #498 (Cesc1986) and the #556 display cluster. Design researched and approved before implementation; targets v1.6.1/rc.3 — nothing here touches the rc.2 soak. ## Tests TDD gates green in full: app 100/100/100/100 (12,244 tests), ui 100/100/100/100 (4,236 tests). New coverage for the clock resolution (source precedence + tie-breaks), blocker serialization, pin glyph, unknown-kind fallback, column-picker annotation branches, collapsed countdown, and both `useContainerPolicy` paths (backend-verdict preferred, legacy fallback). ## Notes for review - `dashboard-container-metrics.ts` kept its `freshUpdates` KPI via a local `isFreshUpdate()` helper (same 7-day threshold) after the badge helper's deletion — dashboard behavior unchanged. - The owner does a rendered visual pass in the QA environment before this merges. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Changelog - ✨ Added backend-resolved maturity clocks with source/start metadata and defensive eligibility parsing. - ✨ Added shared update-kind labeling with localized `Unknown` fallback. - ✨ Added pin-gate policy detection and `tagPinGated` propagation from backend to UI. - ✨ Added auto-hidden column annotations and accurate picker badge/tooltips. - 🔧 Changed update terminology: “Digest update” and “Security hold”. - 🔧 Consolidated maturity countdowns into update-kind/status tooltips and insight notes. - 🔧 Moved pinned status to persistent pin glyphs in version/tag cells. - 🔧 Preserved dashboard fresh-update KPI behavior with an independent seven-day threshold. - 🔧 Improved tooltip fallback synchronization and registry search by dotted instance ID. - 🔧 Updated pluralized legacy-configuration banner translations. - 🗑️ Removed `UpdateMaturityBadge`, `getUpdateMaturity`, separate NEW/MATURE badges, and the dedicated pinned update-status state. - 🗑️ Removed stale localized `blockedByBouncer`, `updateAge`, maturity-badge, and pinned-summary values. -⚠️ Updated the `Container` contract: removed `updateMaturity`; added `updateMaturityTooltip`, `updatePolicyState`, and `tagPinGated`. ## Concerns - Verify backend `updateEligibility` payloads expose sufficiently reliable maturity clock metadata. - Confirm Crowdin retranslation coverage for removed and repurposed locale keys. - Audit downstream consumers for the removed `getUpdateMaturity`, `updateMaturity`, and `pinned` APIs. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Release plumbing for the v1.6.0-rc.3 cut — the full identity bump the guard tests enforce. - Dated `[1.6.0-rc.3] — 2026-07-21` CHANGELOG heading, fresh empty `[Unreleased]` section, matching link-reference definitions - rc.3 across README badge/highlights summary, website site-config + timeline, updates page (new rc.3 highlights section), API doc example payloads, quickstart tag matrix, and the demo runtime fixtures - Guard tests updated in lockstep: release-identity, release-docs-identity (compare base now `v1.6.0-rc.2...v1.6.0-rc.3`), changelog-links rc.3 delta over rc.2: #566 display-honesty batch, #567 security hardening, #568 maturity-clock restart fix (#565), #569 docs freshness. Verified: 105/105 script tests, 43/43 web-script tests, full pre-push gate green. Last dev-side change before the release head freeze. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Changelog 🔧 **Changed** - Updated release identity from `v1.6.0-rc.2` to `v1.6.0-rc.3` across README, website metadata, roadmap, docs, API examples, quickstart tags, and demo fixtures. - Added the dated `v1.6.0-rc.3` updates section and refreshed changelog link references. - Updated release identity and documentation guard tests, including rc comparison ranges and candidate fixtures. ✨ **Added** - Documented the rc.3 changes covering issues `#566`, `#567`, `#568/`#565, and `#569`. 🔒 **Security** - Documented the rc.3 security hardening batch. ## Verification - Script tests: 105/105 passed - Web-script tests: 43/43 passed - Full pre-push gate: passed ## Concerns - Verify the release date `July 21, 2026` is correct. - Confirm all remaining `1.6.0-rc.2` references are intentional. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedToo many files! This PR contains 212 files, which is 112 over the limit of 100. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (6)
📒 Files selected for processing (214)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Merge origin/main (#560 /store persistence quickstart, #561 rc.2-audit copy freshness + SRI guard hardening, #562 README translations) into the rc.3 release head. Identity conflicts resolved to the rc.3 side with main's copy/badge deltas re-applied (150K pulls, 20 providers, Node 24.7+, Biome 2.5, unified-clock roadmap row). Guard tests 105/105 + 44/44.
Closes every repository-actionable finding from the comprehensive v1.6 review, incorporates the superseded E2E correction from #573, and includes all follow-up review fixes and evidence hardening.
## Summary - replace two secret-like bearer-token fixtures with low-entropy test values - preserve the strict cache-expiry boundary assertions - restore the tracked-working-tree Gitleaks gate after earlier tests shifted the allowlisted source lines ## Verification - `node ./node_modules/vitest/vitest.mjs run registries/BaseRegistry.test.ts --maxWorkers=1 --fileParallelism=false` (221 passed) - `./scripts/scan-secrets.sh` (history and tracked tree clean) - full pre-push gate (100% app/UI coverage, app/UI builds, scripts, workflows, typecheck, Zizmor)
|
Release-readiness update for exact head
The PR is mergeable and now blocked only on a requested maintainer approval. After approval, preserve this exact tree for the |
biggest-littlest
left a comment
There was a problem hiding this comment.
LGTM, release checks green.
Promotes the v1.6.0-rc.3 content from
dev/v1.6tomainfor the release cut.rc.3 delta over rc.2:
tagPinGated), insight-only pinned rows read Current everywhere (table + detail panel), one-clock maturity panel, column-picker auto-hide annotations, registries deep-link fix (I believe that the maturity days are not applied as they should be #556), banner pluralization, tooltip freshness.drydock.sidsession cookie, owner-only store perms, pinned icon CDN revisions, CI secret scans.After merge: dispatch
release-cut.ymlfrommainwithrelease_tag=v1.6.0-rc.3.