package scanner import ( "strings" "testing" ) func TestParseDigestAcceptsOnlyAlgorithmAndHex(t *testing.T) { valid := "sha256:" + strings.Repeat("ab", 32) d, err := ParseDigest(valid) if err != nil { t.Fatalf("ParseDigest(%q) = %v, want it accepted", valid, err) } if d.Algorithm != "sha256" { t.Errorf("Algorithm = %q, want sha256", d.Algorithm) } if d.Hex != strings.Repeat("ab", 32) { t.Errorf("Hex = %q, want the 64 hex characters", d.Hex) } if got := d.String(); got != valid { t.Errorf("String() = %q, want %q", got, valid) } } // TestParseDigestRejectsEverythingElse is the security boundary: anything that // is not exactly "sha256:<64 lowercase hex>" must be refused before it can be // joined onto a filesystem path or sent to the hold as a blob name. func TestParseDigestRejectsEverythingElse(t *testing.T) { hex64 := strings.Repeat("ab", 32) cases := []struct { name string digest string }{ {"empty", ""}, {"no algorithm prefix", hex64}, {"path traversal in the hex", "sha256:../../../escaped-marker"}, {"traversal with no algorithm", "../../../escaped-marker"}, {"absolute path", "sha256:/etc/passwd"}, {"separator in the hex", "sha256:ab/cd"}, {"null byte", "sha256:" + hex64 + "\x00"}, {"unsupported algorithm", "sha512:" + strings.Repeat("cd", 64)}, {"uppercase hex", "sha256:" + strings.ToUpper(hex64)}, {"short hex", "sha256:abcd"}, {"long hex", "sha256:" + hex64 + "ab"}, {"non-hex characters", "sha256:" + strings.Repeat("zz", 32)}, {"empty hex", "sha256:"}, {"empty algorithm", ":" + hex64}, {"double colon", "sha256:sha256:" + hex64}, {"leading space", " sha256:" + hex64}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if d, err := ParseDigest(tc.digest); err == nil { t.Errorf("ParseDigest(%q) accepted it as %+v, want an error", tc.digest, d) } }) } }