Skip to content

Security: X-Api-Key presence-only check allows quota bypass (5GB → 100GB) #123

@dobby-coder

Description

@dobby-coder

Summary

Severity: high (abuse / quota bypass; no data confidentiality impact)

The ApiKeyPresent request guard at src/main.rs:80-89 checks only that the X-Api-Key header is present — the value is never validated against any backend:

async fn from_request(req: &'r rocket::Request<'_>) -> ... {
    let present = req.headers().get_one(\"X-Api-Key\").is_some();
    rocket::request::Outcome::Success(ApiKeyPresent(present))
}

The comment claims "value not validated here — PKG handles that", but the value is never forwarded to PKG either (no reference to X-Api-Key outside main.rs:86). The PKG only authenticates the recipient at decryption time — it has no role at upload time.

is_api_key flips the per-upload and rolling-window quota limits 20×:

Limit Without header With header
PER_UPLOAD_LIMIT 5 GB 100 GB
ROLLING_LIMIT (per sender, sliding window) 5 GB 100 GB

(src/store.rs:11-14, applied at src/main.rs:315 and src/main.rs:464.)

Impact

Any anonymous client can defeat the per-upload and rolling-window abuse controls by sending an arbitrary header — e.g. X-Api-Key: x. The 5 GB caps that limit storage abuse on Procolix become 100 GB caps for anyone who reads the source.

This is not an authentication bypass (recipients still need PKG-issued keys to decrypt), but it does defeat the storage / bandwidth abuse controls.

Suggested fix

  • Validate the X-Api-Key value against a configured allowlist (config-driven, hashed at rest) before treating the request as a higher-quota tenant. The validated key should also identify the tenant for per-key quota accounting (today the rolling window is per sender email — a known, weaker tracker).
  • If the upstream design really does treat presence as authorization (it should not), at minimum rename the type to ApiKeyHeaderPresent and document explicitly that quota tiers are unauthenticated, so reviewers do not assume otherwise.

Where

  • src/main.rs:79-89 — guard
  • src/main.rs:125is_api_key: api_key.0 set on FileState
  • src/main.rs:315, src/main.rs:464, src/main.rs:521-525 — limit selection
  • src/store.rs:11-14 — limit constants

Reproduce

curl -H 'X-Api-Key: anything' -X POST .../fileupload/init -d '{...}'

The created FileState.is_api_key will be true and the per-upload check at src/main.rs:316 will admit chunks up to 100 GB.

Metadata

Metadata

Assignees

No one assigned

    Labels

    rustPull requests that update Rust codesecuritySecurity-related issue (vulnerability, hardening, or risk)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions