diff --git a/VERSION b/VERSION index 6e8bf73aa..d917d3e26 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0 +0.1.2 diff --git a/benchmarks/quality/quality.go b/benchmarks/quality/quality.go index e998e6f0b..b9d730fb6 100644 --- a/benchmarks/quality/quality.go +++ b/benchmarks/quality/quality.go @@ -75,7 +75,7 @@ type TierSummary struct { // LoadSamples reads a samples JSON file (the benchmarks/samples.json shape). func LoadSamples(path string) ([]Sample, error) { - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) // #nosec G304 -- path is a benchmark fixture path supplied by the harness caller (e.g. benchmarks/samples.json), not externally supplied if err != nil { return nil, fmt.Errorf("read samples: %w", err) } diff --git a/internal/cache/git_watcher.go b/internal/cache/git_watcher.go index b5d3ed701..6d6b470c0 100644 --- a/internal/cache/git_watcher.go +++ b/internal/cache/git_watcher.go @@ -223,7 +223,7 @@ func (w *GitWatcher) hashFiles(workingDir string, files []string) (map[string]st // hashFile hashes a single file's content using streaming func (w *GitWatcher) hashFile(path string) (string, error) { - f, err := os.Open(path) + f, err := os.Open(path) // #nosec G304 -- path is joined from the watcher's working directory and a git-reported repo-relative file path, not externally supplied if err != nil { return "", err } diff --git a/internal/cache/multilevel.go b/internal/cache/multilevel.go index 5eecfbe2c..eb524e525 100644 --- a/internal/cache/multilevel.go +++ b/internal/cache/multilevel.go @@ -52,7 +52,7 @@ func (mc *MultiLevelCache) Get(key string) (string, bool) { // L2: Disk hash := hashKey(key) path := filepath.Join(mc.l2Dir, hash) - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) // #nosec G304 -- path is a hash-derived filename under the cache's configured l2Dir, not externally supplied if err == nil { val := string(data) mc.promoteToL1(key, val) diff --git a/internal/cache/query_cache.go b/internal/cache/query_cache.go index fdf44c05b..69ad4d128 100644 --- a/internal/cache/query_cache.go +++ b/internal/cache/query_cache.go @@ -317,6 +317,7 @@ func (c *QueryCache) Invalidate(predicate func(*CacheEntry) bool) error { args[i] = key } + // #nosec G201 -- IN clause is placeholder tokens only; values parameterized query := fmt.Sprintf("DELETE FROM query_cache WHERE key IN (%s)", strings.Join(placeholders, ",")) _, err = c.db.ExecContext(context.Background(), query, args...) diff --git a/internal/config/config.go b/internal/config/config.go index 6299a0d50..fc1469a3b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -700,7 +700,7 @@ func (c *Config) Save(path string) (retErr error) { } // Create file with restrictive permissions (owner read/write only) - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600) + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600) // #nosec G304 -- path is the configuration file location supplied by the caller (CLI flag/default config path), not externally supplied if err != nil { return err } diff --git a/internal/core/estimator.go b/internal/core/estimator.go index 0fd062919..d46389f33 100644 --- a/internal/core/estimator.go +++ b/internal/core/estimator.go @@ -71,7 +71,7 @@ func (c *tokenCache) getShard(key uint64) *tokenCacheShard { func hashText(text string) uint64 { h := fnv.New64a() - h.Write([]byte(text)) + _, _ = h.Write([]byte(text)) // #nosec G104 -- hash.Hash.Write never returns an error return h.Sum64() } diff --git a/internal/core/runner.go b/internal/core/runner.go index c86866245..e0b4958bd 100644 --- a/internal/core/runner.go +++ b/internal/core/runner.go @@ -66,7 +66,7 @@ func (r *OSCommandRunner) Run(ctx context.Context, args []string) (string, int, return hint, 127, err } - cmd := exec.CommandContext(ctx, cmdPath, args[1:]...) + cmd := exec.CommandContext(ctx, cmdPath, args[1:]...) // #nosec G204 -- cmdPath resolved via validated exec.LookPath; not a shell, args passed directly cmd.Env = r.Env var buf bytes.Buffer diff --git a/internal/fastops/fastops_amd64.s b/internal/fastops/fastops_amd64.s index 645d0c3a5..cbe79dc37 100644 --- a/internal/fastops/fastops_amd64.s +++ b/internal/fastops/fastops_amd64.s @@ -10,9 +10,14 @@ TEXT ·hasANSIavx2(SB), NOSPLIT, $0-17 TESTQ CX, CX JEQ ansi_false - // Broadcast 0x1b to all 32 bytes of Y1 + // Broadcast 0x1b to all 32 bytes of Y1. VPBROADCASTB only has a + // GPR-source form under AVX-512 (EVEX-encoded); the AVX2 (VEX-encoded) + // form requires an XMM/memory source, so stage the byte through X1 + // first to avoid emitting an AVX-512 instruction that SIGILLs on + // AVX2-only hardware. MOVB $0x1b, R8 - VPBROADCASTB R8, Y1 + MOVQ R8, X1 + VPBROADCASTB X1, Y1 // Number of 32-byte chunks → byte offset MOVQ CX, AX @@ -70,8 +75,11 @@ TEXT ·countBytesAVX2(SB), NOSPLIT, $0-32 TESTQ CX, CX JEQ count_done - // Broadcast target byte to all 32 bytes of Y1 - VPBROADCASTB R8, Y1 + // Broadcast target byte to all 32 bytes of Y1 via an AVX2-safe (VEX, + // XMM-source) form; see hasANSIavx2 for why a direct GPR source is + // avoided. + MOVQ R8, X1 + VPBROADCASTB X1, Y1 MOVQ CX, AX SHRQ $5, AX // AX = floor(len / 32) diff --git a/internal/fastops/simd.go b/internal/fastops/simd.go index 7edf368fd..0b6099f20 100644 --- a/internal/fastops/simd.go +++ b/internal/fastops/simd.go @@ -179,7 +179,7 @@ func FastLower(s string) string { // safe: b is a private copy created above; no other reference to its // backing array exists and it is never mutated after this point, so // aliasing it as a string preserves string immutability. - return unsafe.String(unsafe.SliceData(b), len(b)) + return unsafe.String(unsafe.SliceData(b), len(b)) // #nosec G103 -- audited: b is a private copy exclusively owned here and never mutated after this point (see comment above) } // FastEqual compares strings with early exit diff --git a/internal/filter/dedup.go b/internal/filter/dedup.go index 1a4993c8e..af1d8ed97 100644 --- a/internal/filter/dedup.go +++ b/internal/filter/dedup.go @@ -15,10 +15,11 @@ func SimHash(content string) uint64 { for i := 0; i+2 < len(content); i++ { ngram := content[i : i+3] h := fnv.New64a() - h.Write([]byte(ngram)) + _, _ = h.Write([]byte(ngram)) // #nosec G104 -- hash.Hash.Write never returns an error hash := h.Sum64() for j := 0; j < 64; j++ { + // #nosec G115 -- j is a bounded non-negative loop index (0-63) if hash&(1< len(middle) { diff --git a/secrets_fuzz_test.go b/secrets_fuzz_test.go index 5f7536a61..addb6156b 100644 --- a/secrets_fuzz_test.go +++ b/secrets_fuzz_test.go @@ -1,12 +1,25 @@ package tok_test import ( + "regexp" "strings" "testing" "github.com/GrayCodeAI/tok" ) +// redactionMarkerPattern matches the "[REDACTED:]" markers RedactSecrets +// substitutes in place of detected secrets. +var redactionMarkerPattern = regexp.MustCompile(`\[REDACTED:[^\]]*\]`) + +// stripRedactionMarkers removes "[REDACTED:]" markers from s. Used to +// check for leaked secret values outside of the markers themselves, since a +// short captured value can coincidentally be a substring of a fixed type +// label (e.g. "Bearer Token") without that being an actual leak. +func stripRedactionMarkers(s string) string { + return redactionMarkerPattern.ReplaceAllString(s, "") +} + // FuzzRedactSecrets exercises the secrets redactor with arbitrary input. // // It asserts the security-relevant invariants that hold for the current @@ -71,11 +84,18 @@ func FuzzRedactSecrets(f *testing.F) { if !strings.Contains(out, "[REDACTED:") { t.Fatalf("redacted output carries no [REDACTED:] marker\ninput: %q\nout: %q", input, out) } + outsideMarkers := stripRedactionMarkers(out) for _, m := range matches { if len(m.Value) < 4 { continue } - if after, before := strings.Count(out, m.Value), strings.Count(input, m.Value); after >= before { + // Count occurrences outside of "[REDACTED:]" markers only: + // a short captured value can coincidentally be a substring of a + // *type label* (e.g. captured value "eare" is a substring of the + // fixed marker text "[REDACTED:Bearer Token]"), which is a + // spurious match against our own marker text, not a leak of the + // original secret. + if after, before := strings.Count(outsideMarkers, m.Value), strings.Count(input, m.Value); after >= before { t.Fatalf("detected secret value not reduced by redaction (before=%d after=%d)\ntype: %s\nvalue: %q\ninput: %q\nout: %q", before, after, m.Type, m.Value, input, out) }