fix: validate X-Api-Key against configured allowlist#130
fix: validate X-Api-Key against configured allowlist#130dobby-coder[bot] wants to merge 2 commits intomainfrom
Conversation
The previous request guard only checked that an `X-Api-Key` header was present and admitted any non-empty value to the higher-quota tier (100 GB/upload, 100 GB rolling window vs. the default 5 GB). An unauthenticated client could defeat the storage / bandwidth abuse controls by sending an arbitrary header value. Validate the header value against a configured allowlist of sha256 hashes, identify the caller by tenant id when matched, and use that tenant id as the rolling-window accounting key (`api-key:<tenant>`). When no allowlist is configured (or the value does not match), the caller is treated as the default tier — including requests that send an `X-Api-Key` header. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
solve the conflicts |
|
The bot is on it! Resolving conflicts on PR #130 right away — Dobby will rebase against main and check the build before pushing! |
…-validation # Conflicts: # src/main.rs # src/store.rs
|
Conflicts resolved against Two conflicts, both in fields added to
No code outside the two conflict sites needed to change — main's Verification on merged tree:
PR is back to mergeable. CC @rubenhensen. |
|
Closing in favor of the redesign discussed in #123: cryptify will validate the API key against pg-pkg via a new |
Summary
Closes #123.
The
ApiKeyPresentrequest guard only checked presence of theX-Api-Keyheader — any value (X-Api-Key: x) admitted the caller to the higher-quota tier (100 GB per upload + 100 GB rolling, vs. the default 5 GB). The value was never forwarded to PKG either, so the comment "PKG handles validation" was wrong.What changed
api_keysconfig section: a list of{ tenant, hash }entries wherehashis hex sha256 of the raw key value. Raw keys never appear on disk.validate_api_key(header_value, configured) -> Option<String>helper insrc/config.rsthat hashes the incoming value and matches it against the allowlist with a constant-time compare per candidate. Returns the tenant id on match,Noneon miss / missing / empty.ApiKeyPresent(bool)is replaced byApiKey(Option<String>).FileState.is_api_key: boolbecomesFileState.api_key_tenant: Option<String>. Limit selection (PER_UPLOAD_LIMITvs.API_KEY_PER_UPLOAD_LIMIT, same for rolling) keys offOption::is_some().api-key:<tenant>for API-tier callers (per-tenant, not per sender email), addressing the "rolling window is per sender email — a known, weaker tracker" point in the issue./usage?email=…returns the per-tenant total whenX-Api-Keyis validated, otherwise the per-email total as before.X-Api-Keyheader.Tests
src/config.rsships 8 new unit tests onvalidate_api_key:NoneNone"anything","x") →None(pins the new behaviour: presence-only no longer grants access)Nonefor any valueSome(tenant)Full test suite:
cargo test→ 38 passed; 0 failed.cargo fmt --all -- --checkandcargo checkare both clean on the touched files.Reviewer quickstart
To exercise the guard end-to-end against a running instance, generate a hash with
printf 'YOUR-KEY' | sha256sum, drop it intoconf/config.dev.tomlunder[[api_keys]], restart the container (bind-mounted config does NOT pick up changes without a restart — seerepos/cryptify.md), and send a request withX-Api-Key: YOUR-KEY.Notes / follow-ups
API_KEY_PER_UPLOAD_LIMIT = 100 GBandAPI_KEY_ROLLING_LIMIT = 100 GBexactly as before. If those should change at the same time, that's a separate small PR.postguard-website) and extensions do not need changes — they already sendX-Api-Keywhen configured. Today they would silently get the default tier if the key is not in the allowlist; that is the intended behaviour.🤖 Generated with Claude Code