security: make .semgrepignore loading opt-in and reported
ober
ad463b98b03b4edf8f522ff3e9410b8c4a268d82
--- a/lib/semgrep/cli.sls +++ b/lib/semgrep/cli.sls @@ -32,10 +32,12 @@ target)) (def (usage) (display - "usage: semgrep scan --config RULES.yml [--lang LANG] [--severity LEVEL] [--include GLOB] [--exclude GLOB] TARGET\n") + "usage: semgrep scan --config RULES.yml [--lang LANG] [--severity LEVEL] [--include GLOB] [--exclude GLOB] [--use-semgrepignore] TARGET\n") (display "\n") (display - "MVP support: YAML configs with pattern/pattern-regex rules; json/python/javascript/dockerfile/html/xml/yaml/c/terraform/php/java/csharp/swift/dart/move/julia/kotlin/ruby/rust/go/scala/cpp targets.\n")) + "MVP support: YAML configs with pattern/pattern-regex rules; json/python/javascript/dockerfile/html/xml/yaml/c/terraform/php/java/csharp/swift/dart/move/julia/kotlin/ruby/rust/go/scala/cpp targets.\n") + (display + "Ignore rules from a scanned tree's .semgrepignore are untrusted and only honored with --use-semgrepignore.\n")) (def (parse-args args) (let loop ([xs args] [config #f] @@ -43,67 +45,72 @@ [format 'json] [severities '()] [autofix? #f] + [use-ignore? #f] [includes '()] [excludes '()] [targets '()]) (cond [(null? xs) (values config language format (reverse severities) autofix? - (reverse includes) (reverse excludes) (reverse targets))] + use-ignore? (reverse includes) (reverse excludes) + (reverse targets))] [(string=? (car xs) "--") (values config language format (reverse severities) autofix? - (reverse includes) (reverse excludes) + use-ignore? (reverse includes) (reverse excludes) (append (reverse targets) (cdr xs)))] [(string=? (car xs) "--json") (loop (cdr xs) config language 'json severities autofix? - includes excludes targets)] + use-ignore? includes excludes targets)] [(string=? (car xs) "--sarif") (loop (cdr xs) config language 'sarif severities autofix? - includes excludes targets)] + use-ignore? includes excludes targets)] [(string=? (car xs) "--text") (loop (cdr xs) config language 'text severities autofix? - includes excludes targets)] + use-ignore? includes excludes targets)] [(string=? (car xs) "--autofix") - (loop (cdr xs) config language format severities #t includes - excludes targets)] + (loop (cdr xs) config language format severities #t + use-ignore? includes excludes targets)] + [(string=? (car xs) "--use-semgrepignore") + (loop (cdr xs) config language format severities autofix? #t + includes excludes targets)] [(string=? (car xs) "--config") (when (null? (cdr xs)) (error 'semgrep-cli "--config needs a value")) (loop (cddr xs) (cadr xs) language format severities - autofix? includes excludes targets)] + autofix? use-ignore? includes excludes targets)] [(string=? (car xs) "-c") (when (null? (cdr xs)) (error 'semgrep-cli "-c needs a value")) (loop (cddr xs) (cadr xs) language format severities - autofix? includes excludes targets)] + autofix? use-ignore? includes excludes targets)] [(string=? (car xs) "--lang") (when (null? (cdr xs)) (error 'semgrep-cli "--lang needs a value")) (loop (cddr xs) config (cadr xs) format severities autofix? - includes excludes targets)] + use-ignore? includes excludes targets)] [(string=? (car xs) "--severity") (when (null? (cdr xs)) (error 'semgrep-cli "--severity needs a value")) (loop (cddr xs) config language format - (cons (cadr xs) severities) autofix? includes excludes - targets)] + (cons (cadr xs) severities) autofix? use-ignore? includes + excludes targets)] [(string=? (car xs) "--include") (when (null? (cdr xs)) (error 'semgrep-cli "--include needs a value")) (loop (cddr xs) config language format severities autofix? - (cons (cadr xs) includes) excludes targets)] + use-ignore? (cons (cadr xs) includes) excludes targets)] [(string=? (car xs) "--exclude") (when (null? (cdr xs)) (error 'semgrep-cli "--exclude needs a value")) (loop (cddr xs) config language format severities autofix? - includes (cons (cadr xs) excludes) targets)] + use-ignore? includes (cons (cadr xs) excludes) targets)] [(and (> (string-length (car xs)) 0) (not (string=? (car xs) "-")) (char=? (string-ref (car xs) 0) #\-)) (error 'semgrep-cli "unknown option" (car xs))] [else (loop (cdr xs) config language format severities autofix? - includes excludes (cons (car xs) targets))]))) + use-ignore? includes excludes (cons (car xs) targets))]))) (def (format-findings format findings) (case format [(json) (findings->json-string findings)] @@ -179,6 +186,21 @@ [(ignore-line? (car lines)) (loop (cdr lines) acc)] [else (loop (cdr lines) (cons (car lines) acc))])) '()))) + (def (report-ignored-paths dir paths) + (let ([port (current-error-port)]) + (display "semgrep: .semgrepignore (opt-in) ignored " port) + (display (length paths) port) + (display " path(s) under " port) + (display dir port) + (display ":" port) + (newline port) + (for-each + (lambda (path) + (display " " port) + (display path port) + (newline port)) + paths) + (flush-output-port port))) (def (ignore-pattern-matches? pattern relative name) (or (glob-match? pattern relative) (and (not (sg-string-contains-char? pattern #\/)) @@ -244,86 +266,130 @@ (let loop ([xs seen]) (and (not (null? xs)) (or (equal? key (car xs)) (loop (cdr xs)))))) - (def (expand-open-directory-target language-opt dir root) - (let ([ignore-patterns (read-semgrepignore dir)] + (def (expand-open-directory-target + language-opt + dir + root + use-ignore?) + (let ([ignore-patterns (if use-ignore? + (read-semgrepignore dir) + '())] [entry-count 0] [path-char-count 0] + [ignored-paths '()] [seen '()]) - (let walk ([relative-dir ""] [depth 0] [acc '()]) - (when (> depth max-recursive-depth) - (error 'semgrep-cli - "recursive target exceeds depth limit" - max-recursive-depth)) - (let* ([directory-info (secure-entry-info - (cli-root-handle root) - relative-dir)] - [key (identity-key directory-info)]) - (unless (eq? (secure-entry-kind directory-info) 'directory) - (error 'semgrep-cli - "recursive target changed type" - relative-dir)) - (when (identity-seen? key seen) - (error 'semgrep-cli - "recursive target contains a directory cycle" - relative-dir)) - (set! seen (cons key seen)) - (let loop ([entries (secure-directory-list - (cli-root-handle root) - relative-dir - max-directory-entries - max-directory-list-bytes)] - [current-acc acc]) - (if (null? entries) - current-acc - (let* ([name (car entries)] - [relative (if (string=? relative-dir "") - name - (sg-path-join relative-dir name))] - [display (sg-path-join dir relative)]) - (set! entry-count (+ entry-count 1)) - (set! path-char-count - (+ path-char-count (string-length relative))) - (when (> entry-count max-recursive-entries) - (error 'semgrep-cli - "recursive target exceeds entry limit" - max-recursive-entries)) - (when (> path-char-count max-recursive-path-chars) - (error 'semgrep-cli - "recursive target exceeds path-byte budget" - max-recursive-path-chars)) - (if (ignored-path? ignore-patterns relative name) - (loop (cdr entries) current-acc) - (let ([info (secure-entry-info - (cli-root-handle root) - relative)]) - (case (secure-entry-kind info) - [(symlink) (loop (cdr entries) current-acc)] - [(directory) - (if (skip-directory-name? name) - (loop (cdr entries) current-acc) - (loop - (cdr entries) - (walk - relative - (+ depth 1) - current-acc)))] - [(file) - (when (> (secure-entry-links info) 1) - (error 'semgrep-cli - "recursive target contains a hard-linked file" - display)) - (loop - (cdr entries) - (if (scannable-file? language-opt display) - (cons - (make-cli-target - root - relative - display) - current-acc) - current-acc))] - [else - (loop (cdr entries) current-acc)])))))))))) + (let ([expanded (let walk ([relative-dir ""] + [depth 0] + [acc '()]) + (when (> depth max-recursive-depth) + (error 'semgrep-cli + "recursive target exceeds depth limit" + max-recursive-depth)) + (let* ([directory-info (secure-entry-info + (cli-root-handle root) + relative-dir)] + [key (identity-key directory-info)]) + (unless (eq? (secure-entry-kind + directory-info) + 'directory) + (error 'semgrep-cli + "recursive target changed type" + relative-dir)) + (when (identity-seen? key seen) + (error 'semgrep-cli + "recursive target contains a directory cycle" + relative-dir)) + (set! seen (cons key seen)) + (let loop ([entries (secure-directory-list + (cli-root-handle root) + relative-dir + max-directory-entries + max-directory-list-bytes)] + [current-acc acc]) + (if (null? entries) + current-acc + (let* ([name (car entries)] + [relative (if (string=? + relative-dir + "") + name + (sg-path-join + relative-dir + name))] + [display (sg-path-join + dir + relative)]) + (set! entry-count (+ entry-count 1)) + (set! path-char-count + (+ path-char-count + (string-length relative))) + (when (> entry-count + max-recursive-entries) + (error 'semgrep-cli + "recursive target exceeds entry limit" + max-recursive-entries)) + (when (> path-char-count + max-recursive-path-chars) + (error 'semgrep-cli + "recursive target exceeds path-byte budget" + max-recursive-path-chars)) + (if (ignored-path? + ignore-patterns + relative + name) + (begin + (set! ignored-paths + (cons display ignored-paths)) + (loop + (cdr entries) + current-acc)) + (let ([info (secure-entry-info + (cli-root-handle + root) + relative)]) + (case (secure-entry-kind info) + [(symlink) + (loop + (cdr entries) + current-acc)] + [(directory) + (if (skip-directory-name? + name) + (loop + (cdr entries) + current-acc) + (loop + (cdr entries) + (walk + relative + (+ depth 1) + current-acc)))] + [(file) + (when (> (secure-entry-links + info) + 1) + (error 'semgrep-cli + "recursive target contains a hard-linked file" + display)) + (loop + (cdr entries) + (if (scannable-file? + language-opt + display) + (cons + (make-cli-target + root + relative + display) + current-acc) + current-acc))] + [else + (loop + (cdr entries) + current-acc)]))))))))]) + (when (and use-ignore? (pair? ignored-paths)) + (report-ignored-paths dir (reverse ignored-paths))) + expanded))) (def (expand-file-target target) (let-values ([(parent name) (path-parent-and-name target)]) (let ([handle (secure-directory-open parent #f)]) @@ -342,7 +408,7 @@ (values (list (make-cli-target root name target)) (list root))))))) - (def (expand-target language-opt target) + (def (expand-target language-opt use-ignore? target) (if (string=? target "-") (values (list target) '()) (guard (directory-error [#t (expand-file-target target)]) @@ -357,9 +423,11 @@ (expand-open-directory-target language-opt target - root)) + root + use-ignore?)) (list root))))))) - (def (expand-targets language-opt includes excludes targets) + (def (expand-targets language-opt use-ignore? includes + excludes targets) (let ([opened-roots '()]) (guard (failure [#t @@ -382,6 +450,7 @@ (let-values ([(next-targets next-roots) (expand-target language-opt + use-ignore? (car remaining))]) (set! opened-roots (append next-roots opened-roots)) (loop @@ -493,7 +562,7 @@ (member "--help" args) (member "-h" args)) (begin (usage) 0) - (let-values ([(config language format severities autofix? includes excludes targets) + (let-values ([(config language format severities autofix? use-ignore? includes excludes targets) (parse-args args)]) (unless config (usage) @@ -503,11 +572,8 @@ (error 'semgrep-cli "missing target")) (let ([rules (parse-config-file config)]) (let-values ([(expanded-targets roots) - (expand-targets - language - includes - excludes - targets)]) + (expand-targets language use-ignore? + includes excludes targets)]) (dynamic-wind (lambda () (void)) (lambda () --- a/src/.jerbuild-hashes +++ b/src/.jerbuild-hashes @@ -34,7 +34,7 @@ . "285FDB423AD7E28C") ("src/semgrep/result/builders.ss" . "93A64AF4435E6132") - ("src/semgrep/cli.ss" . "DF74D39C29AC0141") + ("src/semgrep/cli.ss" . "6145E877B2DA1583") ("src/semgrep/output/sarif.ss" . "740FF3708E6C1BB") ("src/semgrep/engine/js-vardef-scan.ss" . --- a/src/semgrep/cli.ss +++ b/src/semgrep/cli.ss @@ -30,9 +30,10 @@ (if (cli-target? target) (cli-target-display target) target)) (def (usage) - (display "usage: semgrep scan --config RULES.yml [--lang LANG] [--severity LEVEL] [--include GLOB] [--exclude GLOB] TARGET\n") + (display "usage: semgrep scan --config RULES.yml [--lang LANG] [--severity LEVEL] [--include GLOB] [--exclude GLOB] [--use-semgrepignore] TARGET\n") (display "\n") - (display "MVP support: YAML configs with pattern/pattern-regex rules; json/python/javascript/dockerfile/html/xml/yaml/c/terraform/php/java/csharp/swift/dart/move/julia/kotlin/ruby/rust/go/scala/cpp targets.\n")) + (display "MVP support: YAML configs with pattern/pattern-regex rules; json/python/javascript/dockerfile/html/xml/yaml/c/terraform/php/java/csharp/swift/dart/move/julia/kotlin/ruby/rust/go/scala/cpp targets.\n") + (display "Ignore rules from a scanned tree's .semgrepignore are untrusted and only honored with --use-semgrepignore.\n")) (def (parse-args args) (let loop ([xs args] @@ -41,6 +42,7 @@ [format 'json] [severities '()] [autofix? #f] + [use-ignore? #f] [includes '()] [excludes '()] [targets '()]) @@ -51,82 +53,90 @@ format (reverse severities) autofix? + use-ignore? (reverse includes) (reverse excludes) (reverse targets))] [(string=? (car xs) "--") - (values config - language - format - (reverse severities) - autofix? - (reverse includes) - (reverse excludes) - (append (reverse targets) (cdr xs)))] + (values config + language + format + (reverse severities) + autofix? + use-ignore? + (reverse includes) + (reverse excludes) + (append (reverse targets) (cdr xs)))] [(string=? (car xs) "--json") - (loop (cdr xs) config language 'json severities autofix? includes excludes targets)] + (loop (cdr xs) config language 'json severities autofix? use-ignore? includes excludes targets)] [(string=? (car xs) "--sarif") - (loop (cdr xs) config language 'sarif severities autofix? includes excludes targets)] + (loop (cdr xs) config language 'sarif severities autofix? use-ignore? includes excludes targets)] [(string=? (car xs) "--text") - (loop (cdr xs) config language 'text severities autofix? includes excludes targets)] + (loop (cdr xs) config language 'text severities autofix? use-ignore? includes excludes targets)] [(string=? (car xs) "--autofix") - (loop (cdr xs) config language format severities #t includes excludes targets)] + (loop (cdr xs) config language format severities #t use-ignore? includes excludes targets)] + [(string=? (car xs) "--use-semgrepignore") + (loop (cdr xs) config language format severities autofix? #t includes excludes targets)] [(string=? (car xs) "--config") - (when (null? (cdr xs)) (error 'semgrep-cli "--config needs a value")) - (loop (cddr xs) (cadr xs) language format severities autofix? includes excludes targets)] + (when (null? (cdr xs)) (error 'semgrep-cli "--config needs a value")) + (loop (cddr xs) (cadr xs) language format severities autofix? use-ignore? includes excludes targets)] [(string=? (car xs) "-c") - (when (null? (cdr xs)) (error 'semgrep-cli "-c needs a value")) - (loop (cddr xs) (cadr xs) language format severities autofix? includes excludes targets)] + (when (null? (cdr xs)) (error 'semgrep-cli "-c needs a value")) + (loop (cddr xs) (cadr xs) language format severities autofix? use-ignore? includes excludes targets)] [(string=? (car xs) "--lang") - (when (null? (cdr xs)) (error 'semgrep-cli "--lang needs a value")) - (loop (cddr xs) config (cadr xs) format severities autofix? includes excludes targets)] + (when (null? (cdr xs)) (error 'semgrep-cli "--lang needs a value")) + (loop (cddr xs) config (cadr xs) format severities autofix? use-ignore? includes excludes targets)] [(string=? (car xs) "--severity") - (when (null? (cdr xs)) (error 'semgrep-cli "--severity needs a value")) - (loop (cddr xs) - config - language - format - (cons (cadr xs) severities) - autofix? - includes - excludes - targets)] + (when (null? (cdr xs)) (error 'semgrep-cli "--severity needs a value")) + (loop (cddr xs) + config + language + format + (cons (cadr xs) severities) + autofix? + use-ignore? + includes + excludes + targets)] [(string=? (car xs) "--include") - (when (null? (cdr xs)) (error 'semgrep-cli "--include needs a value")) - (loop (cddr xs) - config - language - format - severities - autofix? - (cons (cadr xs) includes) - excludes - targets)] + (when (null? (cdr xs)) (error 'semgrep-cli "--include needs a value")) + (loop (cddr xs) + config + language + format + severities + autofix? + use-ignore? + (cons (cadr xs) includes) + excludes + targets)] [(string=? (car xs) "--exclude") - (when (null? (cdr xs)) (error 'semgrep-cli "--exclude needs a value")) - (loop (cddr xs) - config - language - format - severities - autofix? - includes - (cons (cadr xs) excludes) - targets)] + (when (null? (cdr xs)) (error 'semgrep-cli "--exclude needs a value")) + (loop (cddr xs) + config + language + format + severities + autofix? + use-ignore? + includes + (cons (cadr xs) excludes) + targets)] [(and (> (string-length (car xs)) 0) (not (string=? (car xs) "-")) (char=? (string-ref (car xs) 0) #\-)) - (error 'semgrep-cli "unknown option" (car xs))] + (error 'semgrep-cli "unknown option" (car xs))] [else - (loop (cdr xs) - config - language - format - severities - autofix? - includes - excludes - (cons (car xs) targets))]))) + (loop (cdr xs) + config + language + format + severities + autofix? + use-ignore? + includes + excludes + (cons (car xs) targets))]))) (def (format-findings format findings) (case format @@ -207,6 +217,21 @@ [else (loop (cdr lines) (cons (car lines) acc))])) '()))) +(def (report-ignored-paths dir paths) + (let ([port (current-error-port)]) + (display "semgrep: .semgrepignore (opt-in) ignored " port) + (display (length paths) port) + (display " path(s) under " port) + (display dir port) + (display ":" port) + (newline port) + (for-each (lambda (path) + (display " " port) + (display path port) + (newline port)) + paths) + (flush-output-port port))) + (def (ignore-pattern-matches? pattern relative name) (or (glob-match? pattern relative) (and (not (sg-string-contains-char? pattern #\/)) @@ -283,70 +308,77 @@ (or (equal? key (car xs)) (loop (cdr xs)))))) -(def (expand-open-directory-target language-opt dir root) - (let ([ignore-patterns (read-semgrepignore dir)] +(def (expand-open-directory-target language-opt dir root use-ignore?) + (let ([ignore-patterns (if use-ignore? (read-semgrepignore dir) '())] [entry-count 0] [path-char-count 0] + [ignored-paths '()] [seen '()]) - (let walk ([relative-dir ""] [depth 0] [acc '()]) - (when (> depth max-recursive-depth) - (error 'semgrep-cli "recursive target exceeds depth limit" - max-recursive-depth)) - (let* ([directory-info - (secure-entry-info (cli-root-handle root) relative-dir)] - [key (identity-key directory-info)]) - (unless (eq? (secure-entry-kind directory-info) 'directory) - (error 'semgrep-cli "recursive target changed type" relative-dir)) - (when (identity-seen? key seen) - (error 'semgrep-cli "recursive target contains a directory cycle" - relative-dir)) - (set! seen (cons key seen)) - (let loop ([entries (secure-directory-list - (cli-root-handle root) - relative-dir - max-directory-entries - max-directory-list-bytes)] - [current-acc acc]) - (if (null? entries) - current-acc - (let* ([name (car entries)] - [relative (if (string=? relative-dir "") - name - (sg-path-join relative-dir name))] - [display (sg-path-join dir relative)]) - (set! entry-count (+ entry-count 1)) - (set! path-char-count - (+ path-char-count (string-length relative))) - (when (> entry-count max-recursive-entries) - (error 'semgrep-cli "recursive target exceeds entry limit" - max-recursive-entries)) - (when (> path-char-count max-recursive-path-chars) - (error 'semgrep-cli "recursive target exceeds path-byte budget" - max-recursive-path-chars)) - (if (ignored-path? ignore-patterns relative name) - (loop (cdr entries) current-acc) - (let ([info (secure-entry-info - (cli-root-handle root) relative)]) - (case (secure-entry-kind info) - [(symlink) - (loop (cdr entries) current-acc)] - [(directory) - (if (skip-directory-name? name) - (loop (cdr entries) current-acc) - (loop (cdr entries) - (walk relative (+ depth 1) current-acc)))] - [(file) - (when (> (secure-entry-links info) 1) - (error 'semgrep-cli - "recursive target contains a hard-linked file" - display)) - (loop - (cdr entries) - (if (scannable-file? language-opt display) - (cons (make-cli-target root relative display) - current-acc) - current-acc))] - [else (loop (cdr entries) current-acc)])))))))))) + (let ([expanded + (let walk ([relative-dir ""] [depth 0] [acc '()]) + (when (> depth max-recursive-depth) + (error 'semgrep-cli "recursive target exceeds depth limit" + max-recursive-depth)) + (let* ([directory-info + (secure-entry-info (cli-root-handle root) relative-dir)] + [key (identity-key directory-info)]) + (unless (eq? (secure-entry-kind directory-info) 'directory) + (error 'semgrep-cli "recursive target changed type" relative-dir)) + (when (identity-seen? key seen) + (error 'semgrep-cli "recursive target contains a directory cycle" + relative-dir)) + (set! seen (cons key seen)) + (let loop ([entries (secure-directory-list + (cli-root-handle root) + relative-dir + max-directory-entries + max-directory-list-bytes)] + [current-acc acc]) + (if (null? entries) + current-acc + (let* ([name (car entries)] + [relative (if (string=? relative-dir "") + name + (sg-path-join relative-dir name))] + [display (sg-path-join dir relative)]) + (set! entry-count (+ entry-count 1)) + (set! path-char-count + (+ path-char-count (string-length relative))) + (when (> entry-count max-recursive-entries) + (error 'semgrep-cli "recursive target exceeds entry limit" + max-recursive-entries)) + (when (> path-char-count max-recursive-path-chars) + (error 'semgrep-cli "recursive target exceeds path-byte budget" + max-recursive-path-chars)) + (if (ignored-path? ignore-patterns relative name) + (begin + (set! ignored-paths (cons display ignored-paths)) + (loop (cdr entries) current-acc)) + (let ([info (secure-entry-info + (cli-root-handle root) relative)]) + (case (secure-entry-kind info) + [(symlink) + (loop (cdr entries) current-acc)] + [(directory) + (if (skip-directory-name? name) + (loop (cdr entries) current-acc) + (loop (cdr entries) + (walk relative (+ depth 1) current-acc)))] + [(file) + (when (> (secure-entry-links info) 1) + (error 'semgrep-cli + "recursive target contains a hard-linked file" + display)) + (loop + (cdr entries) + (if (scannable-file? language-opt display) + (cons (make-cli-target root relative display) + current-acc) + current-acc))] + [else (loop (cdr entries) current-acc)]))))))))]) + (when (and use-ignore? (pair? ignored-paths)) + (report-ignored-paths dir (reverse ignored-paths))) + expanded))) (def (expand-file-target target) (let-values ([(parent name) (path-parent-and-name target)]) @@ -363,7 +395,7 @@ (error 'semgrep-cli "target has multiple hard links" target)) (values (list (make-cli-target root name target)) (list root))))))) -(def (expand-target language-opt target) +(def (expand-target language-opt use-ignore? target) (if (string=? target "-") (values (list target) '()) (guard (directory-error @@ -376,10 +408,10 @@ (raise walk-error)]) (values (reverse (expand-open-directory-target - language-opt target root)) + language-opt target root use-ignore?)) (list root))))))) -(def (expand-targets language-opt includes excludes targets) +(def (expand-targets language-opt use-ignore? includes excludes targets) (let ([opened-roots '()]) (guard (failure [#t @@ -397,7 +429,7 @@ (reverse expanded)) opened-roots) (let-values ([(next-targets next-roots) - (expand-target language-opt (car remaining))]) + (expand-target language-opt use-ignore? (car remaining))]) (set! opened-roots (append next-roots opened-roots)) (loop (cdr remaining) (append (reverse next-targets) expanded)))))))) @@ -506,7 +538,7 @@ (member "--help" args) (member "-h" args)) (begin (usage) 0) - (let-values ([(config language format severities autofix? includes excludes targets) + (let-values ([(config language format severities autofix? use-ignore? includes excludes targets) (parse-args args)]) (unless config (usage) @@ -516,7 +548,7 @@ (error 'semgrep-cli "missing target")) (let ([rules (parse-config-file config)]) (let-values ([(expanded-targets roots) - (expand-targets language includes excludes targets)]) + (expand-targets language use-ignore? includes excludes targets)]) (dynamic-wind (lambda () (void)) (lambda () new file mode 100644 --- /dev/null +++ b/tests/ignorehostile/.semgrepignore @@ -0,0 +1 @@ +*.py new file mode 100644 --- /dev/null +++ b/tests/ignorehostile/payload.py @@ -0,0 +1 @@ +eval(payload) --- a/tests/smoke.ss +++ b/tests/smoke.ss @@ -57,6 +57,14 @@ (or (string=? (substring haystack i (+ i nlen)) needle) (loop (+ i 1)))))))) +(define (call-with-captured-output thunk) + (let ([out (open-output-string)] + [err (open-output-string)]) + (let ([code (parameterize ([current-output-port out] + [current-error-port err]) + (thunk))]) + (values code (get-output-string out) (get-output-string err))))) + (define (count-findings pred findings) (let loop ([xs findings] [count 0]) (cond @@ -5245,9 +5253,39 @@ (check (main '("scan" "--config" "tests/fixtures/eval.yml" "tests/fixtures")) => 1)) +(test-case "cli main does not auto-trust a hostile semgrepignore by default" + (let-values ([(code out err) + (call-with-captured-output + (lambda () + (main '("scan" "--json" "--config" "tests/fixtures/eval.yml" + "tests/ignorehostile"))))]) + (check code => 1) + (check (string-contains? out "payload.py") => #t) + (check (string-contains? err "ignored") => #f))) + +(test-case "cli main honors semgrepignore only when opted in" + (let-values ([(code out err) + (call-with-captured-output + (lambda () + (main '("scan" "--json" "--use-semgrepignore" + "--config" "tests/fixtures/eval.yml" + "tests/ignorehostile"))))]) + (check code => 0) + (check (string-contains? out "payload.py") => #f) + (check (string-contains? err "payload.py") => #t) + (check (string-contains? err "ignored") => #t))) + (test-case "cli main honors semgrepignore negation" - (check (main '("scan" "--config" "tests/fixtures/eval.yml" "tests/ignorefixtures")) - => 1)) + (let-values ([(code out err) + (call-with-captured-output + (lambda () + (main '("scan" "--json" "--use-semgrepignore" + "--config" "tests/fixtures/eval.yml" + "tests/ignorefixtures"))))]) + (check code => 1) + (check (string-contains? out "keep.py") => #t) + (check (string-contains? out "skip.py") => #f) + (check (string-contains? err "skip.py") => #t))) (test-case "cli main accepts json flag" (check (main '("scan" "--json" "--config" "tests/fixtures/eval.yml" "tests/fixtures/demo.py")) @@ -5257,6 +5295,7 @@ (check (main '("scan" "--config" "tests/fixtures/eval.yml" "--include" "*.py" "--exclude" "demo.py" + "--exclude" "ignored.py" "tests/fixtures")) => 0))