updates
ober
6b2d9dd6893775ab9c1f409353c10e6503f06dd8
--- a/gitsafe/scanner.ss +++ b/gitsafe/scanner.ss @@ -257,6 +257,45 @@ #t] [else (loop (+ i 1))])))) + ;; Absolute filesystem paths often contain long alphanumeric directory + ;; components that fit the permissive base64 regex. They name locations, + ;; not credentials; precise credential patterns still run on these lines. + (def (in-absolute-path-context? line match-start) + (or (and (< match-start (string-length line)) + (char=? (string-ref line match-start) #\/)) + (let loop ([i (- match-start 1)]) + (cond + [(< i 0) #f] + [(char=? (string-ref line i) #\/) #t] + [(or (char=? (string-ref line i) #\space) + (char=? (string-ref line i) #\tab) + (char=? (string-ref line i) #\")) + #f] + [else (loop (- i 1))])))) + + ;; Documentation often lists identifiers as `a/b/c/.../`. A long list can + ;; resemble base64 only because the base64 alphabet admits slash; its + ;; repeated separators and trailing slash distinguish it from a token. + (def (slash-delimited-identifier-list? matched) + (and (string-suffix? "/" matched) + (not (string-contains matched "+")) + (not (string-contains matched "=")) + (>= (length (string-split matched #\/)) 5))) + + ;; Published digest test vectors are deterministic examples, not secrets. + ;; Keep this intentionally small: arbitrary hashes still receive entropy + ;; scanning unless another contextual suppression applies. + (def *well-known-hash-test-vectors* + '("d41d8cd98f00b204e9800998ecf8427e" + "900150983cd24fb0d6963f7d28e17f72" + "da39a3ee5e6b4b0d3255bfef95601890afd80709" + "a9993e364706816aba3e25717850c26c9cd0d89d" + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")) + + (def (well-known-hash-test-vector? matched) + (member matched *well-known-hash-test-vectors*)) + (def *git-sha-prefix-re* (re "(?i:(^|[^A-Za-z0-9_])(?:old\\s+head|new\\s+head|old|new|head|commit|revision|rev|object|tree|parent|merge|merged|checkout|reset|rebase|tag)[^A-Za-z0-9_]{0,24}$)")) @@ -615,6 +654,9 @@ file line (re-match-start m)))) (and (member pid *url-sensitive-patterns*) (or (in-url-context? line (re-match-start m)) + (in-absolute-path-context? line (re-match-start m)) + (slash-delimited-identifier-list? matched) + (well-known-hash-test-vector? matched) (in-digest-context? line (re-match-start m)) (in-git-sha-context? line (re-match-start m) matched) (in-dockerfile-from? line)))) --- a/test/test-gitsafe.ss +++ b/test/test-gitsafe.ss @@ -854,6 +854,38 @@ findings)]) (check-equal? '() entropy-findings))) + (test-case "scan-content: absolute filesystem paths are not entropy secrets" + (let* ([c (default-config)] + [findings (scan-content "handoff.md" + "smoke script in /var/folders/01/7797pkc13nq0x1fl7wxp1s0c0000gn/T/opencode/\n" + c)]) + (check-equal? '() (filter (lambda (f) + (member (finding-pattern-id f) + '(high-entropy-base64 high-entropy-hex))) + findings)))) + + (test-case "scan-content: slash-delimited documentation lists are not entropy secrets" + (let* ([c (default-config)] + [findings (scan-content "Project.md" + "extension/optimizer/memory/log/prepared/variable/secret/temp/cache/count/\n" + c)]) + (check-equal? '() (filter (lambda (f) + (member (finding-pattern-id f) + '(high-entropy-base64 high-entropy-hex))) + findings)))) + + (test-case "scan-content: standard digest test vectors are not entropy secrets" + (let* ([c (default-config)] + [findings (scan-content "digest-test.ss" + (string-append + "sha1 empty: da39a3ee5e6b4b0d3255bfef95601890afd80709\n" + "sha256 abc: ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\n") + c)]) + (check-equal? '() (filter (lambda (f) + (member (finding-pattern-id f) + '(high-entropy-base64 high-entropy-hex))) + findings)))) + (test-case ".gitsafeignore ** does not skip files (integration)" ;; A hostile in-repo .gitsafeignore containing "**" must not cause all ;; files to be skipped; the secret is still scanned and reported.