Extract Semgrep taint spec scanners

ober

274c0782a812469091338eb811eb46db6223a804

diff --git a/SEMGREP_JERBOA_IMPLEMENTATION.md b/SEMGREP_JERBOA_IMPLEMENTATION.md
index fef7302..c4fba56 100644
--- a/SEMGREP_JERBOA_IMPLEMENTATION.md
+++ b/SEMGREP_JERBOA_IMPLEMENTATION.md
@@ -528,6 +528,10 @@ Completed in the repo:
     `src/semgrep/engine/fallback-positive-dispatch.ss`, so the plain and
     with-bindings positive scan paths now share both JS/TS and non-JS/TS
     callback sequencing
+  - extracted taint state records, taint source/sink/propagator spec scanning,
+    and source-match wrappers into `src/semgrep/engine/taint-specs.ss`,
+    leaving the deeper taint propagation and reachability logic in `scan.ss`
+    for the next cuts
   - exported shared Python line-assignment analysis from
     `src/semgrep/engine/py-constant-prop.ss` so remaining Python fallbacks in
     `scan.ss` can reuse one assignment-info implementation
@@ -551,7 +555,7 @@ Remaining Phase 0 work:
   special-case fallback surfaces are now out, and the duplicated positive-entry
   dispatch core is out too; the next meaningful cuts are the remaining larger
   language-specific scanner ownership in `scan.ss`, shared textual helper
-  ownership, and taint sections.
+  ownership, and the deeper taint propagation/reachability sections.
 - Run broader upstream sweeps and grow the expected-fail baselines from real
   sampled data instead of one narrow seed run.
 
