Skip to content

v0.4.0

Latest

Choose a tag to compare

@rameerez rameerez released this 09 Aug 01:42
· 2 commits to main since this release
Immutable release. Only release title and notes can be modified.
5d92121

api_keys 0.4.0 — security hardening

This is a security-focused release. Production users should upgrade after reading the migration and behavior changes below.

Migration before deployment

Existing applications must add the bounded authentication lookup index before deploying 0.4.0:

bundle update api_keys
bin/rails generate api_keys:add_authentication_index
bin/rails db:migrate

The generated migration is idempotent and uses concurrent index operations on PostgreSQL where supported. It creates the (prefix, last4, digest_algorithm) lookup used to bound bcrypt candidate work.

Security and reliability highlights

  • Cached entries are key-ID hints only. Every request reloads the database row and cryptographically verifies the presented token, so revocation, expiration, scope, environment, and deletion changes take effect immediately.
  • SHA-256 and bcrypt keys can coexist across configuration changes. Token/digest/cost/prefix/candidate work is structurally bounded; bcrypt performs at most 32 candidate comparisons and fails closed on overfull sets.
  • Plaintext credentials are removed from authentication logs, model/result inspection and serialization, cache values, async callback payloads, and newly written session payloads.
  • One-time dashboard/session handoffs are now application-key-derived AES-256-GCM ciphertext, bound to the created key ID, purpose-bound, ten-minute-expiring, downgrade-safe, and deleted on first retrieval. Legacy 0.3.x handoffs remain readable only for upgrade continuity and are never newly written; because those old values had no timestamp, clear/rotate existing sessions at deploy time if immediate removal is required.
  • Owner rows are pessimistically locked across quota validation and insertion for helper, association, and direct Active Record creation paths.
  • Authentication identity is immutable; digest formats, scopes, metadata, type/environment configuration, public-token policy, and permission ceilings are validated and fail closed.
  • The mounted dashboard now fails closed on missing/ineffective owner authentication, scopes every record lookup to its owner, hardens malformed/error paths, and applies credential-safe cache/referrer/frame/MIME/Permissions-Policy headers plus an enforcing nonce CSP.
  • Production API-key authentication requires HTTPS by default. Query-string credentials remain disabled by default.
  • Callback jobs carry only small serializable, credential-free context; queues resolve after initializers; timestamps are monotonic and counters atomic.
  • CI and release supply chain now include a nine-combination Ruby/Rails matrix, dependency audits, Brakeman, CodeQL, dependency review, Dependabot, secret scanning/push protection, signed protected main, full-SHA Actions, and RubyGems OIDC Trusted Publishing.

Issue #12 is fully resolved

All 13 v0.3.0 review findings are fixed or superseded in this release: raw debug-token logging, plaintext session handoffs, unbounded bcrypt scans, missing-scope status, the decoy parent controller setting, early-bound job queues, tenant/owner aliasing, non-revocable owner-deletion failures, quota races, per-request default stats jobs, redundant new-install owner indexes, development files in the gem, and low native coverage.

See the per-finding code/test/edge-case matrix in PR #23 and the original issue #12.

Behavior changes to review

  • A valid key lacking a required scope now receives 403 Forbidden; missing or invalid credentials remain 401 Unauthorized.
  • Encrypted handoffs add some session-cookie overhead. Applications already near Rails' cookie-size limit, or custom integrations using unusually long tokens, should test their complete session payload or use a server-side session store.
  • Configure dashboard inheritance with config.parent_controller = "Admin::ApplicationController" before engine controllers load. The old internal Engine setting remains a fallback only when the public setting is untouched.
  • Deleting an owner now removes all associated API-key rows, including non-revocable/public types. Direct key-level revoke/destroy protections remain.
  • last_used_at jobs are debounced for one minute by default when exact request counting is disabled. Set stats_update_interval = 0 or nil for per-request timestamps. track_requests_count = true intentionally performs per-request stats work.
  • New typed keys require key_type: or default_key_type; malformed/retired typed environments fail closed. Existing untyped legacy keys remain compatible.
  • Authentication identity fields can no longer be mutated after creation; rotate keys instead.
  • Invalid expiration presets now raise instead of silently creating permanent keys.
  • Authentication callbacks now receive credential-free context hashes and execute asynchronously once. Use a durable Active Job backend when delivery matters.
  • Blank/malformed/out-of-policy scopes fail closed when a scope policy is enabled.
  • Reassess every public: true permission ceiling. The gem validates a finite least-privilege shape, but only the host application can decide whether a business permission is safe for an untrusted client.

Compatibility and verification

  • Gemspec compatibility: Ruby 3.1+ and Rails 6.1+.
  • Actively security-tested matrix: Ruby 3.3, 3.4, and 4.0 × Rails 7.2, 8.0, and 8.1.
  • Final native suite: 315 tests, 966 assertions, no failures/errors/skips; 85.99% line and 77.14% branch coverage.
  • Root, dummy, and Docker dependency audits: no known vulnerabilities at release time.
  • Brakeman and CodeQL: clean on the release commit.
  • Published gem SHA-256: 008ade0316c35dbe8339318d0af20013441788d86d97cda3b8b2ab47b7bdd835.
  • Tag/commit: v0.4.05d92121228b4a0b3bfa50e1beb308a2db7335d95 (GitHub signature verified).
  • Trusted Publishing run: Release #31288696180.

Full details are in the 0.4.0 changelog and release PR.

No software is literally guaranteed 100% secure. Consuming applications remain responsible for TLS/proxy correctness, secure cookies/session stores, rate limiting, business authorization, durable queues, secure database/cache/log/backup operations, monitoring, and incident response.