Skip to content

v1.1.0 — hardened, keyless again, and gated on a booted stack

Choose a tag to compare

@AndrewCTF AndrewCTF released this 13 Sep 13:42
· 12 commits to master since this release
fe155b8

Minor release. It closes the security gaps found against ISO 27001, NIST CSF 2.0,
SSDF and ASVS 5.0, restores a basemap that works with no key, and stops images
from being published unless the real stack boots and renders.

Read "Upgrading" before you pull: several defaults now fail closed.

The globe works without a key again

CARTO began stamping "API KEY REQUIRED" onto every keyless Dark Matter tile,
server-side requests included, so v1.0.x globes showed the watermark. The default
basemap is now Esri's World Dark Gray canvas, with its label layer composited on
the server and cached as before.

  • If the tile proxy can't be reached (backend down or still booting), the browser
    fetches Esri base tiles directly, so the globe is never blank. Those fallback
    tiles have no labels.
  • The map no longer waits on /api/config. It renders on keyless defaults with a
    "connecting to backend…" label, then upgrades in place.
  • The sign-in prompt appears only after the backend actually answers 401, not
    whenever the backend is unreachable.

Trade-off: Esri's canvas stops at zoom 16 (CARTO went to 20), so street level is
upsampled. The satellite basemap still reaches 19.

Colour schemes

The seven hand-rolled schemes now use published palettes: IBM Carbon (default,
Daylight), GitHub Primer (Midnight, High contrast) and Radix Colors (Slate,
Night watch, Paper). The four Blueprint schemes are unchanged. Every scheme clears
WCAG AA in the contrast guard.

Security

The full gap analysis is docs/security/gap-analysis-2026-09.md, and all 253
ASVS 5.0 Level 1 + 2 requirements are assessed in
docs/security/asvs-l2-assessment.md. Highlights:

  • op.python runs in a bubblewrap jail and refuses to run without one.
  • Running a workflow or managing a model requires operator authority.
  • The rate limiter no longer trusts a caller's own X-Forwarded-For, and
    X-Velocity-Tier is ignored from every peer.
  • Foundry is closed on a keyless box, and /mcp answers 503 until API_KEY is set.
  • Baseline security headers, and a CSP in every production web build.
  • The audit log is append-only and hash-chained (GET /api/audit/verify).
  • Supabase sessions are verified against JWKS and re-checked with GoTrue, failing
    closed. MFA and a maximum session age are opt-in.
  • Sidecars require a per-spawn bearer token.

Releases are gated

publish.yml now pushes images only after ci.yml and the new
release-gate.yml pass for the tagged commit. The gate builds the shipped
images, boots them with docker-compose.prod.yml, and drives the page in a real
browser. That includes loading with the API stopped and recovering in place when
it starts. The Cloudflare gateway and deploy.sh are gone; releases ship as
ghcr images only.

Upgrading

  • docker-compose.prod.yml requires VELOCITY_VERSION (there is no
    :latest):
    VELOCITY_VERSION=v1.1.0 docker compose -f docker-compose.prod.yml pull
    VELOCITY_VERSION=v1.1.0 docker compose -f docker-compose.prod.yml up -d
  • nginx binds 127.0.0.1:8080. Put a TLS reverse proxy on the host in front
    of it.
  • ?key= works on WebSocket upgrades only. HTTP callers must send
    X-API-Key or Authorization: Bearer.
  • Secrets shorter than 32 characters refuse to boot.
  • Alert-rule webhooks must be public hosts unless listed in
    WORKFLOWS_HTTP_ALLOW_HOSTS.
  • op.python returns 503 where the kernel blocks unprivileged user
    namespaces, including the production container. Set
    WORKFLOWS_PYTHON_UNSANDBOXED=1 only if you accept running it unjailed.
  • The first tile requests after upgrade fetch from Esri. Old CARTO tiles stay in
    the cache directory but are no longer served.

The operator-facing list is in docs/decisions.md and
docs/security/operator-hardening.md.

Images

  • ghcr.io/andrewctf/velocity-api:v1.1.0
  • ghcr.io/andrewctf/velocity-web:v1.1.0

Full changelog: v1.0.1...v1.1.0