Summary
SignatureValidatingAuthorizer accepts a signature over only
SIGNING_CONSTANT || pubkey || unix_time. The proof is not bound to the HTTP
method, path, body, store_id, or a server nonce, and remains valid for up to
24 hours in either direction.
As a result, a captured Authorization header acts as a replayable bearer
credential for every operation belonging to that public key.
Observed on 88a5703496386465556d920dccf49512296c53d0 (current main).
Impact
Anyone who obtains one valid header can replay it to read, modify, or delete
the victim tenant's VSS state during the validity window. Header capture is
especially plausible when the service is exposed directly over its cleartext
HTTP listener.
Code evidence
auth-impls/src/signature.rs:64-86 checks only the timestamp and an ECDSA
signature over the constant, public key, and timestamp.
verify receives only a header map, so it cannot bind the proof to request
method, path, or body.
- The resulting
user_token is the public key hex string.
Proof of concept
Using the existing build_token helper in auth-impls/src/signature.rs, this
minimal test demonstrates that the same credential is accepted repeatedly:
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
let (token, _) = build_token(now);
let mut headers = HashMap::new();
headers.insert("authorization".to_string(), token);
let auth = SignatureValidatingAuthorizer;
assert!(auth.verify(&headers).await.is_ok());
assert!(auth.verify(&headers).await.is_ok());
Because no request data is passed to verify, the two accepted uses can be
for different VSS routes and bodies.
Suggested remediation
- Bind signatures to the method, canonical path, body hash, and tenant/store
context.
- Add a server-issued, single-use nonce with a short TTL.
- Reduce the validity window and require TLS for authenticated traffic.
Reported by Bitcoin Red Team.
Summary
SignatureValidatingAuthorizeraccepts a signature over onlySIGNING_CONSTANT || pubkey || unix_time. The proof is not bound to the HTTPmethod, path, body,
store_id, or a server nonce, and remains valid for up to24 hours in either direction.
As a result, a captured
Authorizationheader acts as a replayable bearercredential for every operation belonging to that public key.
Observed on
88a5703496386465556d920dccf49512296c53d0(currentmain).Impact
Anyone who obtains one valid header can replay it to read, modify, or delete
the victim tenant's VSS state during the validity window. Header capture is
especially plausible when the service is exposed directly over its cleartext
HTTP listener.
Code evidence
auth-impls/src/signature.rs:64-86checks only the timestamp and an ECDSAsignature over the constant, public key, and timestamp.
verifyreceives only a header map, so it cannot bind the proof to requestmethod, path, or body.
user_tokenis the public key hex string.Proof of concept
Using the existing
build_tokenhelper inauth-impls/src/signature.rs, thisminimal test demonstrates that the same credential is accepted repeatedly:
Because no request data is passed to
verify, the two accepted uses can befor different VSS routes and bodies.
Suggested remediation
context.
Reported by Bitcoin Red Team.