All versions since v3.0.1
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 pastbusy_timeout, mapped to PrismaP1008.SQLITE_BUSY_SNAPSHOT— a stale read snapshot upgrading to a write; returned immediately, sobusy_timeoutcould 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 opaque500— when retries are exhausted, the request returns503 Service Unavailablewith aRetry-Afterheader, logged atwarnand 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_MSdefault lowered5000→1000.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 SDK —
github.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.
docker pull ghcr.io/bridgeinpt/bridgeport:v3.0.1-----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgEyiv4hf6iBgr34ICjN6HnEP/vs Yr31eNU5HhdkQaYd4AAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 AAAAQAQzL5g91VpEHH4vS6L7IftJp7NJQnkfYUd7BLVWvTbALcCIjoqnf65ol/jEO0QbKz HDrnnGVhKOtcKUU3Y4GgY= -----END SSH SIGNATURE-----
v3.0.2
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/HEADrequests are now exempt from the limiter;/api/*and/mcpstay throttled — the limiter protects the programmatic surface, not local file serving. - Genuine rate-limit responses now return a correct
429 Too Many RequestswithRetry-Afterinstead 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.txtendpoint 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-----
v3.1.0 Latest
What’s new
This is a security-hardening release. It closes an unauthenticated deploy-trigger path in the incoming webhook endpoints, escapes every user-influenced value that reaches a shell command (closing command-injection paths, including one on the control-plane host), fixes a cross-environment access-control gap on the database monitoring routes, and adds SSRF guards to the registry and outgoing-webhook clients. It also ships dashboard-topology rendering fixes and expanded documentation. No database migrations.
If you use the incoming webhook endpoints (/api/webhooks/*), read “Action required” below before upgrading — they now fail closed.
Action required before upgrading
Skip this section if you don’t use the incoming CI/CD webhook endpoints.
Incoming webhooks now fail closed
The /api/webhooks/deploy, /api/webhooks/deploy-image, and /api/webhooks/github endpoints now require a configured secret and a valid signature. Previously the signature was verified only when a secret happened to be set (and the GitHub endpoint could be bypassed entirely by omitting the signature header). If the relevant secret is not set, the endpoint is now disabled and returns 401.
Before upgrading, make sure the secret is configured on your BridgePort instance:
# Generate a strong secret and set it in your environment / compose:openssl rand -base64 32# WEBHOOK_SECRET=... (for /api/webhooks/deploy and /deploy-image)# GITHUB_WEBHOOK_SECRET=... (for /api/webhooks/github)Your CI must sign the exact request body bytes it sends (HMAC-SHA256, hex; the GitHub endpoint uses the sha256= prefix). See docs/guides/webhooks.md. If you don’t use these endpoints, no action is needed. (#351)
Security
This release resolves findings from an internal security review. The webhook issue is the only one reachable without authentication; the command-injection and SSRF paths require an authenticated operator (BridgePort’s role gate already blocks read-only “viewer” accounts from the operations that reach them), and the IDOR affects environment-scoped API tokens.
- Unauthenticated webhook deploy-trigger / signature bypass — the GitHub webhook verified its signature only when the header was present, so omitting it skipped verification even with a secret configured; the other webhook endpoints verified only when a secret was set. All three now fail closed (secret + valid signature required), HMAC is computed over the raw request bytes, and the comparison is length-safe. Malformed payloads and image tags containing shell metacharacters are rejected. (#351)
- Command-injection hardening in shell exec paths — every user-influenced value interpolated into a shell command (container name, image reference, health-check URL, database-backup connection fields, SSH monitoring placeholders) is now shell-escaped, and config/compose files are written over SFTP instead of shell heredocs. (#351)
- Cross-environment access control (IDOR) — the database
metrics,test-connection, andmonitoringsub-routes now verify the database belongs to the environment in the request path, so an environment-scoped token can no longer reach databases in other environments. (#351) - SSRF guards — the container-registry client and the admin outgoing-webhook delivery path now block private/loopback/link-local/cloud-metadata addresses and no longer follow redirects;
registryUrlmust be a valid URL. (#351) - Defense-in-depth — baseline security response headers (
X-Content-Type-Options,X-Frame-Options,Referrer-Policy), an explicit secret-scrub on outbound Sentry events, and a startup warning whenJWT_SECRETis weak. (#351)
Tracked as follow-ups (not in this release): hashing agent tokens at rest, explicit per-route role gates on the service/registry routes, and per-account login throttling.
Fixes
Dashboard topology rendering (#335, #336, #337)
- Services with deployments on multiple servers now render under every server they run on in the dashboard topology diagram, instead of only one. (#335, #336)
- Saved server-box sizes are clamped to their children’s footprint, so a manually-resized server box no longer clips the services inside it. (#337)
Documentation
- “What’s Next” roadmap page — the docs site now has a
/roadmap/page generated at build time from the GitHub milestones and epics, plus a discoverable manual redeploy workflow for refreshing it. (#349) - Maintainer runbook — a new
docs/development/maintainer-runbook.mdconsolidating release, CI, tracker, and naming conventions. (#350) - Docs-site build & deploy documentation and general discoverability improvements. (#315, #334)
Under the hood
- Release → docs refresh —
release.ymlnow pings the Cloudflare Pages deploy hook after cutting a release, so the docs changelog refreshes without waiting for the next push to master. (#314) - Dependency-group bumps: #331, #332, #333. -----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgEyiv4hf6iBgr34ICjN6HnEP/vs Yr31eNU5HhdkQaYd4AAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 AAAAQNdqD+8jBq0JCSd88RMP81EGZNPSM0s0KvG5Xa0UWCVsx0Ii8D8rfvQ1qr//f4IH52 Dk1u7+dyoKVwWppZ27vg8= -----END SSH SIGNATURE-----