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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user