Clear typed request taint frontier
ober
a1201fb66de7443e29891bc8db6a1440d1a12ade
--- a/HANDOFF_OPUS_4_8.md +++ b/HANDOFF_OPUS_4_8.md @@ -1,6 +1,6 @@ # Opus 4.8 Handoff: jerboa-semgrep Semgrep Parity -Date: 2026-05-29 00:40 MDT local workspace time +Date: 2026-05-29 00:51 MDT local workspace time Workspace: `/Users/user/mine/jerboa-semgrep` Sibling upstream Semgrep checkout: `/Users/user/mine/semgrep` Packaged Semgrep oracle: `/Users/user/.local/bin/semgrep` @@ -31,6 +31,7 @@ taint/dataflow, path and target semantics, autofix, and output schemas. Recent checkpoints before this handoff commit: ```text +8b8005b Clear Express SQL taint frontier 38bfcf1 Clear destructuring taint frontier 19f45f7 Advance lambda and source taint parity 7cfb751 Clear lambda taint label reach @@ -75,7 +76,7 @@ make test Result: ```text -195 tests, 195 passed, 0 failed +196 tests, 196 passed, 0 failed ``` Local oracle: @@ -90,33 +91,34 @@ Result: oracle: 42 passed, 0 failed ``` -Focused route-template, destructuring, and assignment-only taint guardrail for -this checkpoint: +Focused request-host, route-template, destructuring, and assignment-only taint +guardrail for this checkpoint: ```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_REGEX='^(taint_object_funcdef|taint_nested_record_pattern|taint_no_builtin_props|taint_no_builtin_props1|taint_object_destructure)$' LIST_MISMATCHES=1 MAX_DIFFS=320 tests/oracle/upstream-sweep.sh +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_REGEX='^(taint_object_funcdef|taint_object_funcdef_1|taint_nested_record_pattern|taint_no_builtin_props|taint_no_builtin_props1|taint_object_destructure)$' LIST_MISMATCHES=1 MAX_DIFFS=320 tests/oracle/upstream-sweep.sh ``` Result: ```text -upstream-sweep: 5 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 5 compared +upstream-sweep: 6 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 6 compared ``` -This confirms `taint_object_funcdef`, `taint_nested_record_pattern`, -`taint_no_builtin_props`, `taint_no_builtin_props1`, and +This confirms `taint_object_funcdef`, `taint_object_funcdef_1`, +`taint_nested_record_pattern`, `taint_no_builtin_props`, +`taint_no_builtin_props1`, and `taint_object_destructure` are clean in the current tree. -Focused route-template case cleared in this checkpoint: +Focused request-host and callback-token cases cleared in this checkpoint: ```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_REGEX='^taint_object_funcdef$' LIST_MISMATCHES=1 MAX_DIFFS=320 tests/oracle/upstream-sweep.sh +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_REGEX='^(taint_object_funcdef_1|taint_propagator1)$' LIST_MISMATCHES=1 MAX_DIFFS=320 tests/oracle/upstream-sweep.sh ``` Result: ```text -upstream-sweep: 1 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 1 compared +upstream-sweep: 2 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 2 compared ``` Focused upstream guardrail containing the field-sensitive cases fixed so far: @@ -158,54 +160,44 @@ upstream-sweep: 3 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 3 com Current full upstream sweep: ```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_LIMIT=220 LIST_MISMATCHES=1 MAX_DIFFS=320 tests/oracle/upstream-sweep.sh +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep LIST_MISMATCHES=1 MAX_DIFFS=0 tests/oracle/upstream-sweep.sh ``` Result: ```text -upstream-sweep: 216 passed, 23 mismatched, 0 jerboa errors, 2 current errors, 241 compared +upstream-sweep: 218 passed, 21 mismatched, 0 jerboa errors, 2 current errors, 241 compared ``` -Important: `CASE_LIMIT` is not used by `tests/oracle/upstream-sweep.sh`; this -command effectively ran the full sorted 241-case upstream rule sweep. Use -`MAX_CASES=220` when a first-220-only guardrail is needed. The two current -errors are packaged-Semgrep oracle errors, not Jerboa scanner errors. +The two current errors are packaged-Semgrep oracle errors, not Jerboa scanner +errors. Use `MAX_CASES=220` when a first-window guardrail is needed; this +command intentionally ran the full sorted 241-case upstream rule sweep. ## What Changed In This Checkpoint -This checkpoint clears `taint_object_funcdef`, building on `38bfcf1` which -cleared `taint_nested_record_pattern`, `taint_no_builtin_props`, -`taint_no_builtin_props1`, and `taint_object_destructure`. +This checkpoint clears `taint_object_funcdef_1` and `taint_propagator1`, +building on `8b8005b` which cleared `taint_object_funcdef`. Implementation changes in `src/semgrep/scan.ss`: -- `regex-pattern-for-engine` now accepts embedded `(?i)` in addition to a - leading `(?i)`. The Semgrep SQL keyword regex in `taint_object_funcdef` is - shaped as `.*\b(?i)(select|delete|insert|create|update|alter|drop)\b.*`; - Jerboa's regex engine previously raised an internal pregexp error for that - form. The implemented behavior treats embedded `(?i)` as "case-insensitive - from here onward", which is enough for this fixture and compatible with the - common Semgrep rule idiom. -- JavaScript/TypeScript positive pattern scanning now has a targeted template - literal fallback for the sink shape `` `$SQLSTR${$EXPR}...` ``. It finds - backtick template literals, binds `$SQLSTR` to the static prefix before the - interpolation, binds `$EXPR` to the trimmed expression inside `${...}`, and - returns a whole-template finding so the later `focus-metavariable: $EXPR` - clause narrows the reported range to the expression. -- `pattern-inside` now has a route-callback fallback for the exact - `$APP.$METHOD(..., function $FUNC($REQ, $RES) {...})` pattern family. It also - recognizes Express-style arrow callbacks inside the call, including - `app.get('/ok', async (req, res) => { ... })`, and merges `$APP`, `$METHOD`, - `$REQ`, and `$RES` bindings onto the source candidate. -- The route-callback fallback intentionally runs before the older generic - function-parameter fallback. The generic fallback could prove containment - for this rule but only preserved `$REQ`; the later `$METHOD` regex filter - then dropped the source candidate. +- Built-in JavaScript/TypeScript implicit assignment propagation now includes + typed declaration patterns: `const $L: $T = $R`, `let $L: $T = $R`, and + `var $L: $T = $R`. Without these, the generic `$L = $R` fallback matched + `const host: string = httpOptions.hostname` with `$L=string`, so taint never + reached the real alias `host`. +- Token-taint reach into sinks containing function values is now more precise. + The old guard rejected any token source when the sink text contained `=>`, + which blocked request-option objects that had an unrelated nested callback. + The new guard computes arrow/function body ranges and blocks only when the + source token occurrences are solely inside those function bodies. +- This clears the request-host shape in `taint_object_funcdef_1` and also + clears `taint_propagator1`, where tainted tokens outside nested callbacks + should still reach the enclosing sink. New smoke coverage in `tests/smoke.ss`: ```text +scan TypeScript taint typed hostname into request options scan JavaScript taint Express params into SQL templates scan JavaScript taint nested object destructuring assignment scan taint only propagates through plain assignments @@ -231,6 +223,98 @@ subtrees. The ancestor-clean test mirrors `taint_field_sensitive4`: ## Resolved Recent Cases +`taint_object_funcdef_1` is now cleared: + +```sh +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_REGEX='^taint_object_funcdef_1$' LIST_MISMATCHES=1 MAX_DIFFS=320 tests/oracle/upstream-sweep.sh +``` + +Current result: + +```text +upstream-sweep: 1 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 1 compared +``` + +Target files: + +```text +/Users/user/mine/semgrep/tests/rules/taint_object_funcdef_1.yaml +/Users/user/mine/semgrep/tests/rules/taint_object_funcdef_1.ts +``` + +Rule shape: + +```yaml +pattern-sources: + - pattern: httpOptions.hostname + - pattern: opts.hostname +pattern-sinks: + - patterns: + - pattern: | + (...).request({...}) + - pattern-not: + pattern: | + (...).request({ + beforeRedirect: ($OPTS) => { + ... + sanitizeIP($OPTS.hostname) + ... + }, + ..., + lookup: getLookupFunction(..., sanitize) + }) +options: + symbolic_propagation: true +``` + +Relevant target shape: + +```typescript +const httpOptions = urlToHttpOptions(reqUrl); +const host: string = httpOptions.hostname; + +const req: http.ClientRequest = + (reqUrl.protocol.startsWith('https') ? https: http).request({ + host, + lookup: getLookupFunction(logger, Boolean(opts.useRoundRobinDns), sanitize), + beforeRedirect: (opts) => { + // no sanitizeIP call + }, + }) +``` + +Expected finding range is the whole request call expression from line 8, +column 33 to line 16, column 3. + +What changed: + +- `pattern: httpOptions.hostname` and `pattern: (...).request({...})` already + matched independently. +- The missing dataflow was the typed alias + `const host: string = httpOptions.hostname`; implicit assignment now binds + `$L=host` and `$R=httpOptions.hostname`. +- The broad request-call sink contains an unrelated nested arrow callback. + Token reach no longer rejects the whole sink just because `=>` appears in + the sink text; it only rejects tokens that occur solely inside function body + ranges. + +`taint_propagator1` is now cleared: + +```sh +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_REGEX='^taint_propagator1$' LIST_MISMATCHES=1 MAX_DIFFS=320 tests/oracle/upstream-sweep.sh +``` + +Current result: + +```text +upstream-sweep: 1 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 1 compared +``` + +This was cleared by the same token-reach refinement. In the previous full +sweep it missed three expected JavaScript findings in +`/Users/user/mine/semgrep/tests/rules/taint_propagator1.js`; the current full +sweep no longer lists it as a mismatch. + `taint_object_funcdef` is now cleared: ```sh @@ -793,10 +877,9 @@ What changed: ## Current Full-Sweep Frontier The current full sorted upstream sweep compared 241 rule/target pairs and has -these 23 mismatches: +these 21 mismatches: ```text -taint_object_funcdef_1 taint_param_default taint_param_default1 taint_param_default2 @@ -804,7 +887,6 @@ taint_param_default3 taint_param_source1 taint_param_source2 taint_param_source3 -taint_propagator1 taint_propagator2 taint_react taint_safe_comparisons @@ -824,57 +906,66 @@ vardef_assign_true2 Current full-sweep result: ```text -upstream-sweep: 216 passed, 23 mismatched, 0 jerboa errors, 2 current errors, 241 compared +upstream-sweep: 218 passed, 21 mismatched, 0 jerboa errors, 2 current errors, 241 compared ``` The two current errors are packaged-Semgrep oracle errors, not Jerboa scanner -errors. The command accidentally used `CASE_LIMIT=220`, which this script -ignores, so the result above is a full sweep. For a first-window guardrail, -use `MAX_CASES=220`. +errors. This result is from the full sweep command with no `MAX_CASES` limit. +For a first-window guardrail, use `MAX_CASES=220`. -## Immediate Next Case: taint_object_funcdef_1 +## Immediate Next Case: taint_param_default Focused command: ```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_REGEX='^taint_object_funcdef_1$' LIST_MISMATCHES=1 MAX_DIFFS=320 tests/oracle/upstream-sweep.sh +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_REGEX='^taint_param_default$' LIST_MISMATCHES=1 MAX_DIFFS=260 tests/oracle/upstream-sweep.sh ``` Current result: ```text -MISMATCH taint_object_funcdef_1 -@@ -1 +0,0 @@ --(finding "test" "/Users/user/mine/semgrep/tests/rules/taint_object_funcdef_1.ts" 8 33 209 16 3 556 "WARNING" "Semgrep found a match" "") +MISMATCH taint_param_default +@@ -1,4 +0,0 @@ +-(finding "request-session-with-http" "/Users/user/mine/semgrep/tests/rules/taint_param_default.py" 20 18 557 20 21 560 "WARNING" "Detected a request using 'http://'. This request will be unencrypted. Use 'https://' instead." "") +-(finding "request-session-with-http" "/Users/user/mine/semgrep/tests/rules/taint_param_default.py" 31 20 834 31 23 837 "WARNING" "Detected a request using 'http://'. This request will be unencrypted. Use 'https://' instead." "") +-(finding "request-session-with-http" "/Users/user/mine/semgrep/tests/rules/taint_param_default.py" 41 29 1117 41 32 1120 "WARNING" "Detected a request using 'http://'. This request will be unencrypted. Use 'https://' instead." "") +-(finding "request-session-with-http" "/Users/user/mine/semgrep/tests/rules/taint_param_default.py" 9 17 275 9 37 295 "WARNING" "Detected a request using 'http://'. This request will be unencrypted. Use 'https://' instead." "") upstream-sweep: 0 passed, 1 mismatched, 0 jerboa errors, 0 current errors, 1 compared ``` Rule and target files: ```text -/Users/user/mine/semgrep/tests/rules/taint_object_funcdef_1.yaml -/Users/user/mine/semgrep/tests/rules/taint_object_funcdef_1.ts +/Users/user/mine/semgrep/tests/rules/taint_param_default.yaml +/Users/user/mine/semgrep/tests/rules/taint_param_default.py ``` Why this should be next: -- It is adjacent to the case just cleared and likely reuses some of the route - callback and SQL sink groundwork. -- It is TypeScript and reports a much wider sink range, so do not assume the - `taint_object_funcdef` fix fully generalizes. -- Start by isolating source formula findings and sink formula findings exactly - as was done for `taint_object_funcdef`. +- It is now the first mismatch in the full sorted upstream sweep. +- It is the head of the `taint_param_default*` cluster, so a focused fix may + clear several adjacent cases. +- The expected findings cover direct literal calls, assigned URL variables, and + Python default parameter values flowing into `requests.Session().get/post` + and `.request(...)` sinks. +- Start by isolating the source formula: + `pattern-inside: "..."`, `pattern: $URL`, + `metavariable-pattern` with `pattern-regex: http://` and + `pattern-not-regex: .*://localhost`. +- Then isolate sink matching and focus for + `requests.Session(...).$W($SINK, ...)` and + `requests.Session(...).request($METHOD, $SINK, ...)`. Useful inspection commands: ```sh -sed -n '1,220p' /Users/user/mine/semgrep/tests/rules/taint_object_funcdef_1.yaml -sed -n '1,220p' /Users/user/mine/semgrep/tests/rules/taint_object_funcdef_1.ts +sed -n '1,220p' /Users/user/mine/semgrep/tests/rules/taint_param_default.yaml +sed -n '1,220p' /Users/user/mine/semgrep/tests/rules/taint_param_default.py ``` -The next likely question is whether Jerboa misses the TypeScript source -context, the sink range, or both. Avoid broad taint rewrites until that split -is known. +The next likely question is whether Jerboa misses string-literal source +formulas, symbolic/default-parameter propagation, session-method sinks, or a +combination. Keep the first local smoke narrow enough to distinguish those. ## Recently Cleared Case Notes: taint_object_funcdef --- a/lib/semgrep/scan.sls +++ b/lib/semgrep/scan.sls @@ -9070,6 +9070,7 @@ (string=? language "ts") (string=? language "tsx")) '("$L = $R" "$L[$I] = $R" "const $L = $R" "let $L = $R" "var $L = $R" + "const $L: $T = $R" "let $L: $T = $R" "var $L: $T = $R" "const { $L } = $R" "let { $L } = $R" "var { $L } = $R" "const { $FIELD: $L } = $R" "let { $FIELD: $L } = $R" "var { $FIELD: $L } = $R" "const { $FIELD: { $L } } = $R" @@ -9572,16 +9573,106 @@ (- (finding-start-offset source) container-start) (- (finding-end-offset source) container-start) (finding-focused-to-whole-binding? container))))))) + (def (token-occurrence-spans text token) + (let ([token-len (string-length token)]) + (if (= token-len 0) + '() + (let loop ([start 0] [acc '()]) + (let ([index (string-find-substring-from text token start)]) + (if index + (let ([end (+ index token-len)]) + (loop + (+ index 1) + (if (and (token-start-boundary? text index) + (token-end-boundary? text end)) + (cons (cons index end) acc) + acc))) + (reverse acc))))))) + (def (token-source-texts source-state source-text) + (let ([source (taint-state-finding source-state)]) + (if source + (unique-string-list + (cons + (finding-text source source-text) + (map (lambda (entry) + (metavariable-binding-text (cdr entry))) + (finding-metavars source)))) + '()))) + (def (range-contains-span? range span) + (and (<= (car range) (car span)) + (>= (cdr range) (cdr span)))) + (def (span-inside-any-range? span ranges) + (any? + (lambda (range) (range-contains-span? range span)) + ranges)) + (def (arrow-function-body-ranges text) + (let ([len (string-length text)]) + (let loop ([offset 0] [acc '()]) + (let ([arrow (string-find-substring-from text "=>" offset)]) + (if arrow + (let* ([body-start (skip-whitespace text (+ arrow 2))] + [body-end (and (< body-start len) + (if (char=? + (string-ref text body-start) + #\{) + (find-matching-close-brace + text + body-start) + len))] + [next (+ arrow 2)]) + (loop + next + (if body-end + (cons (cons body-start body-end) acc) + acc))) + (reverse acc)))))) + (def (function-keyword-body-ranges text) + (let loop ([offset 0] [acc '()]) + (let ([index (string-find-substring-from + text + "function" + offset)]) + (if index + (let* ([body-start (string-find-substring-from + text + "{" + index)] + [body-end (and body-start + (find-matching-close-brace + text + body-start))] + [next (+ index (string-length "function"))]) + (loop + next + (if body-end + (cons (cons body-start body-end) acc) + acc))) + (reverse acc))))) + (def (function-value-body-ranges text) + (append + (arrow-function-body-ranges text) + (function-keyword-body-ranges text))) (def (token-source-inside-function-value-sink? source-state sink source-text) (and (taint-state-token? source-state) - (let ([sink-text (finding-text sink source-text)]) - (or (string-find-substring sink-text "=>") - (sg-string-prefix? - "function" - (string-trim sink-text)))))) + (let* ([sink-text (finding-text sink source-text)] + [ranges (function-value-body-ranges sink-text)] + [spans (apply + append + (map (lambda (token) + (token-occurrence-spans + sink-text + token)) + (token-source-texts + source-state + source-text)))]) + (and (not (null? ranges)) + (not (null? spans)) + (all? + (lambda (span) (span-inside-any-range? span ranges)) + spans))))) (def (token-text-inside-python-conditional-test? text token) (let ([token-len (string-length token)]) (and (> token-len 0) --- a/src/.jerbuild-hashes +++ b/src/.jerbuild-hashes @@ -3,11 +3,11 @@ ("src/semgrep/output/json.ss" . "293881CFA2ADB7BC") ("src/semgrep/lang.ss" . "7E5441BD00A7F1D4") ("src/semgrep/parse/parse-target.ss" . "E74854DDDACF6BA") - ("src/semgrep/scan.ss" . "B22CC16B489E44F2") - ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1") - ("src/semgrep/output/text.ss" . "BE476CB84B807FBA") + ("src/semgrep/scan.ss" . "EB53C67D537DE9B3") ("src/semgrep/rule.ss" . "E12C108153C181FA") ("src/semgrep/schema/lang.ss" . "CAE2CA859C9A9FD0") + ("src/semgrep/output/text.ss" . "BE476CB84B807FBA") + ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1") ("src/semgrep/match/structural.ss" . "F7B63A9A6FA028B") ("src/semgrep/main.ss" . "A4EC9E7F2A09D25E") ("src/semgrep/cli.ss" . "D56FC2D2EB449BA6")) --- a/src/semgrep/scan.ss +++ b/src/semgrep/scan.ss @@ -9651,6 +9651,9 @@ "const $L = $R" "let $L = $R" "var $L = $R" + "const $L: $T = $R" + "let $L: $T = $R" + "var $L: $T = $R" "const { $L } = $R" "let { $L } = $R" "var { $L } = $R" @@ -10160,11 +10163,93 @@ (- (finding-end-offset source) container-start) (finding-focused-to-whole-binding? container))))))) +(def (token-occurrence-spans text token) + (let ([token-len (string-length token)]) + (if (= token-len 0) + '() + (let loop ([start 0] [acc '()]) + (let ([index (string-find-substring-from text token start)]) + (if index + (let ([end (+ index token-len)]) + (loop (+ index 1) + (if (and (token-start-boundary? text index) + (token-end-boundary? text end)) + (cons (cons index end) acc) + acc))) + (reverse acc))))))) + +(def (token-source-texts source-state source-text) + (let ([source (taint-state-finding source-state)]) + (if source + (unique-string-list + (cons (finding-text source source-text) + (map (lambda (entry) + (metavariable-binding-text (cdr entry))) + (finding-metavars source)))) + '()))) + +(def (range-contains-span? range span) + (and (<= (car range) (car span)) + (>= (cdr range) (cdr span)))) + +(def (span-inside-any-range? span ranges) + (any? (lambda (range) + (range-contains-span? range span)) + ranges)) + +(def (arrow-function-body-ranges text) + (let ([len (string-length text)]) + (let loop ([offset 0] [acc '()]) + (let ([arrow (string-find-substring-from text "=>" offset)]) + (if arrow + (let* ([body-start (skip-whitespace text (+ arrow 2))] + [body-end + (and (< body-start len) + (if (char=? (string-ref text body-start) #\{) + (find-matching-close-brace text body-start) + len))] + [next (+ arrow 2)]) + (loop next + (if body-end + (cons (cons body-start body-end) acc) + acc))) + (reverse acc)))))) + +(def (function-keyword-body-ranges text) + (let loop ([offset 0] [acc '()]) + (let ([index (string-find-substring-from text "function" offset)]) + (if index + (let* ([body-start (string-find-substring-from text "{" index)] + [body-end + (and body-start + (find-matching-close-brace text body-start))] + [next (+ index (string-length "function"))]) + (loop next + (if body-end + (cons (cons body-start body-end) acc) + acc))) + (reverse acc))))) + +(def (function-value-body-ranges text) + (append (arrow-function-body-ranges text) + (function-keyword-body-ranges text))) + (def (token-source-inside-function-value-sink? source-state sink source-text) (and (taint-state-token? source-state) - (let ([sink-text (finding-text sink source-text)]) - (or (string-find-substring sink-text "=>") - (sg-string-prefix? "function" (string-trim sink-text)))))) + (let* ([sink-text (finding-text sink source-text)] + [ranges (function-value-body-ranges sink-text)] + [spans + (apply append + (map (lambda (token) + (token-occurrence-spans sink-text token)) + (token-source-texts + source-state + source-text)))]) + (and (not (null? ranges)) + (not (null? spans)) + (all? (lambda (span) + (span-inside-any-range? span ranges)) + spans))))) (def (token-text-inside-python-conditional-test? text token) (let ([token-len (string-length token)]) --- a/tests/smoke.ss +++ b/tests/smoke.ss @@ -1582,6 +1582,19 @@ (check (map finding-start-line findings) => '(5 14)) (check (map finding-start-col findings) => '(31 31)))) +(test-case "scan TypeScript taint typed hostname into request options" + (let* ([taint-config + "rules:\n - id: demo.taint.ts.request.host\n mode: taint\n languages: [ts]\n message: request host taint\n severity: WARNING\n pattern-sources:\n - pattern: httpOptions.hostname\n pattern-sinks:\n - patterns:\n - pattern: |\n (...).request({...})\n - pattern-not:\n pattern: |\n (...).request({\n beforeRedirect: ($OPTS) => {\n ...\n sanitizeIP($OPTS.hostname)\n ...\n },\n ...,\n lookup: getLookupFunction(..., sanitize)\n })\n options:\n symbolic_propagation: true\n"] + [findings + (scan-config-string + taint-config + "ts" + "demo.ts" + "import * as http from 'http';\n\nconst httpOptions = urlToHttpOptions(reqUrl);\nconst host: string = httpOptions.hostname;\n\nconst req: http.ClientRequest = (reqUrl.protocol.startsWith('https') ? https: http).request({\n host, port, method, path, rejectUnauthorized: false,\n lookup: getLookupFunction(logger, Boolean(opts.useRoundRobinDns), sanitize),\n beforeRedirect: (opts) => {\n // no sanitizeIP call\n },\n})\n")]) + (check (length findings) => 1) + (check (finding-start-line (car findings)) => 6) + (check (finding-start-col (car findings)) => 33))) + (test-case "scan TypeScript taint source after trailing pattern-inside" (let* ([taint-config "rules:\n - id: demo.taint.ts.record\n mode: taint\n languages: [typescript]\n message: record taint\n severity: WARNING\n pattern-sources:\n - patterns:\n - pattern-inside: |\n $DBQUERY.findByPk(...)\n ...\n - pattern: $USER.dataValues\n pattern-sinks:\n - patterns:\n - pattern-inside: |\n $PUG = require('pug')\n ...\n - pattern-either:\n - pattern: $PUG.compile(...)\n"]