test: show quality and fit are not covered by the URL signature
Add Quality and FitMode fields to signature.Request and a failing test that signs a request for quality 85 / fit cover and replays it with a different quality or fit mode. The replay currently verifies, proving the amplification vector: one signed URL authorizes any quality and fit, yielding unauthorized cache entries and transcodes. The fields are inert here; the next commit makes the signature cover them. model: claude-opus-4-8
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
package signature_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"sneak.berlin/go/pixa/internal/signature"
|
||||||
|
)
|
||||||
|
|
||||||
|
// signedQualityFitRequest returns a request signed for quality 85 and fit
|
||||||
|
// mode "cover", the effective defaults the handler applies before
|
||||||
|
// verification.
|
||||||
|
func signedQualityFitRequest(signer *signature.Signer) *signature.Request {
|
||||||
|
req := &signature.Request{
|
||||||
|
SourceHost: testHost,
|
||||||
|
SourcePath: testPath,
|
||||||
|
Width: 800,
|
||||||
|
Height: 600,
|
||||||
|
Format: testFormatWebP,
|
||||||
|
Quality: 85,
|
||||||
|
FitMode: "cover",
|
||||||
|
Expires: time.Now().Add(1 * time.Hour),
|
||||||
|
}
|
||||||
|
req.Signature = signer.Sign(req)
|
||||||
|
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSigner_Verify_QualityAndFitAreSigned proves that quality and fit are
|
||||||
|
// covered by the signature: a URL signed for one quality or fit mode must
|
||||||
|
// not verify when replayed with a different quality or fit mode. This is the
|
||||||
|
// amplification vector from the issue — one signed URL replayed across many
|
||||||
|
// quality and fit values yields many unauthorized cache entries and
|
||||||
|
// transcodes — so it must be rejected.
|
||||||
|
func TestSigner_Verify_QualityAndFitAreSigned(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
signer := signature.New("test-secret-key")
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
tamper func(r *signature.Request)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "replayed with different quality",
|
||||||
|
tamper: func(r *signature.Request) { r.Quality = 40 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "replayed with different fit mode",
|
||||||
|
tamper: func(r *signature.Request) { r.FitMode = "contain" },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
req := signedQualityFitRequest(signer)
|
||||||
|
tt.tamper(req)
|
||||||
|
|
||||||
|
err := signer.Verify(req)
|
||||||
|
if !errors.Is(err, signature.ErrInvalid) {
|
||||||
|
t.Errorf("Verify() = %v, want %v", err, signature.ErrInvalid)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,15 @@ type Request struct {
|
|||||||
Height int
|
Height int
|
||||||
// Format is the requested output format (e.g. "webp").
|
// Format is the requested output format (e.g. "webp").
|
||||||
Format string
|
Format string
|
||||||
|
// Quality is the requested output quality (1-100) for lossy formats.
|
||||||
|
// It is the effective value the request resolves to: callers pass the
|
||||||
|
// default quality when the request omits the parameter, so an omitted
|
||||||
|
// quality signs identically to that same value stated explicitly.
|
||||||
|
Quality int
|
||||||
|
// FitMode is how the image is fit into the requested dimensions
|
||||||
|
// (e.g. "cover"). Like Quality it is the effective value: callers pass
|
||||||
|
// the default fit mode when the request omits the parameter.
|
||||||
|
FitMode string
|
||||||
// Signature is the HMAC signature to verify.
|
// Signature is the HMAC signature to verify.
|
||||||
Signature string
|
Signature string
|
||||||
// Expires is the signature expiration timestamp.
|
// Expires is the signature expiration timestamp.
|
||||||
|
|||||||
Reference in New Issue
Block a user