Release v1.63.0
๐ Features
๐ Redact query arguments
SQL bind arguments and Redis keys/values โ often PII or tokens โ are written to query logs and Redis spans by default. These switches mask them instead. Values are masked rather than dropped, so the argument count stays visible and the log shape is unchanged.
| Config | Description | Default |
|---|---|---|
DB_REDACT_ARGS
| Mask each SQL argument as ***** in the query log, for queries and inside transactions
| false
|
REDIS_REDACT_ARGS
| Mask Redis arguments in the query log and drop db.statement from Redis spans
| false
|
๐ Redis URL & pool tuning
Point GoFr at a managed Redis with a single REDIS_URL (rediss:// turns on TLS and verifies the host). The URL wins: when set, it alone decides host, port, credentials, DB and TLS, and any overlapping REDIS_HOST/REDIS_PORT/โฆ are ignored with a warning. The tuning variables work with or without the URL.
| Config | Description | Default |
|---|---|---|
REDIS_URL
| Full connection URL (redis:// or rediss://)
| โ |
REDIS_POOL_SIZE
| Maximum connections in the pool | go-redis default |
REDIS_MIN_IDLE_CONNS
| Minimum idle connections kept open | go-redis default |
REDIS_DIAL_TIMEOUT
| Timeout for establishing a connection | go-redis default |
REDIS_READ_TIMEOUT
| Timeout for reads from the socket | go-redis default |
REDIS_WRITE_TIMEOUT
| Timeout for writes to the socket | go-redis default |
๐ Richer HTTP retries
service.RetryConfig used to retry immediately, replay POST, and ignore Retry-After โ one slow upstream could turn into a retry storm. These optional fields fix that. Every field's zero value keeps today's behavior, so a config that sets only MaxRetries is unchanged.
| Field | Description |
|---|---|
Backoff
| Constant or exponential backoff, with full jitter |
RetryOn
| Which errors and statuses to retry (StandardRetryable provided)
|
RespectRetryAfter / MaxRetryAfter
| Honor Retry-After on 429/503, capped
|
Methods / IdempotencyKeyHeader
| Retry only safe or idempotency-keyed methods |
PerAttemptTimeout
| Bound the time each attempt waits for response headers |
Budget
| Cap retries per service over a sliding window |
๐ก๏ธ RBAC role arrays & permissions mode
A token whose claim is an array of roles ("roles": ["admin","viewer"]) now grants every role's permissions โ previously the array matched nothing and returned 403. Permissions mode authorizes straight from the token: a string claim is split on spaces, an array is read entry by entry. Role names with spaces stay intact and are never split.
Config (rbac.json)
| Description |
|---|---|
jwtClaimPath
| Claim holding the role(s); now accepts a single role or a role array |
permissionsClaimPath
| Claim holding permissions to authorize from directly (e.g. scope)
|
โ RBAC startup route check
configs/rbac.json is a hand-kept copy of the route table, so a single typo can silently leave a route served with no role check. At startup GoFr now compares the rules against the real routes and reports three problems: a rule that matches no route, a route no rule covers, and a route only partly covered.
| Config | Values | Default |
|---|---|---|
GOFR_RBAC_ROUTE_CHECK
| warn (log and start) ยท fail (refuse to start) ยท off
| warn
|
โฑ๏ธ DB connect timeout
A connect attempt to an unresponsive database had no time limit. Postgres, Supabase and CockroachDB cap the whole attempt via connect_timeout, while MySQL caps the TCP dial.
| Config | Description | Default |
|---|---|---|
DB_CONNECT_TIMEOUT
| Bound a database connect attempt, in whole seconds | 0 (off)
|
๐ Unmatched-metric cap
Scanner traffic (/.env, /wp-json, โฆ) creates a new metric series per path, and across many short-lived instances these pile up at the backend. The cap collapses the overflow to __unmatched__. It can only lower the budget, never raise it, so it cannot reopen the cardinality blow-up it guards against.
| Config | Description | Default |
|---|---|---|
METRICS_UNMATCHED_LIMIT
| Max distinct unmatched (path, method, status) label sets per process
| 500
|
๐ ๏ธ Fixes
HTTP & services
405 Method Not Allowed(not 404) for a known path with the wrong method- Reuse the OAuth token instead of fetching one per request
- Auth and
DefaultHeadersoptions no longer mutate the caller's header map - Redis rate limiter keeps fractional refill and sub-second windows
- Invalid rate-limiter config logs instead of panicking
- Validate
ACCESS_CONTROL_MAX_AGEandALLOW_CREDENTIALSat startup
SQL & Redis
- Cloud SQL connects to Postgres over IAM without resolving the instance name
- Log a missing
DB_DIALECTand keep container SQL nil when uninitialized - Keep container Redis nil when uninitialized
dbresolverskips a replica whose connection failed to initializeDB.Closewaits for its background goroutines- Report migrations below the last applied version that never ran
- Mock container validates a
Selectbefore filling the response
NoSQL, search & messaging
- Dgraph reports the real
HealthCheckcause, and ends spans + records latency on failures - ArangoDB registers metrics and returns not-connected errors on a failed
Connect - SurrealDB stays disconnected when
Connectfails after dialing - NATS detects an existing stream with
errors.Is, not error text - OpenTSDB returns
errInvalidParamfor every wrong-argument type
Files
CommonFileSystemconnection state is safe for concurrent use- FTP no longer desyncs on concurrent or overlapping operations
- SFTP reads JSON arrays and objects on every Go version
CLI, auth & observability
cmdpreserves=in parameter values and exits non-zero on a handler error- Reject empty basic-auth credentials
- Datasource health checks honor the caller's context
- Metrics warn when the environment sets
service.name, and guard a nil Logger - RBAC logs an
inheritsFromrole that matches no defined role - Log discarded JSON encode errors in panic recovery and MCP responses
Submodule note: ArangoDB's generated gomock mocks moved into
_test.gofiles โ import them from tests only.
Full Changelog: v1.62.0...v1.63.0