Files
certctl/internal/validation/unicode_test.go
T
Shankar 1d0733f170 Bundle 9 follow-up: ST1018 ESC sweep + make verify pre-commit gate
CI on the bundle-9 merge (run #24962543332) failed golangci-lint with 16
staticcheck ST1018 'string literal contains the Unicode format character
U+202X, consider using the \u202X escape sequence' hits — across the
two test files we added (internal/validation/unicode_test.go +
internal/connector/issuer/local/bundle9_coverage_test.go).

Mechanical sweep, byte-identical at runtime:

  internal/validation/unicode_test.go (13 + 1 hits cleared)
    RTL/LTR overrides U+202A..U+202E + U+2066..U+2069 (lines 39-47)
    zero-width U+200B..U+200D + U+2060 (lines 67-70)
    additional U+202E in TestValidateUnicodeSafe_ErrorMentionsByteOffset

  internal/connector/issuer/local/bundle9_coverage_test.go (3 hits)
    U+202E in TestValidateCSRUnicode_RejectsDNSNameRTL
    U+200B in TestValidateCSRUnicode_RejectsEmailZeroWidth
    U+202E in TestValidateCSRUnicode_RejectsAdditionalSAN

The strings now use Go \uXXXX escape sequences. Identical UTF-8 bytes
hit ValidateUnicodeSafe at runtime — every test passes unchanged
locally. The file-header comment in unicode_test.go that promised this
convention is now actually honored.

Verification: staticcheck -checks=ST1018 returns clean across the two
packages. go test -count=1 -short still green.

Pre-commit gate added to prevent recurrence:

  Makefile: new 'verify' aggregate target runs gofmt + go vet +
    golangci-lint run + go test -short — same set CI enforces. Run
    'make verify' before every commit going forward.

  cowork/CLAUDE.md: new 'Pre-commit verification gate' paragraph in
    Operating Rules. Documents make verify as the canonical gate;
    explains WHY (Bundle-9 shipped green-on-vet / red-on-CI because
    ST1018 only fires under golangci-lint's staticcheck, not vet);
    documents the staticcheck-only fallback for disk-constrained
    sandboxes.

This commit changes only:
  - 2 test source files (\uXXXX escapes, no behavior change)
  - Makefile (1 new target, 1 .PHONY entry, 1 help line)
  - cowork/CLAUDE.md (1 new operating-rule paragraph)
2026-04-26 21:17:12 +00:00

159 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package validation
import (
"strings"
"testing"
)
// Bundle-9 / Audit L-012 / CWE-1007 + CWE-176 regression suite.
//
// Note: invisible / formatting characters in test inputs are written as
// \uXXXX escape sequences (NOT literal codepoints) so the source file
// stays parseable + readable. Literal BOM / RTL-override bytes inside
// a Go string literal trip the parser ("illegal byte order mark").
func TestValidateUnicodeSafe_AcceptsCleanASCII(t *testing.T) {
cases := []string{
"example.com",
"api.example.com",
"sub-domain.example.co.uk",
"a.b.c.d.example.org",
"localhost",
"192.168.1.1",
"",
}
for _, c := range cases {
t.Run(c, func(t *testing.T) {
if err := ValidateUnicodeSafe(c); err != nil {
t.Errorf("clean ASCII %q rejected: %v", c, err)
}
})
}
}
func TestValidateUnicodeSafe_RejectsRTLOverride(t *testing.T) {
cases := []struct {
name string
in string
}{
{"LRE", "good\u202Acom"},
{"RLE", "good\u202Bcom"},
{"PDF", "good\u202Ccom"},
{"LRO", "good\u202Dcom"},
{"RLO", "good\u202Ecom"},
{"LRI", "good\u2066com"},
{"RLI", "good\u2067com"},
{"FSI", "good\u2068com"},
{"PDI", "good\u2069com"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
err := ValidateUnicodeSafe(c.in)
if err == nil {
t.Fatal("expected rejection")
}
if !strings.Contains(err.Error(), "bidirectional override") {
t.Errorf("error should cite bidirectional override; got: %v", err)
}
})
}
}
func TestValidateUnicodeSafe_RejectsZeroWidth(t *testing.T) {
cases := []struct {
name string
in string
}{
{"ZWSP", "good\u200Bcom"},
{"ZWNJ", "good\u200Ccom"},
{"ZWJ", "good\u200Dcom"},
{"WJ", "good\u2060com"},
{"BOM", "good\uFEFFcom"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
err := ValidateUnicodeSafe(c.in)
if err == nil {
t.Fatal("expected rejection")
}
if !strings.Contains(err.Error(), "zero-width") {
t.Errorf("error should cite zero-width; got: %v", err)
}
})
}
}
func TestValidateUnicodeSafe_RejectsControlChars(t *testing.T) {
cases := []struct {
name string
in string
}{
{"NUL", "good\x00com"},
{"TAB", "good\tcom"},
{"LF", "good\ncom"},
{"CR", "good\rcom"},
{"DEL", "good\x7Fcom"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
err := ValidateUnicodeSafe(c.in)
if err == nil {
t.Fatal("expected rejection")
}
if !strings.Contains(err.Error(), "control character") {
t.Errorf("error should cite control character; got: %v", err)
}
})
}
}
func TestValidateUnicodeSafe_RejectsIDNHomograph(t *testing.T) {
// Cyrillic 'а' (U+0430) inside an otherwise-Latin label — visually
// identical to Latin 'a' but a different codepoint. Classic homograph.
cases := []struct {
name string
in string
}{
{"cyrillic_a_in_apple", "аpple.com"},
{"greek_omicron_in_google", "gοogle.com"},
{"cherokee_letter", "gᏇogle.com"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
err := ValidateUnicodeSafe(c.in)
if err == nil {
t.Fatal("expected rejection")
}
if !strings.Contains(err.Error(), "IDN homograph") {
t.Errorf("error should cite IDN homograph; got: %v", err)
}
})
}
}
func TestValidateUnicodeSafe_AcceptsPureNonASCII(t *testing.T) {
// A fully-Cyrillic label is a legitimate IDN — don't reject. The
// homograph attack we're defending against is the MIX with ASCII.
in := "пример.рф"
if err := ValidateUnicodeSafe(in); err != nil {
t.Errorf("pure-Cyrillic label rejected: %v", err)
}
}
func TestValidateUnicodeSafe_ErrorMentionsByteOffset(t *testing.T) {
in := "good\u202Eevil.com"
err := ValidateUnicodeSafe(in)
if err == nil {
t.Fatal("expected rejection")
}
if !strings.Contains(err.Error(), "byte offset") {
t.Errorf("error should cite byte offset; got: %v", err)
}
}
func TestValidateUnicodeSafe_EmptyStringPasses(t *testing.T) {
if err := ValidateUnicodeSafe(""); err != nil {
t.Errorf("empty string should pass through (different validator handles required); got: %v", err)
}
}