Skip to content

v0.3.0

Choose a tag to compare

@0mjs 0mjs released this 25 Sep 12:35
· 103 commits to dev since this release
7091c7f

Zinc 0.3.0

Zinc 0.3 is a hardening release. It tightens security defaults, bounds memory in stateful middleware, and makes routing, binding, and response handling behave the same way on every path, while recovering the performance that correctness work cost.

This is an intentional pre-1.0 breaking release. Few signatures change, so most applications compile unchanged, but several behaviours are stricter. Read the migration guide before upgrading. In particular:

  • Session cookies from 0.2 are invalidated, so users must sign in again. Secrets need at least 32 random bytes, and Session.Set and Delete now return errors.
  • Prometheus registries are per middleware, not shared globally. A /metrics route outside that middleware needs an explicit registry.
  • BodyLimit (4 MiB by default) now covers forms and multipart uploads.
  • Unsafe configurations panic at startup: wildcard CORS origins with credentials, invalid trusted proxies, short session secrets, and invalid rate-limiter settings.

Security

  • c.IP() walks forwarded chains from the right and stops at the first untrusted hop, so clients can no longer choose their own address. Scheme() accepts only http or https from trusted proxies.
  • Credentialed CORS requires explicit origins.
  • Signed sessions carry an authenticated, server-checked expiry and support key rotation with PreviousSecrets.
  • Group middleware, including authorization, now applies to Static, File, and Mount registered on the group, and to requests rewritten into a prefix.
  • Static serving rejects ambiguous paths and keeps symlinks inside the root.
  • The reverse proxy strips hop-by-hop headers before Director runs and retries only idempotent, replayable requests by default.

Bounded resources

  • Request-body budgets apply to every binding and upload path, and to decompressed bodies.
  • Keyed rate limiters hold at most MaxKeys buckets (10,000 by default) and expire idle ones.
  • Prometheus label sets are bounded, with a series cap and a zinc_http_metrics_dropped_total counter.
  • Oversized route-cache keys bypass the cache instead of growing it.

Correctness

  • A response commits only when bytes, final headers, a flush, or a hijack go out. The error handler runs at most once per request.
  • Binding failures return a generic 400 and keep HTTP causes such as 413. Bind().All merges path and query values for every struct body format, and fields accept encoding.TextUnmarshaler and optional pointers.
  • Routing precedence is consistent under case-insensitive matching. Mounted handlers see a consistently stripped path and RequestURI. app.URL produces URLs that route back to the same values.
  • Gzip, Recover, and Accept negotiation handle edge cases correctly: small flushes, http.ErrAbortHandler, and q=0 exclusions.

Performance

The hardening work added some overhead. This release recovers it with faster query and JSON binding, cached canonical header names in binding plans, lower response and default-error overhead, reuse of confined static roots, and smaller route-cache snapshots.

In the latest comparison against Gin, Echo, and Chi on an Apple M1 Pro, Zinc had the lowest median latency in 60 of 77 workloads. Zinc was measured at 42e11d2 against rival samples recorded the day before; see BENCHMARKS.md for the method and the full results. Correct per-response header ownership adds one small allocation to common string responses.

New APIs

  • App.Close() releases static roots when you run your own http.Server.
  • Context.BodyLimit() exposes the body budget to middleware.
  • Session: PreviousSecrets, Lifetime, ErrSessionCommitted, ErrSessionTooLarge.
  • Rate Limiter: MaxKeys, MaxKeyBytes, IdleTTL, Now.
  • NewPrometheusMetrics(maxSeries).

Documentation and tooling

  • A redesigned documentation site at zinc.carbonsoft.sh, with an interactive route tree, a middleware table that builds your app.Use chain in the right order, and results read straight from the benchmark report.
  • A new Migrating to 0.3 guide, and corrected pages on client IPs, CORS, and sessions.
  • zincbench, a Go tool for recording benchmark runs locally and comparing them against a baseline.

Breaking-change checklist

  1. Warn users that 0.2 session cookies are invalidated, and use a session secret of at least 32 random bytes.
  2. Check the errors from Session.Set and Delete, and call them before writing the response.
  3. Replace AllowOrigins: ["*"] with explicit origins wherever CORS allows credentials.
  4. List every proxy you operate in TrustedProxies, and check the IPs you log and rate-limit by.
  5. Pass one explicit registry to Prometheus and PrometheusHandler if /metrics is not behind that middleware.
  6. Size MaxKeys for keyed rate limiters.
  7. Raise Config.BodyLimit if you accept forms or uploads over 4 MiB.
  8. Return your own errors or set ErrorHandler if clients read binding error details.
  9. Replace type assertions on c.Writer() with http.NewResponseController or Unwrap().
  10. Move public assets off authenticated groups, and call app.Close() when you run your own server.

See the migration guide for details.

Full changelog: v0.2.1...v0.3.0