{"record":{"id":"0c8a7461cdb2d13f","repo":"EpicGames/lore","slug":"presigned-url-hmac-key-must-be-at-least-min-hmac-key-bytes","errorCode":null,"errorMessage":"presigned_url_hmac_key must be at least {MIN_HMAC_KEY_BYTES} bytes, got {}","messagePattern":"presigned_url_hmac_key must be at least (.+?) bytes, got (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"lore-server/src/http/server.rs","lineNumber":207,"sourceCode":"/// Renders the resolved allowlist for the startup log.\nfn describe_allowed_types(types: &[String]) -> String {\n    if types.is_empty() {\n        \"<none>\".to_string()\n    } else {\n        types.join(\", \")\n    }\n}\n\nfn build_presign_config(settings: &PresignSettings) -> Result<Option<PresignConfig>> {\n    let Some(key_hex) = settings.hmac_key.as_deref() else {\n        return Ok(None);\n    };\n\n    let key_bytes = hex::decode(key_hex)\n        .map_err(|e| anyhow::anyhow!(\"presigned_url_hmac_key is not valid hex: {e}\"))?;\n\n    if key_bytes.len() < MIN_HMAC_KEY_BYTES {\n        anyhow::bail!(\n            \"presigned_url_hmac_key must be at least {MIN_HMAC_KEY_BYTES} bytes, got {}\",\n            key_bytes.len()\n        );\n    }\n\n    let key_id = blake3::hash(&key_bytes).to_hex()[..16].to_string();\n    let hmac_key = hmac::Key::new(hmac::HMAC_SHA256, &key_bytes);\n\n    Ok(Some(PresignConfig {\n        hmac_key,\n        key_id,\n        min_ttl_seconds: settings.min_ttl_seconds,\n        default_ttl_seconds: settings.default_ttl_seconds,\n        max_ttl_seconds: settings.max_ttl_seconds,\n        content_type_allowlist: ContentTypeAllowlist::try_from_policy(\n            &settings.content_type_policy,\n        )\n        .map_err(|err| anyhow!(\"{} {err}\", presign_content_type_field(err.field())))?,","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/EpicGames/lore/blob/074eb0b0d1194c997d7cf28b55519e3e197b3e23/lore-server/src/http/server.rs#L189-L225","documentation":"After successfully hex-decoding presigned_url_hmac_key, build_presign_config checks the decoded byte length against MIN_HMAC_KEY_BYTES and bails if the key is too short. Weak HMAC keys would allow presigned-URL signature forgery, so the server refuses to start.","triggerScenarios":"PresignSettings.hmac_key decodes to fewer than MIN_HMAC_KEY_BYTES bytes, e.g. hex string of a 16-byte key passed where 32 bytes are required.","commonSituations":"Developers use a short test secret like \"deadbeef\"; a legacy deployment predates the minimum-length requirement; someone truncated the key during copy-paste.","solutions":["Generate a longer key: `openssl rand -hex 32` and update the config.","Check the decoded length: (hex_string.len() / 2) must be >= MIN_HMAC_KEY_BYTES.","Rotate the short key in the upstream secret store and redeploy with the new value."],"exampleFix":"// before\npresigned_url_hmac_key = \"00112233445566778899aabbccddeeff\" // 16 bytes\n// after\npresigned_url_hmac_key = \"<output of: openssl rand -hex 32>\" // 32 bytes","handlingStrategy":"validation","validationCode":"const MIN_HMAC_KEY_BYTES: usize = 32;\nfn key_is_long_enough(hex_key: &str) -> bool {\n    hex::decode(hex_key).map(|b| b.len() >= MIN_HMAC_KEY_BYTES).unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always generate 256-bit (32-byte) HMAC keys; never reuse short test secrets in prod.","Add a config lint that checks decoded key length before deploy.","Keep keys in a secret manager and rotate to new full-length values."],"tags":["config","hmac","weak-key","startup"],"backgroundTag":"value-out-of-range","analyzedSha":"074eb0b0d1194c997d7cf28b55519e3e197b3e23","analyzedAt":"2026-09-13T09:00:57.509Z","contentChangedAt":"2026-09-13T09:00:57.509Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}