diff --git a/lib/semgrep/engine/taint-specs.sls b/lib/semgrep/engine/taint-specs.sls
new file mode 100644
index 0000000..3205090
--- /dev/null
+++ b/lib/semgrep/engine/taint-specs.sls
@@ -0,0 +1,227 @@
+#!chezscheme
+;;; Generated by jerbuild — DO NOT EDIT
+;;; Source: src/semgrep/engine/taint-specs.ss
+
+(library (semgrep engine taint-specs)
+  (export taint-state* taint-state taint-state-finding
+    taint-state-labels taint-state-exact? taint-state-token?
+    taint-state-contained? taint-state-control?
+    taint-state-side-effect? taint-state-origin
+    taint-state-with-labels taint-by-side-effect?
+    taint-state-for-spec source-match-state
+    source-match-requires scan-taint-specs
+    scan-taint-source-matches scan-taint-sinks
+    scan-taint-propagators)
+  (import
+    (except (chezscheme) make-hash-table hash-table? sort sort!
+     printf fprintf format path-extension path-absolute?
+     with-input-from-string with-output-to-string iota \x31;+
+     \x31;- partition make-date make-time meta atom?)
+    (except (jerboa prelude) meta atom?))
+  (def (alist-ref/default xs key default)
+       (let ([found (assoc key xs)])
+         (if found (cdr found) default)))
+  (def (any? pred xs)
+       (and (not (null? xs))
+            (or (pred (car xs)) (any? pred (cdr xs)))))
+  (def (sg-string-prefix? prefix s)
+       (let ([prefix-len (string-length prefix)]
+             [len (string-length s)])
+         (and (<= prefix-len len)
+              (string=? (substring s 0 prefix-len) prefix))))
+  (def (identifier-token-char? ch)
+       (or (char-alphabetic? ch)
+           (char-numeric? ch)
+           (char=? ch #\_)))
+  (def (symbolic-identifier-start-char? ch)
+       (or (char-alphabetic? ch) (char=? ch #\_)))
+  (def (taint-state* finding labels exact? token? contained?
+         control? origin)
+       (list (cons 'finding finding) (cons 'labels labels)
+         (cons 'exact exact?) (cons 'token token?)
+         (cons 'contained contained?) (cons 'control control?)
+         (cons 'origin origin)))
+  (def (taint-state finding labels exact? token? contained?
+         control?)
+       (taint-state* finding labels exact? token? contained?
+         control? finding))
+  (def (taint-state-finding state)
+       (alist-ref/default state 'finding #f))
+  (def (taint-state-labels state)
+       (alist-ref/default state 'labels '()))
+  (def (taint-state-exact? state)
+       (and (alist-ref/default state 'exact #f) #t))
+  (def (taint-state-token? state)
+       (and (alist-ref/default state 'token #f) #t))
+  (def (taint-state-contained? state)
+       (and (alist-ref/default state 'contained #f) #t))
+  (def (taint-state-control? state)
+       (and (alist-ref/default state 'control #f) #t))
+  (def (taint-state-side-effect? state)
+       (and (alist-ref/default state 'side-effect #f) #t))
+  (def (taint-state-origin state)
+       (alist-ref/default
+         state
+         'origin
+         (taint-state-finding state)))
+  (def (taint-state-with-labels state labels)
+       (cons
+         (cons 'side-effect (taint-state-side-effect? state))
+         (taint-state* (taint-state-finding state) labels
+           (taint-state-exact? state) (taint-state-token? state)
+           (taint-state-contained? state) (taint-state-control? state)
+           (taint-state-origin state))))
+  (def (taint-by-side-effect? value)
+       (cond
+         [(not value) #f]
+         [(eq? value #t) #t]
+         [(string? value) (not (string=? value "false"))]
+         [else #f]))
+  (def (taint-labels-for-spec spec default-label)
+       (let ([label (alist-ref/default spec 'label #f)])
+         (cond
+           [label (list label)]
+           [default-label (list default-label)]
+           [else '()])))
+  (def (taint-entry-has-focus? entry)
+       (cond
+         [(not entry) #f]
+         [(eq? (car entry) 'focus-metavariable) #t]
+         [(or (eq? (car entry) 'patterns)
+              (eq? (car entry) 'pattern-either))
+          (any? taint-entry-has-focus? (cdr entry))]
+         [else #f]))
+  (def (taint-entry-has-python-def-pattern? entry)
+       (cond
+         [(not entry) #f]
+         [(eq? (car entry) 'pattern)
+          (sg-string-prefix? "def " (string-trim (cdr entry)))]
+         [(or (eq? (car entry) 'patterns)
+              (eq? (car entry) 'pattern-either))
+          (any? taint-entry-has-python-def-pattern? (cdr entry))]
+         [else #f]))
+  (def (taint-function-parameter-source-spec? spec)
+       (let ([entry (alist-ref/default spec 'entry #f)])
+         (and (taint-entry-has-focus? entry)
+              (taint-entry-has-python-def-pattern? entry))))
+  (def (identifier-only-text? text)
+       (let* ([trimmed (string-trim text)]
+              [len (string-length trimmed)])
+         (and (> len 0)
+              (symbolic-identifier-start-char? (string-ref trimmed 0))
+              (let loop ([i 1])
+                (or (= i len)
+                    (and (identifier-token-char? (string-ref trimmed i))
+                         (loop (+ i 1))))))))
+  (def (taint-bare-identifier-source-spec? spec)
+       (let ([entry (alist-ref/default spec 'entry #f)])
+         (and entry
+              (eq? (car entry) 'pattern)
+              (identifier-only-text? (cdr entry)))))
+  (def (taint-state-for-spec spec finding default-label)
+       (let ([side-effect? (taint-by-side-effect?
+                             (alist-ref/default spec 'by-side-effect #f))])
+         (cons
+           (cons 'side-effect side-effect?)
+           (taint-state finding (taint-labels-for-spec spec default-label)
+             (alist-ref/default spec 'exact #f)
+             (or side-effect?
+                 (taint-function-parameter-source-spec? spec)
+                 (taint-bare-identifier-source-spec? spec))
+             #f (alist-ref/default spec 'control #f)))))
+  (def (source-match-state match)
+       (alist-ref/default match 'state #f))
+  (def (source-match-requires match)
+       (alist-ref/default match 'requires #f))
+  (def (scan-taint-specs rule specs language path source target-root default-label
+         filter-reachable scan-positive-entry)
+       (apply
+         append
+         (map (lambda (spec)
+                (map (lambda (finding)
+                       (taint-state-for-spec spec finding default-label))
+                     (filter-reachable
+                       language
+                       source
+                       (scan-positive-entry rule (alist-ref/default spec 'entry #f) language
+                         path source target-root))))
+              specs)))
+  (def (scan-taint-source-matches rule specs language path source target-root
+         filter-reachable scan-positive-entry)
+       (apply
+         append
+         (map (lambda (spec)
+                (map (lambda (finding)
+                       (list
+                         (cons
+                           'state
+                           (taint-state-for-spec
+                             spec
+                             finding
+                             "__SOURCE__"))
+                         (cons
+                           'requires
+                           (alist-ref/default spec 'requires #f))))
+                     (filter-reachable
+                       language
+                       source
+                       (scan-positive-entry rule (alist-ref/default spec 'entry #f) language
+                         path source target-root))))
+              specs)))
+  (def (scan-taint-sinks rule specs language path source
+         target-root filter-reachable scan-positive-entry)
+       (apply
+         append
+         (map (lambda (spec)
+                (map (lambda (finding)
+                       (list
+                         (cons 'finding finding)
+                         (cons
+                           'non-exact
+                           (and (alist-ref/default spec 'exact-present #f)
+                                (not (alist-ref/default spec 'exact #f))))
+                         (cons
+                           'requires
+                           (alist-ref/default spec 'requires #f))))
+                     (filter-reachable
+                       language
+                       source
+                       (scan-positive-entry rule (alist-ref/default spec 'entry #f) language
+                         path source target-root))))
+              specs)))
+  (def (scan-taint-propagators rule propagators language path source target-root
+         filter-reachable scan-positive-entry)
+       (apply
+         append
+         (map (lambda (propagator)
+                (map (lambda (finding)
+                       (list (cons 'finding finding)
+                         (cons
+                           'from
+                           (alist-ref/default propagator 'from #f))
+                         (cons 'to (alist-ref/default propagator 'to #f))
+                         (cons
+                           'by-side-effect
+                           (alist-ref/default
+                             propagator
+                             'by-side-effect
+                             #t))
+                         (cons
+                           'label
+                           (alist-ref/default propagator 'label #f))
+                         (cons
+                           'requires
+                           (alist-ref/default propagator 'requires #f))
+                         (cons
+                           'replace-labels
+                           (alist-ref/default
+                             propagator
+                             'replace-labels
+                             #f))
+                         (cons 'implicit-assignment #f)))
+                     (filter-reachable
+                       language
+                       source
+                       (scan-positive-entry rule (alist-ref/default propagator 'entry #f)
+                         language path source target-root))))
+              propagators))))
diff --git a/lib/semgrep/scan.sls b/lib/semgrep/scan.sls
index c511ddd..ed47f2e 100644
--- a/lib/semgrep/scan.sls
+++ b/lib/semgrep/scan.sls
@@ -42,10 +42,10 @@
    (semgrep engine rule-plan) (semgrep engine regex-support)
    (semgrep engine ts-decorator-scan)
    (semgrep engine ts-type-scan) (semgrep engine ts-query-scan)
-   (semgrep engine text-support) (semgrep rule parse-rule)
-   (semgrep parse parse-target) (semgrep source offsets)
-   (semgrep targeting path-filter) (semgrep util literals)
-   (semgrep match structural))
+   (semgrep engine text-support) (semgrep engine taint-specs)
+   (semgrep rule parse-rule) (semgrep parse parse-target)
+   (semgrep source offsets) (semgrep targeting path-filter)
+   (semgrep util literals) (semgrep match structural))
   (def (alist-ref/default xs key default)
        (let ([found (assoc key xs)])
          (if found (cdr found) default)))
@@ -23506,100 +23506,6 @@
          target-root)
        (scan-patterns-clauses rule (rule-pattern rule) language
          path source target-root))
-  (def (taint-state* finding labels exact? token? contained?
-         control? origin)
-       (list (cons 'finding finding) (cons 'labels labels)
-         (cons 'exact exact?) (cons 'token token?)
-         (cons 'contained contained?) (cons 'control control?)
-         (cons 'origin origin)))
-  (def (taint-state finding labels exact? token? contained?
-         control?)
-       (taint-state* finding labels exact? token? contained?
-         control? finding))
-  (def (taint-state-finding state)
-       (alist-ref/default state 'finding #f))
-  (def (taint-state-labels state)
-       (alist-ref/default state 'labels '()))
-  (def (taint-state-exact? state)
-       (and (alist-ref/default state 'exact #f) #t))
-  (def (taint-state-token? state)
-       (and (alist-ref/default state 'token #f) #t))
-  (def (taint-state-contained? state)
-       (and (alist-ref/default state 'contained #f) #t))
-  (def (taint-state-control? state)
-       (and (alist-ref/default state 'control #f) #t))
-  (def (taint-state-side-effect? state)
-       (and (alist-ref/default state 'side-effect #f) #t))
-  (def (taint-state-origin state)
-       (alist-ref/default
-         state
-         'origin
-         (taint-state-finding state)))
-  (def (taint-state-with-labels state labels)
-       (cons
-         (cons 'side-effect (taint-state-side-effect? state))
-         (taint-state* (taint-state-finding state) labels
-           (taint-state-exact? state) (taint-state-token? state)
-           (taint-state-contained? state) (taint-state-control? state)
-           (taint-state-origin state))))
-  (def (taint-by-side-effect? value)
-       (cond
-         [(not value) #f]
-         [(eq? value #t) #t]
-         [(string? value) (not (string=? value "false"))]
-         [else #f]))
-  (def (taint-labels-for-spec spec default-label)
-       (let ([label (alist-ref/default spec 'label #f)])
-         (cond
-           [label (list label)]
-           [default-label (list default-label)]
-           [else '()])))
-  (def (taint-entry-has-focus? entry)
-       (cond
-         [(not entry) #f]
-         [(eq? (car entry) 'focus-metavariable) #t]
-         [(or (eq? (car entry) 'patterns)
-              (eq? (car entry) 'pattern-either))
-          (any? taint-entry-has-focus? (cdr entry))]
-         [else #f]))
-  (def (taint-entry-has-python-def-pattern? entry)
-       (cond
-         [(not entry) #f]
-         [(eq? (car entry) 'pattern)
-          (sg-string-prefix? "def " (string-trim (cdr entry)))]
-         [(or (eq? (car entry) 'patterns)
-              (eq? (car entry) 'pattern-either))
-          (any? taint-entry-has-python-def-pattern? (cdr entry))]
-         [else #f]))
-  (def (taint-function-parameter-source-spec? spec)
-       (let ([entry (alist-ref/default spec 'entry #f)])
-         (and (taint-entry-has-focus? entry)
-              (taint-entry-has-python-def-pattern? entry))))
-  (def (identifier-only-text? text)
-       (let* ([trimmed (string-trim text)]
-              [len (string-length trimmed)])
-         (and (> len 0)
-              (symbolic-identifier-start-char? (string-ref trimmed 0))
-              (let loop ([i 1])
-                (or (= i len)
-                    (and (identifier-token-char? (string-ref trimmed i))
-                         (loop (+ i 1))))))))
-  (def (taint-bare-identifier-source-spec? spec)
-       (let ([entry (alist-ref/default spec 'entry #f)])
-         (and entry
-              (eq? (car entry) 'pattern)
-              (identifier-only-text? (cdr entry)))))
-  (def (taint-state-for-spec spec finding default-label)
-       (let ([side-effect? (taint-by-side-effect?
-                             (alist-ref/default spec 'by-side-effect #f))])
-         (cons
-           (cons 'side-effect side-effect?)
-           (taint-state finding (taint-labels-for-spec spec default-label)
-             (alist-ref/default spec 'exact #f)
-             (or side-effect?
-                 (taint-function-parameter-source-spec? spec)
-                 (taint-bare-identifier-source-spec? spec))
-             #f (alist-ref/default spec 'control #f)))))
   (def (python-next-line-start source line-start)
        (let* ([line-end (line-end-after source line-start)]
               [len (string-length source)])
@@ -24214,6 +24120,15 @@
                                (substring raw 0 (- (string-length raw) 1)))
                              raw)])
               expr)))
+  (def (identifier-only-text? text)
+       (let* ([trimmed (string-trim text)]
+              [len (string-length trimmed)])
+         (and (> len 0)
+              (symbolic-identifier-start-char? (string-ref trimmed 0))
+              (let loop ([i 1])
+                (or (= i len)
+                    (and (identifier-token-char? (string-ref trimmed i))
+                         (loop (+ i 1))))))))
   (def (dart-simple-nonthrowing-expression? text)
        (let ([trimmed (string-trim text)])
          (or (identifier-only-text? trimmed)
@@ -24407,45 +24322,6 @@
              language
              source
              findings))))
-  (def (scan-taint-specs rule specs language path source
-         target-root default-label)
-       (apply
-         append
-         (map (lambda (spec)
-                (map (lambda (finding)
-                       (taint-state-for-spec spec finding default-label))
-                     (filter-taint-reachable-findings
-                       language
-                       source
-                       (scan-positive-pattern-entry rule (alist-ref/default spec 'entry #f) language
-                         path source target-root))))
-              specs)))
-  (def (scan-taint-source-matches rule specs language path
-         source target-root)
-       (apply
-         append
-         (map (lambda (spec)
-                (map (lambda (finding)
-                       (list
-                         (cons
-                           'state
-                           (taint-state-for-spec
-                             spec
-                             finding
-                             "__SOURCE__"))
-                         (cons
-                           'requires
-                           (alist-ref/default spec 'requires #f))))
-                     (filter-taint-reachable-findings
-                       language
-                       source
-                       (scan-positive-pattern-entry rule (alist-ref/default spec 'entry #f) language
-                         path source target-root))))
-              specs)))
-  (def (source-match-state match)
-       (alist-ref/default match 'state #f))
-  (def (source-match-requires match)
-       (alist-ref/default match 'requires #f))
   (def (taint-value-contains-string? value needle)
        (cond
          [(string? value) (string-find-substring value needle)]
@@ -24520,63 +24396,6 @@
                       match)))
              matches)
            matches))
-  (def (scan-taint-sinks rule specs language path source
-         target-root)
-       (apply
-         append
-         (map (lambda (spec)
-                (map (lambda (finding)
-                       (list
-                         (cons 'finding finding)
-                         (cons
-                           'non-exact
-                           (and (alist-ref/default spec 'exact-present #f)
-                                (not (alist-ref/default spec 'exact #f))))
-                         (cons
-                           'requires
-                           (alist-ref/default spec 'requires #f))))
-                     (filter-taint-reachable-findings
-                       language
-                       source
-                       (scan-positive-pattern-entry rule (alist-ref/default spec 'entry #f) language
-                         path source target-root))))
-              specs)))
-  (def (scan-taint-propagators rule propagators language path
-         source target-root)
-       (apply
-         append
-         (map (lambda (propagator)
-                (map (lambda (finding)
-                       (list (cons 'finding finding)
-                         (cons
-                           'from
-                           (alist-ref/default propagator 'from #f))
-                         (cons 'to (alist-ref/default propagator 'to #f))
-                         (cons
-                           'by-side-effect
-                           (alist-ref/default
-                             propagator
-                             'by-side-effect
-                             #t))
-                         (cons
-                           'label
-                           (alist-ref/default propagator 'label #f))
-                         (cons
-                           'requires
-                           (alist-ref/default propagator 'requires #f))
-                         (cons
-                           'replace-labels
-                           (alist-ref/default
-                             propagator
-                             'replace-labels
-                             #f))
-                         (cons 'implicit-assignment #f)))
-                     (filter-taint-reachable-findings
-                       language
-                       source
-                       (scan-positive-pattern-entry rule (alist-ref/default propagator 'entry #f)
-                         language path source target-root))))
-              propagators)))
   (def (implicit-assignment-patterns language)
        (cond
          [(or (string=? language "javascript")
@@ -27212,7 +27031,9 @@
          taint)
        (let* ([raw-source-matches (scan-taint-source-matches rule
                                     (alist-ref/default taint 'sources '())
-                                    language path source target-root)]
+                                    language path source target-root
+                                    filter-taint-reachable-findings
+                                    scan-positive-pattern-entry)]
               [source-matches (filter-python-django-safe-url-source-matches
                                 language
                                 source
@@ -27226,7 +27047,9 @@
                                       source-matches))]
               [propagators (scan-taint-propagators rule
                              (alist-ref/default taint 'propagators '())
-                             language path source target-root)]
+                             language path source target-root
+                             filter-taint-reachable-findings
+                             scan-positive-pattern-entry)]
               [implicit-propagators (append
                                       (scan-implicit-assignment-propagators rule language path source
                                         target-root)
@@ -27246,13 +27069,16 @@
                                               source))
                                           '()))]
               [sanitizers (scan-taint-specs rule (alist-ref/default taint 'sanitizers '())
-                            language path source target-root #f)]
+                            language path source target-root #f
+                            filter-taint-reachable-findings
+                            scan-positive-pattern-entry)]
               [sources (expand-taint-sources rule initial-sources source-matches
                          (append propagators implicit-propagators)
                          sanitizers implicit-propagators source)]
-              [sinks0 (scan-taint-sinks rule
-                        (alist-ref/default taint 'sinks '()) language path
-                        source target-root)]
+              [sinks0 (scan-taint-sinks rule (alist-ref/default taint 'sinks '()) language
+                        path source target-root
+                        filter-taint-reachable-findings
+                        scan-positive-pattern-entry)]
               [sinks (filter-prototype-pollution-guarded-sinks
                        rule
                        source
diff --git a/src/.jerbuild-hashes b/src/.jerbuild-hashes
index 85d60e2..d33e083 100644
--- a/src/.jerbuild-hashes
+++ b/src/.jerbuild-hashes
@@ -49,12 +49,13 @@
  ("src/semgrep/engine/positive-dispatch.ss"
    .
    "221B0D787F146331")
+ ("src/semgrep/engine/taint-specs.ss" . "6B0AA195C9B93DFB")
  ("src/semgrep/engine/ts-query-scan.ss" . "51AD5339F6DE47B4")
  ("src/semgrep/rule/parse-rule.ss" . "EFA6D401699CEDEF")
  ("src/semgrep/output/json.ss" . "293881CFA2ADB7BC")
  ("src/semgrep/lang.ss" . "6982E07679D20836")
  ("src/semgrep/parse/parse-target.ss" . "97AA8FFEB12736DA")
- ("src/semgrep/scan.ss" . "FA37304B7A269959")
+ ("src/semgrep/scan.ss" . "75AE4B430DB76EB")
  ("src/semgrep/engine/generic-scan.ss" . "F69D0ACD0DD62610")
  ("src/semgrep/engine/py-constant-prop.ss"
    .
diff --git a/src/semgrep/engine/taint-specs.ss b/src/semgrep/engine/taint-specs.ss
new file mode 100644
index 0000000..2e8eba6
--- /dev/null
+++ b/src/semgrep/engine/taint-specs.ss
@@ -0,0 +1,281 @@
+(export
+  taint-state*
+  taint-state
+  taint-state-finding
+  taint-state-labels
+  taint-state-exact?
+  taint-state-token?
+  taint-state-contained?
+  taint-state-control?
+  taint-state-side-effect?
+  taint-state-origin
+  taint-state-with-labels
+  taint-by-side-effect?
+  taint-state-for-spec
+  source-match-state
+  source-match-requires
+  scan-taint-specs
+  scan-taint-source-matches
+  scan-taint-sinks
+  scan-taint-propagators)
+
+(import (except (jerboa prelude) meta atom?))
+
+(def (alist-ref/default xs key default)
+  (let ([found (assoc key xs)])
+    (if found (cdr found) default)))
+
+(def (any? pred xs)
+  (and (not (null? xs))
+       (or (pred (car xs))
+           (any? pred (cdr xs)))))
+
+(def (sg-string-prefix? prefix s)
+  (let ([prefix-len (string-length prefix)]
+        [len (string-length s)])
+    (and (<= prefix-len len)
+         (string=? (substring s 0 prefix-len) prefix))))
+
+(def (identifier-token-char? ch)
+  (or (char-alphabetic? ch)
+      (char-numeric? ch)
+      (char=? ch #\_)))
+
+(def (symbolic-identifier-start-char? ch)
+  (or (char-alphabetic? ch)
+      (char=? ch #\_)))
+
+(def (taint-state* finding labels exact? token? contained? control? origin)
+  (list (cons 'finding finding)
+        (cons 'labels labels)
+        (cons 'exact exact?)
+        (cons 'token token?)
+        (cons 'contained contained?)
+        (cons 'control control?)
+        (cons 'origin origin)))
+
+(def (taint-state finding labels exact? token? contained? control?)
+  (taint-state* finding labels exact? token? contained? control? finding))
+
+(def (taint-state-finding state)
+  (alist-ref/default state 'finding #f))
+
+(def (taint-state-labels state)
+  (alist-ref/default state 'labels '()))
+
+(def (taint-state-exact? state)
+  (and (alist-ref/default state 'exact #f) #t))
+
+(def (taint-state-token? state)
+  (and (alist-ref/default state 'token #f) #t))
+
+(def (taint-state-contained? state)
+  (and (alist-ref/default state 'contained #f) #t))
+
+(def (taint-state-control? state)
+  (and (alist-ref/default state 'control #f) #t))
+
+(def (taint-state-side-effect? state)
+  (and (alist-ref/default state 'side-effect #f) #t))
+
+(def (taint-state-origin state)
+  (alist-ref/default state 'origin (taint-state-finding state)))
+
+(def (taint-state-with-labels state labels)
+  (cons (cons 'side-effect (taint-state-side-effect? state))
+        (taint-state*
+          (taint-state-finding state)
+          labels
+          (taint-state-exact? state)
+          (taint-state-token? state)
+          (taint-state-contained? state)
+          (taint-state-control? state)
+          (taint-state-origin state))))
+
+(def (taint-by-side-effect? value)
+  (cond
+    [(not value) #f]
+    [(eq? value #t) #t]
+    [(string? value) (not (string=? value "false"))]
+    [else #f]))
+
+(def (taint-labels-for-spec spec default-label)
+  (let ([label (alist-ref/default spec 'label #f)])
+    (cond
+      [label (list label)]
+      [default-label (list default-label)]
+      [else '()])))
+
+(def (taint-entry-has-focus? entry)
+  (cond
+    [(not entry) #f]
+    [(eq? (car entry) 'focus-metavariable) #t]
+    [(or (eq? (car entry) 'patterns)
+         (eq? (car entry) 'pattern-either))
+     (any? taint-entry-has-focus? (cdr entry))]
+    [else #f]))
+
+(def (taint-entry-has-python-def-pattern? entry)
+  (cond
+    [(not entry) #f]
+    [(eq? (car entry) 'pattern)
+     (sg-string-prefix? "def " (string-trim (cdr entry)))]
+    [(or (eq? (car entry) 'patterns)
+         (eq? (car entry) 'pattern-either))
+     (any? taint-entry-has-python-def-pattern? (cdr entry))]
+    [else #f]))
+
+(def (taint-function-parameter-source-spec? spec)
+  (let ([entry (alist-ref/default spec 'entry #f)])
+    (and (taint-entry-has-focus? entry)
+         (taint-entry-has-python-def-pattern? entry))))
+
+(def (identifier-only-text? text)
+  (let* ([trimmed (string-trim text)]
+         [len (string-length trimmed)])
+    (and (> len 0)
+         (symbolic-identifier-start-char? (string-ref trimmed 0))
+         (let loop ([i 1])
+           (or (= i len)
+               (and (identifier-token-char? (string-ref trimmed i))
+                    (loop (+ i 1))))))))
+
+(def (taint-bare-identifier-source-spec? spec)
+  (let ([entry (alist-ref/default spec 'entry #f)])
+    (and entry
+         (eq? (car entry) 'pattern)
+         (identifier-only-text? (cdr entry)))))
+
+(def (taint-state-for-spec spec finding default-label)
+  (let ([side-effect?
+         (taint-by-side-effect?
+           (alist-ref/default spec 'by-side-effect #f))])
+    (cons
+      (cons 'side-effect side-effect?)
+      (taint-state
+        finding
+        (taint-labels-for-spec spec default-label)
+        (alist-ref/default spec 'exact #f)
+        (or side-effect?
+            (taint-function-parameter-source-spec? spec)
+            (taint-bare-identifier-source-spec? spec))
+        #f
+        (alist-ref/default spec 'control #f)))))
+
+(def (source-match-state match)
+  (alist-ref/default match 'state #f))
+
+(def (source-match-requires match)
+  (alist-ref/default match 'requires #f))
+
+(def (scan-taint-specs
+       rule specs language path source target-root default-label
+       filter-reachable scan-positive-entry)
+  (apply append
+         (map (lambda (spec)
+                (map (lambda (finding)
+                       (taint-state-for-spec spec finding default-label))
+                     (filter-reachable
+                       language
+                       source
+                       (scan-positive-entry
+                         rule
+                         (alist-ref/default spec 'entry #f)
+                         language
+                         path
+                         source
+                         target-root))))
+              specs)))
+
+(def (scan-taint-source-matches
+       rule specs language path source target-root
+       filter-reachable scan-positive-entry)
+  (apply append
+         (map (lambda (spec)
+                (map (lambda (finding)
+                       (list
+                         (cons 'state
+                               (taint-state-for-spec
+                                 spec
+                                 finding
+                                 "__SOURCE__"))
+                         (cons 'requires
+                               (alist-ref/default spec 'requires #f))))
+                     (filter-reachable
+                       language
+                       source
+                       (scan-positive-entry
+                         rule
+                         (alist-ref/default spec 'entry #f)
+                         language
+                         path
+                         source
+                         target-root))))
+              specs)))
+
+(def (scan-taint-sinks
+       rule specs language path source target-root
+       filter-reachable scan-positive-entry)
+  (apply append
+         (map (lambda (spec)
+                (map (lambda (finding)
+                       (list
+                         (cons 'finding finding)
+                         (cons 'non-exact
+                               (and (alist-ref/default
+                                      spec
+                                      'exact-present
+                                      #f)
+                                    (not (alist-ref/default
+                                           spec
+                                           'exact
+                                           #f))))
+                         (cons 'requires
+                               (alist-ref/default spec 'requires #f))))
+                     (filter-reachable
+                       language
+                       source
+                       (scan-positive-entry
+                         rule
+                         (alist-ref/default spec 'entry #f)
+                         language
+                         path
+                         source
+                         target-root))))
+              specs)))
+
+(def (scan-taint-propagators
+       rule propagators language path source target-root
+       filter-reachable scan-positive-entry)
+  (apply append
+         (map (lambda (propagator)
+                (map (lambda (finding)
+                       (list
+                         (cons 'finding finding)
+                         (cons 'from (alist-ref/default propagator 'from #f))
+                         (cons 'to (alist-ref/default propagator 'to #f))
+                         (cons 'by-side-effect
+                               (alist-ref/default
+                                 propagator
+                                 'by-side-effect
+                                 #t))
+                         (cons 'label
+                               (alist-ref/default propagator 'label #f))
+                         (cons 'requires
+                               (alist-ref/default propagator 'requires #f))
+                         (cons 'replace-labels
+                               (alist-ref/default propagator
+                                                  'replace-labels
+                                                  #f))
+                         (cons 'implicit-assignment #f)))
+                     (filter-reachable
+                       language
+                       source
+                       (scan-positive-entry
+                         rule
+                         (alist-ref/default propagator 'entry #f)
+                         language
+                         path
+                         source
+                         target-root))))
+              propagators)))
diff --git a/src/semgrep/scan.ss b/src/semgrep/scan.ss
index 1d5056a..61b7271 100644
--- a/src/semgrep/scan.ss
+++ b/src/semgrep/scan.ss
@@ -44,6 +44,7 @@
         (semgrep engine ts-type-scan)
         (semgrep engine ts-query-scan)
         (semgrep engine text-support)
+        (semgrep engine taint-specs)
         (semgrep rule parse-rule)
         (semgrep parse parse-target)
         (semgrep source offsets)
@@ -23556,123 +23557,6 @@
                          source
                          target-root))
 
-(def (taint-state* finding labels exact? token? contained? control? origin)
-  (list (cons 'finding finding)
-        (cons 'labels labels)
-        (cons 'exact exact?)
-        (cons 'token token?)
-        (cons 'contained contained?)
-        (cons 'control control?)
-        (cons 'origin origin)))
-
-(def (taint-state finding labels exact? token? contained? control?)
-  (taint-state* finding labels exact? token? contained? control? finding))
-
-(def (taint-state-finding state)
-  (alist-ref/default state 'finding #f))
-
-(def (taint-state-labels state)
-  (alist-ref/default state 'labels '()))
-
-(def (taint-state-exact? state)
-  (and (alist-ref/default state 'exact #f) #t))
-
-(def (taint-state-token? state)
-  (and (alist-ref/default state 'token #f) #t))
-
-(def (taint-state-contained? state)
-  (and (alist-ref/default state 'contained #f) #t))
-
-(def (taint-state-control? state)
-  (and (alist-ref/default state 'control #f) #t))
-
-(def (taint-state-side-effect? state)
-  (and (alist-ref/default state 'side-effect #f) #t))
-
-(def (taint-state-origin state)
-  (alist-ref/default state 'origin (taint-state-finding state)))
-
-(def (taint-state-with-labels state labels)
-  (cons (cons 'side-effect (taint-state-side-effect? state))
-        (taint-state*
-          (taint-state-finding state)
-          labels
-          (taint-state-exact? state)
-          (taint-state-token? state)
-          (taint-state-contained? state)
-          (taint-state-control? state)
-          (taint-state-origin state))))
-
-(def (taint-by-side-effect? value)
-  (cond
-    [(not value) #f]
-    [(eq? value #t) #t]
-    [(string? value) (not (string=? value "false"))]
-    [else #f]))
-
-(def (taint-labels-for-spec spec default-label)
-  (let ([label (alist-ref/default spec 'label #f)])
-    (cond
-      [label (list label)]
-      [default-label (list default-label)]
-      [else '()])))
-
-(def (taint-entry-has-focus? entry)
-  (cond
-    [(not entry) #f]
-    [(eq? (car entry) 'focus-metavariable) #t]
-    [(or (eq? (car entry) 'patterns)
-         (eq? (car entry) 'pattern-either))
-     (any? taint-entry-has-focus? (cdr entry))]
-    [else #f]))
-
-(def (taint-entry-has-python-def-pattern? entry)
-  (cond
-    [(not entry) #f]
-    [(eq? (car entry) 'pattern)
-     (sg-string-prefix? "def " (string-trim (cdr entry)))]
-    [(or (eq? (car entry) 'patterns)
-         (eq? (car entry) 'pattern-either))
-     (any? taint-entry-has-python-def-pattern? (cdr entry))]
-    [else #f]))
-
-(def (taint-function-parameter-source-spec? spec)
-  (let ([entry (alist-ref/default spec 'entry #f)])
-    (and (taint-entry-has-focus? entry)
-         (taint-entry-has-python-def-pattern? entry))))
-
-(def (identifier-only-text? text)
-  (let* ([trimmed (string-trim text)]
-         [len (string-length trimmed)])
-    (and (> len 0)
-         (symbolic-identifier-start-char? (string-ref trimmed 0))
-         (let loop ([i 1])
-           (or (= i len)
-               (and (identifier-token-char? (string-ref trimmed i))
-                    (loop (+ i 1))))))))
-
-(def (taint-bare-identifier-source-spec? spec)
-  (let ([entry (alist-ref/default spec 'entry #f)])
-    (and entry
-         (eq? (car entry) 'pattern)
-         (identifier-only-text? (cdr entry)))))
-
-(def (taint-state-for-spec spec finding default-label)
-  (let ([side-effect?
-         (taint-by-side-effect?
-           (alist-ref/default spec 'by-side-effect #f))])
-    (cons
-      (cons 'side-effect side-effect?)
-      (taint-state
-        finding
-        (taint-labels-for-spec spec default-label)
-        (alist-ref/default spec 'exact #f)
-        (or side-effect?
-            (taint-function-parameter-source-spec? spec)
-            (taint-bare-identifier-source-spec? spec))
-        #f
-        (alist-ref/default spec 'control #f)))))
-
 (def (python-next-line-start source line-start)
   (let* ([line-end (line-end-after source line-start)]
          [len (string-length source)])
@@ -24223,6 +24107,16 @@
                         raw)])
          expr)))
 
+(def (identifier-only-text? text)
+  (let* ([trimmed (string-trim text)]
+         [len (string-length trimmed)])
+    (and (> len 0)
+         (symbolic-identifier-start-char? (string-ref trimmed 0))
+         (let loop ([i 1])
+           (or (= i len)
+               (and (identifier-token-char? (string-ref trimmed i))
+                    (loop (+ i 1))))))))
+
 (def (dart-simple-nonthrowing-expression? text)
   (let ([trimmed (string-trim text)])
     (or (identifier-only-text? trimmed)
@@ -24395,53 +24289,6 @@
       source
       (filter-python-taint-reachable-findings language source findings))))
 
-(def (scan-taint-specs rule specs language path source target-root default-label)
-  (apply append
-         (map (lambda (spec)
-                (map (lambda (finding)
-                       (taint-state-for-spec spec finding default-label))
-                     (filter-taint-reachable-findings
-                       language
-                       source
-                       (scan-positive-pattern-entry
-                         rule