An authenticated user of any role could inject arguments into git
subprocesses running as the backend user (root under systemd):
- repo_url was passed unvalidated to git ls-remote/clone, enabling the
ext:: transport (host command execution on git < 2.52 defaults) and
file:// local-repository disclosure (all git versions).
- app_path on POST /deploy/clone was passed without a '--' terminator,
enabling --upload-pack=<cmd> option injection independent of git
version and protocol.*.allow policy.
- branch flowed into 'git fetch origin <branch>' as an unterminated
refspec.
Fix (new app/utils/git_security.py applied at every affected sink):
- repo_url scheme allowlist (https/http/git/ssh + scp-like SSH);
rejects ext::/file://, local paths, leading '-', whitespace.
- '-c protocol.ext.allow=never -c protocol.file.allow=never' pinned on
every remote-touching git call, plus a restrictive GIT_ALLOW_PROTOCOL
in the subprocess environment.
- '--' positional terminator before URL/path/refspec arguments —
co-load-bearing with protocol pinning, which alone does not stop
option injection such as --upload-pack=<cmd>.
- branch/ref validation (leading '-', ref-format metacharacters) for
clone --branch, fetch refspecs and stored deploy configs.
- POST /deploy/clone now confines app_path to the managed apps root,
mirroring how /apps/from-repository derives it server-side.
Adds 31 regression tests; bumps version to 1.7.86.
Reported-by: CaptBoykin
Co-authored-by: Tyler Boykin <25166954+CaptBoykin@users.noreply.github.com>
Domains and DeliveryLog filtered empties get Clear filters; the extension
catalog filtered empty gets Clear filters and the installed empty gets a
Browse catalog button.
The fleet table gains native selection (DataTable selectable) and a floating
bulk bar with a Set group… picker (including Ungrouped) that applies via
updateServer per row.
The Events tab becomes the CRM record timeline: deployments (expandable,
as before) interleaved newest-first with environment-variable changes and —
for admins — audit-log entries targeting the app, via the new target_id
feed filter. Compact non-expandable rows for config/audit entries.
AuditService.get_logs gains a target_id filter (paired with target_type) and
/admin/activity/feed passes both through — the per-resource activity
timeline can now ask 'what happened to THIS app'.
Service/Server/Monitor/Workspace/Project detail pages record visits for the
palette's Recently used section and host a FavoriteStar in their title areas
that pins the entity into the palette's Favorites group.
Removing an IP list entry or deleting a cron job now toasts with an Undo
action that re-POSTs the captured row (comment/description preserved). The
useConfirm gate stays; cron's ?focus=create:cron deep-link rides along.
Each QuickCreate destination opens its create surface via useFocusParam:
?focus=create:server|monitor|domain|workspace|project (cron lands with the
jobs commit).
The CRM '+' lives in the sidebar footer and mobile top bar next to the
notification bell: one menu for New Service/Server/Monitor/Domain/Cron/
Workspace/Project. Route-based flows navigate directly; modal-based flows
use ?focus=create:<kind> deep links. FavoriteStar pins entities from detail
pages.
utils/recents.js tracks detail-page visits (10 max) and pinned favorites in
localStorage; useRecordVisit is the one-line detail-page hook. The palette
gains a Favorites group (weighted high, searchable) and folds visited
entities into Recently used.
Services groups by project or status, Workspaces and Servers by status; the
hand-rolled __select columns are gone in favor of DataTable's native
selection; Servers captures groupBy in its saved views.
ResourceListPage owns persisted groupBy state (captured in saved views),
renders GroupMenu in the toolbar, accepts selectable/selectedIds/
onToggleSelect/onSelectAll for the native checkbox column, and turns the row
cursor on by default for list pages.
useTableViews deep-compares the live table state against the active view
(hiddenKeys set-normalized, sorts order-sensitive); a dirty view shows an
amber dot on the trigger and Update / Reset-to-saved actions in the popover.
Group-by: groupable columns + groupBy prop render collapsible group header
rows (chevron + label + count); GroupMenu popover picks the grouping. Keyboard
nav: j/k (or arrows) move a cursor row, Enter opens it, x toggles selection —
ignored while typing or in dialogs. Selection: selectable + controlled
selectedKeys/onToggleRow/onToggleAll render a real checkbox column with an
indeterminate select-all, ending the hand-rolled __select column pattern.
One anatomy diagram, the toolbar placement rule, the drawer width scale, and
the EmptyState/Modal/useConfirm/row-click conventions; primitives list gains
ListToolbar, SortChipBar, ViewMenu and the views hook.
ResourceListPage renders the shared ListToolbar and gains a searchInTopbar
flag; Workspaces moves its search into the shared topbar to match Services
(search state still flows through for views). Superseded .wp-list__toolbar /
__tabletools SCSS removed; the .empty-state specificity comment is updated
now that the page-partial copies are gone.
Drawer widths snap to the 380/520/640/720 scale; thirteen raw ui/dialog uses
(services, projects, vaults, serverdetail tabs, webhooks, doctor panel)
convert to the shared Modal wrapper. ConnectProviderModal keeps its custom
chrome (branded header + scroll region Modal can't express).
Security tabs' legacy empty-state markup becomes the component, their bespoke
confirmDialog flows (firewall, fail2ban, ip lists, ssh keys) move to
useConfirm, and the remaining window.confirm calls (telemetry cleanup,
service settings, connections hub) are replaced. The duplicate .empty-state
block in _security.scss is gone.
DataTable emptyState overrides and legacy empty-state markup become the
shared component (wording and CTAs preserved). Also fixes DataTable passing
emptyMessage to a nonexistent EmptyState prop — it now lands on
'description', so custom empty messages actually render.
servers/deployments/server-services/packages/survey toolbars and the email
accounts/queue rows move to ListToolbar (selects get .sk-listhead__select);
email table emptyState overrides become emptyTitle/emptyMessage; the legacy
unscoped .empty-state in _servers.scss is scoped under .servers-page.
cron/bk/incidents listheads move to ListToolbar (counts and menus preserved);
bk-empty and mon-empty become EmptyState; run-history drawer width snapped to
the 640 step of the standard scale.
The .dom-listhead rows (incl. Git's six borrowed section headers) move to the
shared ListToolbar; .dom-empty filtered-empty blocks become EmptyState;
retired SCSS deleted.
.sk-listhead [title][filters][extras] [count][tools] replaces the five
hand-rolled listhead patterns; includes a standard __select style for
secondary pickers. Codifies the placement rule in its docstring.
Both tables switch to controlled sort/cols (same storageKeys); views capture
the FilterDrawer value plus sorts and columns, never server-side page state.
ViewMenu publishes into the topbar via useTopbarActions with a primitive
active-key dep to avoid re-publish loops.
ViewMenu published into the shared topbar before the filter button; the table
switches to controlled sort/cols (same storageKeys, choices survive). A view
captures search, the FilterDrawer status/type value, sorts and columns.
useTableViews merges builtin views with the user's saved views from the API
(auto-applying the default on load); ViewMenu is the picker popover — apply
on click, star for default, delete, save-current-as form, update-active. The
ResourceListPage toolbar grows the menu (state = filter/search/sorts/hidden
columns/page size), so Services and Workspaces get views by declaring their
builtin defs.
SavedView model (user/page/name/state JSON/is_default, unique per
user+page+name), idempotent 082 migration, saved_view_service with
one-default-per-page enforcement, and a JWT-scoped /api/v1/views CRUD
blueprint. Covered by 9 endpoint tests.
Queue messages/operations, delivery log, process table, env-history and
docker containers migrated. The containers tab's select+button sort UI is
deleted in favor of native header sorting (running-first default kept) with
controlled menus; FilterDrawer pairings untouched.
Docker containers/images (size parses to bytes for numeric sort), cloudflared
tunnels, cron, systemd services and survey sites now use the shared primitive;
tabs with toolbars (cloudflared, cron, services) get controlled menus.
Users, invitations, API keys, webhook deliveries, top endpoints and migration
history gain sortable columns (compact variants preserved) and standard
footers. ActivityTab keeps its server-paged fetch; its ad-hoc pager swaps to
DataTableFooter's paged mode.
Workspace sites/services/servers/members, the domain DNS record grid and the
service packages table now use the shared primitive with sortable columns and
footers; conditional DNS columns and fixed-layout classes preserved.
FTP users/connections and the nine fleet tables (versions, rollouts, command
queue, approvals, alerts, anomalies, thresholds, capacity) gain sortable
columns, persisted choices and standard footers; tables with an in-page
toolbar get controlled SortMenu/ColumnsMenu.
Repos and webhooks gain controlled SortMenu/ColumnsMenu in the listhead and
filtered footers; the webhook drawer's recent-deployments table sorts
uncontrolled. The commits feed is not table-shaped and stays as-is.
Snapshots table gains controlled sort/column menus in the listhead plus a
sort chip bar; schedules, latest-snapshots digest and run history sort
uncontrolled with persisted choices; standard footers throughout.
Firewall, Fail2ban, IP allow/blocklists, integrity changes, quarantine,
scanner findings/history and SSH keys now use the shared primitive: sortable
columns (epoch/numeric sortValues), SortMenu+ColumnsMenu in card headers
(.sec-tableactions slot), standard footers, persisted choices.
SortChipBar renders one chip per sort level above the table — click to flip
direction, x to remove, Reset to clear — and collapses to nothing when no
sort is active. Wired into ResourceListPage (list view) and the Servers page.
The servers list now runs on the shared primitive: identical cell markup and
style hooks, sortable name/status/CPU/mem/disk/last-seen (nulls last),
SortMenu + ColumnsMenu in the listhead, a shared footer, and the page's
sticky header preserved. Meter cells generated by a small helper.
Both pages declare sortable columns with meaningful sortValue accessors
(status severity order, timestamps, numerics, nulls last) and pass a
storageKey so ResourceListPage persists sort/columns/page-size.
Jobs' custom Prev/Next 'Page N' pager and Telemetry's bare 'Load more' swap
to the shared DataTableFooter (same state setters, fetch logic untouched);
both tables gain sortable columns and persisted choices. Orphaned pager SCSS
removed.
Email components/accounts/queue tables gain sortable columns; accounts and
queue toolbars get SortMenu + ColumnsMenu (new .sk-email__tabletools slot).
FleetProxy keeps its renderRow layout but gains header sorting (column
visibility intentionally off — renderRow emits every cell).
Cron jobs + run history and the domains list get sortable headers (schedule,
last/next run, duration, expiry sort by real values), SortMenu/ColumnsMenu in
the in-page listheads (new .dom-listhead__tools slot), and standard footers.