Skip to content

All versions since v2.3.0

v2.3.0

What’s new

BridgePort 2.3.0 adds outbound webhook subscriptions with HMAC-signed, retried delivery and Idempotency-Key support for safe POST retries, ships a typed OpenAPI spec (/openapi.json) generated from the real request schemas, and surfaces service-type/language badges and deployment servers in the UI. One database migration applies automatically on container start — no manual steps.

One behavior change to know about if you use multi-channel Slack routing and per-environment channel overrides together — see below.


Behavior changes

Per-environment Slack overrides now demultiplex fan-outs

Skip this if you don’t set per-environment Slack channel overrides.

Previously, a per-environment Slack channel override only acted as a fallback for unrouted notification types. A type routed to multiple channels globally fired into all of them from every environment, so you couldn’t mute one environment’s noise without losing another’s.

Now an override collapses a fan-out to that environment’s own channel:

  • Exactly one matched route → sent there; the override is not consulted (env-agnostic single routes still reach every environment).
  • More than one match (fan-out) → if the originating environment has a usable override, send only to that environment’s channel; otherwise the full fan-out is preserved.
  • No match → environment override as fallback, else the global default (unchanged).

Impact: any environment that has both a multi-channel routing and a per-environment override set will now receive only its override channel instead of the full fan-out. Single-routed types and unrouted-fallback behavior are unchanged. To split a shared alert per environment: route the type to >1 channel globally, then set each environment’s override to its own channel. (#221)


Database migrations

One migration, applied automatically on container start (BridgePort’s golden rule — zero human intervention).

  • 20260611215341_webhooks_idempotency — adds three tables: WebhookSubscription (env-scoped endpoints, encrypted signing secret), WebhookDelivery (per-attempt delivery records with retry state), and IdempotencyKey (24h dedupe store for mutating POSTs). Additive only — no changes to existing tables. (#234)

Features

Webhook subscriptions (#234)

Environment-scoped outbound webhooks let external systems react to BridgePort events in near-real-time, as an alternative to the existing admin-scoped notification fan-out.

  • Manage subscriptions under /api/environments/:envId/webhooks (create / list / get / delete), with delivery history at GET …/:id/deliveries.
  • Deliveries are HMAC-signed (X-BridgePort-Signature: sha256=…), sent in the background, and retried with exponential backoff (5 attempts).
  • Terminal events fire within seconds: deployment.completed/failed, plan.completed/failed, backup.completed/failed, sync.completed.
  • Signing secrets are encrypted at rest (AES-256-GCM) and never returned by the API (hasSecret boolean only).
  • This is a separate system from the admin-scoped WebhookConfig notifications (X-Webhook-Signature) — different contracts, kept side by side rather than retrofitted.

Idempotency-Key for safe POST retries (#234)

Mutating POSTs now honor a client-supplied Idempotency-Key header. A global hook dedupes against the key for a 24-hour window and replays the original response, so a client can safely retry a POST whose outcome is unknown.

  • Same key, mismatched body → 409 IDEMPOTENCY_KEY_REUSED.
  • Same key, request still in progress → 409.

Typed OpenAPI spec (#227)

/openapi.json and Swagger UI now carry real request contracts, generated from the existing Zod validation schemas — one source of truth, no doc/validation drift.

  • A checked-in openapi.json snapshot plus a CI drift check that fails if routes change without regenerating the spec.
  • Documentation-only (uses Zod 4’s native z.toJSONSchema(), no new runtime deps); runtime validation and the custom error envelope are unchanged.

Service-type & language badges, deployment servers (#223)

  • Service-type badge on each service card (promoted from inline text; keeps the Generic fallback).
  • Config-file language badge (e.g. yaml, nginx) on text config files, alongside the binary badge.
  • Servers on service detail — a linked list of deployment servers above Deployment History, plus a new linked Server column in the history table ( for legacy rows). No schema change.

API changes

Added

  • POST /api/environments/:envId/webhooks — create a webhook subscription. (#234)
  • GET /api/environments/:envId/webhooks — list subscriptions. (#234)
  • GET /api/environments/:envId/webhooks/:id — get a subscription. (#234)
  • DELETE /api/environments/:envId/webhooks/:id — delete a subscription. (#234)
  • GET /api/environments/:envId/webhooks/:id/deliveries — delivery history. (#234)
  • Idempotency-Key request header honored on mutating POSTs. (#234)
  • serviceDeployment.server field on GET /api/services/:id/deployments-history responses (null for legacy rows). (#223)
  • Typed request/error contracts now present throughout /openapi.json. (#227)

Deprecated

  • The sync result envelope’s legacy success alias is flagged deprecated: true in the OpenAPI spec — prefer the canonical field. (#227)

Removed

  • None.

Security

  • GHSA-w5hq-g745-h8pq (uuid) — missing buffer bounds check in v3/v5/v6. Pinned to ^11.1.1 via pnpm override. Transitive and dev/tooling-only (hyperidautocannon, stress tests); does not reach the production runtime or Docker image, and the vulnerable code path was never called. (#231)
  • GHSA-92pp-h63x-v22m (@hono/node-server)serveStatic middleware bypass via repeated slashes. Pinned to ^1.19.13. Transitive and dev-only (@prisma/devprisma); not in the production runtime. (#231)

Under the hood

Toolchain migrated from npm to pnpm (#226)

The entire JS/TS toolchain now runs on a single pnpm 11 workspace (root backend + ui/), resolved by one root pnpm-lock.yaml. Supply-chain controls move to pnpm’s native allowBuilds allowlist and minimumReleaseAge cooldown; CI, the multi-stage Docker build, and Dependabot were all converted. Dev keeps full strict phantom-dependency protection. Contributors: use pnpm install (see updated docs/development/).

UI dependency upgrades

  • React 19 + recharts 3react/react-dom 18.3 → 19.2.7 and recharts 2.15 → 3.8.1 (client-side only; backend/agent/CLI untouched). (#220)
  • Tailwind CSS 4 — 3.4 → 4.3 (Oxide engine, CSS-first @theme config, Vite plugin replaces the PostCSS pipeline). Like-for-like, no design changes. (#233)

Other notable bumps

  • golang.org/x/term (CLI) — 0.43.0 → 0.44.0 (#228)
  • @types/testing-library__jest-dom (dev) — 5.14.9 → 6.0.0 (#229)
  • /build skill fix — runs the two scoped vitest configs instead of bare npm test (which produced ~759 false failures in this repo). (#224) -----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgEyiv4hf6iBgr34ICjN6HnEP/vs Yr31eNU5HhdkQaYd4AAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 AAAAQDDO04K4v3ckb7ElqF+Sfamr5V97DJ1h3Y37atrVlIRmgcKAmANljMESORjqwD+/f9 fINOxMjM6nyBRviMjfeg0= -----END SSH SIGNATURE-----

v3.0.0

What’s new

BridgePort 3.0.0 is a major release. The web UI is rebuilt on shadcn/ui with a new Deep Slate dark theme and an opt-in light theme; an opt-in MCP server lets you drive BridgePort from your own AI agent; database backups gain a Grandfather-Father-Son (GFS) tiered rotation policy; and a full configuration audit makes dozens of previously-hardcoded knobs tunable. It also completes the 2.x deprecation cycle by removing the sync success alias — the one breaking change (see below).

Migrations apply automatically on container start, and the GFS rollout is designed to prune nothing until you opt in. As always, pull the new image and restart.


Action required before upgrading

Skip this section unless you have external scripts or integrations calling the sync endpoints.

Sync responses no longer include success

The deprecated top-level success: boolean has been removed from the sync result envelope on all three sync endpoints. Read the canonical status field instead — and note it carries information the boolean never could: a zero-target sync returns 200 with status: "no_targets", which you should surface as a warning, not a green success.

if (res.success) { … }
if (res.status === "ok") { … } // "partial" / "no_targets" are distinct outcomes

Affected: POST /api/config-files/:id/sync-all, POST /api/services/:id/sync-files, POST /api/servers/:serverId/sync-all-files. The BridgePort UI and per-target results[].success are unaffected. This was deprecated in 2.0 and removed on schedule per the API Stability Policy. (#236)


Database migrations

Two migrations, applied automatically and safely on first start:

  • 20260613210010_settings_audit_240 — drops 11 per-environment monitoring columns the scheduler silently ignored (and an orphaned registry-token field), and backfills the new retention/timeout system settings on the singleton row. Your collect* toggles are preserved. (#241)
  • 20260624145904_add_backup_rotation_policy — adds the GFS rotation policy tables/columns. Crucially, it creates an inert policy snapshot for every database that already has backups, so the first post-upgrade sweep deletes nothing. GFS only begins thinning a database once an operator reviews and saves its policy. (#292)

Features

MCP server — drive BridgePort from your AI agent (#237)

An opt-in Model Context Protocol server at POST /mcp exposes a curated slice of the API as agent tools, so you can operate BridgePort from Claude Desktop, Cursor, Claude Code, and other MCP clients. No model or inference runs on the host — you bring your own agent.

  • Disabled by default — set MCP_ENABLED=true to register the route (fail-closed; MCP_ALLOWED_HOSTS opts into DNS-rebinding protection).
  • ~55 tools: a comprehensive read/observe surface plus a safe-operate set (deploy_service, restart_deployment, rollback_deployment_plan, run_database_backup, …). Every call replays a real internal request, so auth, role/scope enforcement, validation, idempotency, and audit logging behave identically to the REST API.
  • Secrets never leave: a recursive redactor strips every secret-named field from all output; env-scoped tokens only see the tools they can actually call.
  • New admin page at /admin/mcp shows live status, the exposed tool/resource inventory, and client setup guidance.

GFS tiered backup rotation & retention (#292)

Replaces the flat retentionDays model with a Grandfather-Father-Son policy: keep the keepLast most-recent backups plus daily / weekly / monthly / yearly tiers (a backup survives if any tier selects it), with a minFloor safety guard and an optional per-database maxTotalBytes cap.

  • Presets (lean / balanced / long_term / custom) with inheritance: a global default in System Settings → inherited per database → overridable per database.
  • Pinning & exemptions: manual backups are never auto-pruned, and any backup can be pinned.
  • Safe pruning: file-first deletion (artifact → row), fully audit-logged, with a confirmation gate when a policy change would prune more than the configured threshold.
  • New instance timezone setting (ISO-week bucketing) and two new notification types (backup.rotation_error, backup.policy_first_prune).

UI rebuilt on shadcn/ui + Deep Slate and light themes (#272)

The bespoke component layer is replaced with shadcn/ui (Radix + Tailwind v4 + CSS-variable theming). Ships the Deep Slate dark theme and an opt-in light theme with a user-menu switcher (system default, no flash-of-unstyled-content).

  • Semantic design tokens throughout (sky primary, burgundy brand, success/warning/info, themed charts), a ⌘K command palette, collapsible sidebar with a mobile sheet, and react-hook-form + zod forms across editors and modals.
  • Every page rewritten; charts, topology, and the code editor re-themed; legacy components and color ramps removed.
  • Resolved a backlog of UI-audit findings and rendering bugs along the way.

Configuration audit & deploy-time tunables (#241, #257)

A full pass over the configuration surface — making existing behavior controllable and honest, with no new product behavior by default.

  • New admin-editable retention settings (notifications, health logs, webhook deliveries, image digests), read hot by the scheduler each cleanup tick, consolidated into one “Retention” section in System Settings.
  • Exposed previously-uncontrollable knobs (pgDumpTimeoutMs, multipart upload limit) and removed settings the app silently ignored.
  • 21 new deploy-time environment variables (MySQL/webhook timeouts, response-cache size, SSH exec buffer, and more), each defaulting to today’s value — zero behavior change unless set.

API changes

See the API Stability Policy.

Added

  • POST /mcp — opt-in MCP transport (off unless MCP_ENABLED=true). (#237)
  • Backup-policy endpoints: PUT/GET …/backup-policy, POST …/backup-policy/preview (viewer-allowed), and idempotent PUT …/backups/:backupId/pin. (#292)
  • Typed querystring/params schemas added to the remaining untyped routes (incl. audit, events, downloads) — the OpenAPI spec now documents ~42 more query parameters. No behavior change. (#273)
  • Each GitHub Release now ships the OpenAPI spec as an openapi.json asset, so consumers can diff the contract between versions without a running instance. (#294)

Removed

  • Sync response top-level success: boolean (deprecated in 2.0) — read status instead. See Action required above. (#236)
  • 11 dead per-environment MonitoringSettings fields (monitoring interval/retention/bounce + a no-op enabled) and SystemSettings.doRegistryToken — monitoring cadence is global (SCHEDULER_*) and bounce thresholds live on notification types. (#241)

Improvements

Frontend bundle split into route + vendor chunks (#281)

The single ~2.3 MB JS bundle is now ~80 lazy-loaded route and vendor chunks (charts, flow, CodeMirror, Radix, Sentry, React), so pages load on demand and vendor code is long-cacheable — resolved by real code-splitting, not by raising the warning limit.

New port-gantry-crane logo & brand loader (#282)

An SVG-native, monochrome-burgundy port gantry crane mark (a crane carrying a container — orchestration = the port), deliberately name-independent. Doubles as the app’s loading animation (honors prefers-reduced-motion), with a full favicon/PWA icon set.


Fixes

Operator/viewer access to the Secrets & Vars page (#275)

Operators saw an empty “No secrets configured” page because one admin-only fetch in a Promise.all rejected the whole batch (403). The admin-only call is now skipped for non-admins, and viewers no longer see write controls that would only 403 on submit.

Dashboard render loop (“Maximum update depth exceeded”) (#276)

useToast() returned a fresh object every render, which cascaded into an infinite render loop in the topology diagram (surfaced after the UI migration). The toast handle is now a referentially stable module-level singleton, with a regression test.

Duplicate logo icon in the sidebar header (#279)


Security

All resolved via dependency updates:

  • form-data → 4.0.6 — CRLF injection (Dependabot #80). (#283)
  • @opentelemetry/core → 2.8.0 — unbounded memory allocation (Dependabot #81). (#284)
  • nodemailer → 9.0.1 — security-group update. (#285)
  • esbuild ≥ 0.28.1 — GHSA-g7r4-m6w7-qqqr (dev-server only; not exploitable in production builds). (#238)

Under the hood

  • Quieter CI builds (#278): skip the ssh2/cpu-features native addon build that can’t compile against Node 26’s V8 (runtime was always on the pure-JS path) and bump pnpm to 11.7.0.
  • Dependency bumps: @fastify/swagger-ui 6 (#289), @fastify/rate-limit 11 (#288), fastify-plugin 6 (#290), actions/checkout 7 (#286), and the js-minor-and-patch group (#280).

Documentation

  • MCP server woven into the cross-cutting docs for the public release (#258), and the branding doc rewritten around the finalized logo system (#282). -----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgEyiv4hf6iBgr34ICjN6HnEP/vs Yr31eNU5HhdkQaYd4AAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 AAAAQNGQNwQiZVvgQjvkoy6DGLzn3g4fXzhVC/N0uR2QyXZxf0T4Stkq6fKzgcXuMx1vAq Xvu5OSu7ZYix34K4+t2ws= -----END SSH SIGNATURE-----

v3.0.1

What’s new

BridgePort v3.0.1 is a reliability patch. Under sustained concurrent write load, a freshly-booted instance could intermittently return transient 500s (typically on GET /api/auth/me) whenever a second writer held the SQLite write lock. Those statements are now automatically retried, and genuinely unresolvable contention returns a retryable 503 instead of an opaque 500. No database migrations — upgrade is drop-in.


Fixes

Transient 500s under sustained write contention (#299)

BridgePort serves requests through a single synchronous better-sqlite3 connection, so it never self-contends — but when a second writer held the write lock (a long external transaction, a WAL checkpoint, or a test harness resetting state), two SQLite modes surfaced as opaque 500s:

  • SQLITE_BUSY — lock held past busy_timeout, mapped to Prisma P1008.
  • SQLITE_BUSY_SNAPSHOT — a stale read snapshot upgrading to a write; returned immediately, so busy_timeout could never absorb it.

The fix:

  • Automatic retry — a Prisma client extension retries transient contention (P1008/P1017/P2024/P2034, SQLITE_BUSY/locked) with jittered exponential backoff. Retry is per-statement and verified safe — no double-writes, including inside an interactive $transaction.
  • Retryable 503, not opaque 500 — when retries are exhausted, the request returns 503 Service Unavailable with a Retry-After header, logged at warn and kept out of Sentry.

Measured with a concurrent CRUD + /api/auth/me repro under a bursting external write lock: 24 → 0 500s.


API changes

No endpoints or fields were added, changed, or removed. One behavior change: transient database-contention errors now return 503 (with Retry-After) instead of 500. Monitoring or alerting that treats all 5xx as outages should treat these 503s as retryable.


Configuration

  • New DB_RETRY_* knobs tune retry attempts and backoff — sensible defaults, no action required.
  • SQLITE_BUSY_TIMEOUT_MS default lowered 50001000. better-sqlite3’s busy-wait is synchronous and blocks the event loop, so the per-attempt wait is kept short while the async retry loop (which frees the loop between attempts) carries longer contention.

See Configuration.


Database migrations

None — this release makes no schema changes.


Ecosystem

Alongside the 3.x line, BridgePort now has first-class infrastructure-as-code tooling (versioned independently of the platform):

  • Terraform / OpenTofu provider — manage environments, servers, config, secrets, registries, images, and services declaratively. Live on both the Terraform and OpenTofu registries; source at terraform-provider-bridgeport.
  • Go SDKgithub.com/bridgeinpt/bridgeport/client, a typed read + write client for the HTTP API.

Upgrade

Drop-in — pull and restart, no migrations or config changes required.

Terminal window
docker pull ghcr.io/bridgeinpt/bridgeport:v3.0.1

-----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgEyiv4hf6iBgr34ICjN6HnEP/vs Yr31eNU5HhdkQaYd4AAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 AAAAQAQzL5g91VpEHH4vS6L7IftJp7NJQnkfYUd7BLVWvTbALcCIjoqnf65ol/jEO0QbKz HDrnnGVhKOtcKUU3Y4GgY= -----END SSH SIGNATURE-----

v3.0.2 Latest

What’s new

A drop-in patch — no migrations, no config changes. It fixes over-aggressive rate limiting that could surface a normal dashboard load as a 500, makes the frontend self-heal stale chunks after an upgrade, refreshes the app icons, and — the headline — launches the public documentation site at bridgeport.bridgein.com.


Fixes

Rate limiting no longer trips on normal page loads (#312)

The global limiter (100 req/min per IP) was applied to every route, including static-asset serving and the SPA shell — so a single dashboard load (HTML + a dozen hashed JS/CSS chunks + 30s polling) could exhaust the budget for one legitimate user. The rejection on the static route was then mis-classified as a 500 and captured to error monitoring.

  • Static/SPA GET/HEAD requests are now exempt from the limiter; /api/* and /mcp stay throttled — the limiter protects the programmatic surface, not local file serving.
  • Genuine rate-limit responses now return a correct 429 Too Many Requests with Retry-After instead of a 500, and no longer spam error monitoring.

If you front BridgePort with a reverse proxy or CDN, that’s the right layer to DoS-protect static serving.

Frontend self-heals stale chunks after a deploy (#312)

A vite:preloadError handler now does a one-shot, cooldown-guarded reload when a lazy-loaded route chunk fails to fetch — typically right after an upgrade swaps the hashed assets. “Failed to fetch dynamically imported module” errors recover transparently instead of dead-ending the page.


Improvements

Refreshed app icons (#310)

New favicon and PWA/app-tile icons in the brand aesthetic — the red crane mark on dark #0a0e14 tiles, with a transparent favicon that adapts to light and dark browser tab strips.


Security

  • CVE-2026-12143 / GHSA-hmw2-7cc7-3qxx — CRLF injection in form-data (< 4.0.6), high severity, pinned to ≥ 4.0.6. This dependency lives only in the docs-site build toolchain (website/) and never reaches the app runtime or the Docker image, so the running app was never exposed — closed for hygiene. (#309)

Documentation

Public documentation site launched (#307, #308, #311, #313)

BridgePort now has a full docs site at bridgeport.bridgein.com, built with Astro Starlight from the repo’s docs/ — a single source, so the site and GitHub never drift. Highlights:

  • Full-text search, dark/light, branded to match the app UI
  • API reference auto-generated from the OpenAPI spec
  • Architecture & flow diagrams rendered from Mermaid
  • A changelog sourced from these GitHub Releases (so this release will appear there automatically)
  • Real product screenshots (dashboard, monitoring, services)
  • An llms.txt endpoint so AI agents can consume the docs
  • Auto-deploys via Cloudflare on every docs change and on each published release

API changes

None. (Rate-limit responses now return 429 instead of an erroneous 500 — a fix, not a surface change; see Fixes.) -----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgEyiv4hf6iBgr34ICjN6HnEP/vs Yr31eNU5HhdkQaYd4AAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 AAAAQLoiwVIhyZuzlKhDAlmko0bhVpoKrbuRyBug03RBdL0BFftf7YkHAREPYq7n9qD4Hh r2jduikwYktaNiQCaXrQI= -----END SSH SIGNATURE-----