From 02d4e7af8538c4231da242bb0fe1c9a46e7afdf1 Mon Sep 17 00:00:00 2001 From: MC Date: Tue, 27 Oct 2026 18:28:25 -0400 Subject: [PATCH] feat(rules): TypeScript/JavaScript XSS, path traversal and open redirect rules; more SSRF sinks (pack v1.11) Written from the confirmed TS/JS findings the rule-mining report lists as uncovered (XSS 37, path traversal 10, open redirect 4). On eight real checkouts the rules hit 35 times, including all three confirmed findings present there (console LogsPage.tsx, SharedResourceWarning.js, backend/src/routes/serve.ts). 0.40.0. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 10 ++ VERSION | 2 +- pyproject.toml | 2 +- .../opengrep-rules/README.md | 38 ++++ .../typescript/cross-site-scripting.tsx | 82 +++++++++ .../typescript/cross-site-scripting.yaml | 168 ++++++++++++++++++ .../typescript/open-redirect.ts | 23 +++ .../typescript/open-redirect.yaml | 96 ++++++++++ .../typescript/path-traversal.ts | 38 ++++ .../typescript/path-traversal.yaml | 88 +++++++++ .../opengrep-rules/typescript/ssrf.ts | 9 + .../opengrep-rules/typescript/ssrf.yaml | 16 ++ uv.lock | 2 +- 13 files changed, 571 insertions(+), 3 deletions(-) create mode 100644 src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/cross-site-scripting.tsx create mode 100644 src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/cross-site-scripting.yaml create mode 100644 src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/open-redirect.ts create mode 100644 src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/open-redirect.yaml create mode 100644 src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/path-traversal.ts create mode 100644 src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/path-traversal.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ccac97..2ec026c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to traust-engine are documented here. +## [0.40.0] + +- **TypeScript/JavaScript rule tranche (rule pack v1.11).** New rules for the + largest uncovered TS/JS clusters in the confirmed-finding ledger: XSS + (`dangerouslySetInnerHTML`, DOM HTML sinks, `rehype-raw` without + sanitizing, reflected Express responses), path traversal (request values to + fs paths, `sendFile`, `download`) and open redirect (server redirects and + client navigation from address-bar values); SSRF gains http(s).request, + the remaining axios methods, got, superagent, needle and undici sinks. + ## [0.39.3] - Preserve supplied finding remediation effort and independent external diff --git a/VERSION b/VERSION index e0c2d65..9b0025a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.39.3 +0.40.0 diff --git a/pyproject.toml b/pyproject.toml index 4eb895c..c0c4f2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "traust-engine" -version = "0.39.3" +version = "0.40.0" license = "Apache-2.0" description = "Continuous-ops service for the AI security harness, on traust-core" requires-python = ">=3.11" diff --git a/src/traust_engine/data/secure-code-audit/opengrep-rules/README.md b/src/traust_engine/data/secure-code-audit/opengrep-rules/README.md index a054e54..056be60 100644 --- a/src/traust_engine/data/secure-code-audit/opengrep-rules/README.md +++ b/src/traust_engine/data/secure-code-audit/opengrep-rules/README.md @@ -161,3 +161,41 @@ bound text, so a "contains" test needs a leading `.*` (`(?is).*\blike\b`), and it cannot sit directly under `pattern-either` — wrap each branch in its own `patterns:`. + +v1.11 (2026-10-27): **TypeScript/JavaScript tranche, from the ledger.** The +rule-mining report's largest uncovered TS/JS clusters were XSS (CWE-79, 37 +confirmed findings in about 30 repositories), path traversal (CWE-22, 10) +and open redirect (CWE-601, 4); each rule is written from the cited +findings' evidence. Test, spec, mock and mock-backend files are excluded. +- **XSS (`typescript/cross-site-scripting.yaml`).** + - `xss-dangerously-set-inner-html`: a computed `__html` value with no + sanitizer. Translation strings (`t(...)`, `i18n*`) and values named as + sanitized are not matched. + - `xss-dom-html-sink`: `innerHTML`/`outerHTML` assignment, + `insertAdjacentHTML`, `document.write` (also on an opened window) and + jQuery `.html()` with a computed, unsanitized value. + - `xss-markdown-raw-html` (WARNING): `rehype-raw` without a later + `rehype-sanitize`. + - `xss-reflected-response-taint`: a request value in an Express + `send`/`write`/`end` string body (sent as text/html). +- **Path traversal (`typescript/path-traversal.yaml`).** + `path-traversal-fs-taint`: a request value (query, params, body, a + destructured body field, or `req.url`/`req.path`) reaching the path + argument of an fs read, write, stream or remove, or `res.sendFile` / + `res.download`; `path.basename` and filename sanitizers clear it. +- **Open redirect (`typescript/open-redirect.yaml`).** + `open-redirect-response-taint`: a request value as the target of + `res.redirect` or a `Location` header. `open-redirect-client-navigation` + (WARNING): an address-bar value (`URLSearchParams.get`, `location.search`, + `location.hash`) as the URL of `location.assign/replace/href` or + `window.open`. +- **SSRF (`typescript/ssrf.yaml`).** More outbound sinks: `http(s).request`, + the remaining axios methods, got, superagent, needle and undici. + +Measured on eight checkouts (console, backstage, mlflow, keycloak, syndesis, +data-science-pipelines, argo-workflows, feast): 35 hits, including all three +confirmed TS/JS findings present in those checkouts (console `LogsPage.tsx`, +`SharedResourceWarning.js`, `backend/src/routes/serve.ts`). Precision is +unmeasured until the facts are judged (`/mine-ledger`). Not covered: +`javascript:` URLs bound to `href`, markdown-to-jsx and marked renderer +overrides, and sanitize-then-transform pipelines; these stay with review. diff --git a/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/cross-site-scripting.tsx b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/cross-site-scripting.tsx new file mode 100644 index 0000000..2dd066a --- /dev/null +++ b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/cross-site-scripting.tsx @@ -0,0 +1,82 @@ +import DOMPurify from "dompurify"; +import ReactMarkdown from "react-markdown"; +import rehypeRaw from "rehype-raw"; +import rehypeSanitize from "rehype-sanitize"; +import $ from "jquery"; + +export const Entry = ({ response }: { response: string }) => ( + // ruleid: traust-ts-xss-dangerously-set-inner-html +
+); + +export const Clean = ({ response }: { response: string }) => ( + // ok: traust-ts-xss-dangerously-set-inner-html +
+); + +// ok: traust-ts-xss-dangerously-set-inner-html +export const Fixed = () =>
static" }} />; + +export const Help = ({ t, i18nDescription, sanitizedContent }: any) => ( + <> + {/* ok: traust-ts-xss-dangerously-set-inner-html */} +

+ {/* ok: traust-ts-xss-dangerously-set-inner-html */} +

+ {/* ok: traust-ts-xss-dangerously-set-inner-html */} +

+ +); + +export const Warning = ({ t, resources }: any) => ( + // ruleid: traust-ts-xss-dangerously-set-inner-html +

+); + +export function openRawTab(logs: string): void { + const rawWindow = window.open("about:blank"); + // ruleid: traust-ts-xss-dom-html-sink + rawWindow?.document.write(`

${logs}
`); + // ok: traust-ts-xss-dom-html-sink + document.write("

loading

"); +} + +export async function loadConfig(response: Response): Promise { + const script = await response.text(); + const configScript = document.createElement("script"); + // ruleid: traust-ts-xss-dom-html-sink + configScript.innerHTML = script; + const box = document.getElementById("box")!; + // ok: traust-ts-xss-dom-html-sink + box.innerHTML = ""; + // ok: traust-ts-xss-dom-html-sink + box.innerHTML = DOMPurify.sanitize(script); + // ruleid: traust-ts-xss-dom-html-sink + box.insertAdjacentHTML("beforeend", script); + // ruleid: traust-ts-xss-dom-html-sink + $("#answers").html(marked.parse(script)); + // ok: traust-ts-xss-dom-html-sink + box.textContent = script; +} + +export const Docs = ({ text }: { text: string }) => ( + // ruleid: traust-ts-xss-markdown-raw-html + {text} +); + +export const SafeDocs = ({ text }: { text: string }) => ( + // ok: traust-ts-xss-markdown-raw-html + {text} +); + +export function bundle(req: any, res: any): void { + const sha = req.params.sha; + // ruleid: traust-ts-xss-reflected-response-taint + res.status(404).send(`Bundle ${sha} not found`); + // ruleid: traust-ts-xss-reflected-response-taint + res.send("unknown filetype " + req.query.filetype); + // ok: traust-ts-xss-reflected-response-taint + res.status(404).send(`Bundle ${escapeHtml(sha)} not found`); + // ok: traust-ts-xss-reflected-response-taint + res.json({ error: `Bundle ${sha} not found` }); +} diff --git a/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/cross-site-scripting.yaml b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/cross-site-scripting.yaml new file mode 100644 index 0000000..1530353 --- /dev/null +++ b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/cross-site-scripting.yaml @@ -0,0 +1,168 @@ +rules: + - id: traust-ts-xss-dangerously-set-inner-html + languages: [typescript, javascript] + paths: + exclude: + - "*.test.*" + - "*.spec.*" + - "*/__tests__/*" + - "*/__mocks__/*" + - "*/mock-backend/*" + - "*/mocks/*" + - "*/test/*" + - "*/tests/*" + severity: ERROR + message: >- + A computed value is rendered as raw HTML through dangerouslySetInnerHTML + with no sanitizer — markup from log lines, WebSocket messages or API + data runs as script in the viewer's session. Render it as text, or pass + it through DOMPurify.sanitize first. CWE-79. + metadata: + cwe: ["CWE-79"] + category: cross-site-scripting + asvs: ["V5"] + confidence: MEDIUM + references: + - "CWE-79 DOM XSS — unsanitized dangerouslySetInnerHTML" + patterns: + - pattern: "{__html: $X}" + - pattern-not: "{__html: \"...\"}" + - pattern-not: "{__html: $SAN.sanitize(...)}" + - pattern-not: "{__html: sanitize(...)}" + - pattern-not: "{__html: sanitizeHtml(...)}" + - pattern-not: "{__html: DOMPurify.sanitize(...)}" + - pattern-not: "{__html: xss(...)}" + # translation strings and already-sanitized values: `t(...)`, `i18nX`, `props.i18nX(...)`, + # `sanitizedContent` + - metavariable-regex: + metavariable: $X + regex: (?is)^(?!\s*(?:[\w$]+\.)*(?:t|i18n\w*|sanitiz\w*|purif\w*)\b) + + - id: traust-ts-xss-dom-html-sink + languages: [typescript, javascript] + paths: + exclude: + - "*.test.*" + - "*.spec.*" + - "*/__tests__/*" + - "*/__mocks__/*" + - "*/mock-backend/*" + - "*/mocks/*" + - "*/test/*" + - "*/tests/*" + severity: ERROR + message: >- + A computed value is written into the page as HTML (innerHTML, + outerHTML, insertAdjacentHTML, document.write or jQuery .html()) with + no sanitizer — markup in it executes as script. Use textContent, or + sanitize with DOMPurify first. CWE-79. + metadata: + cwe: ["CWE-79"] + category: cross-site-scripting + asvs: ["V5"] + confidence: MEDIUM + references: + - "CWE-79 DOM XSS — HTML sink fed a computed value" + patterns: + - pattern-either: + - pattern: $E.innerHTML = $X + - pattern: $E.outerHTML = $X + - pattern: $E.insertAdjacentHTML($POS, $X) + - pattern: document.write($X) + - pattern: document.writeln($X) + - pattern: $W.document.write($X) + - pattern: $J($SEL).html($X) + - pattern-not: $E.innerHTML = "..." + - pattern-not: $E.innerHTML = "" + - pattern-not: $E.outerHTML = "..." + - pattern-not: $E.insertAdjacentHTML($POS, "...") + - pattern-not: document.write("...") + - pattern-not: $W.document.write("...") + - pattern-not: $J($SEL).html("...") + - metavariable-pattern: + metavariable: $X + patterns: + - pattern-not: $SAN.sanitize(...) + - pattern-not: sanitize(...) + - pattern-not: sanitizeHtml(...) + - pattern-not: escapeHtml(...) + + - id: traust-ts-xss-markdown-raw-html + languages: [typescript, javascript] + paths: + exclude: + - "*.test.*" + - "*.spec.*" + - "*/__tests__/*" + - "*/__mocks__/*" + - "*/mock-backend/*" + - "*/mocks/*" + - "*/test/*" + - "*/tests/*" + severity: WARNING + message: >- + Markdown is rendered with raw HTML enabled (rehype-raw) and no + rehype-sanitize step — HTML embedded in the Markdown source reaches the + page unfiltered. Add rehype-sanitize after rehype-raw, or drop raw HTML. + CWE-79. + metadata: + cwe: ["CWE-79"] + category: cross-site-scripting + asvs: ["V5"] + confidence: MEDIUM + references: + - "CWE-79 stored XSS — Markdown renderer passing raw HTML" + patterns: + - pattern: "[..., rehypeRaw, ...]" + - pattern-not: "[..., rehypeRaw, ..., rehypeSanitize, ...]" + - pattern-not: "[..., rehypeRaw, ..., [rehypeSanitize, ...], ...]" + + - id: traust-ts-xss-reflected-response-taint + languages: [typescript, javascript] + paths: + exclude: + - "*.test.*" + - "*.spec.*" + - "*/__tests__/*" + - "*/__mocks__/*" + - "*/mock-backend/*" + - "*/mocks/*" + - "*/test/*" + - "*/tests/*" + severity: ERROR + message: >- + A request value is echoed into a response body that Express sends as + text/html — markup in the path, query or body runs in the caller's + browser (reflected XSS). Return JSON, set a text/plain content type, or + HTML-escape the value. CWE-79. + metadata: + cwe: ["CWE-79"] + category: cross-site-scripting + asvs: ["V5"] + confidence: MEDIUM + references: + - "CWE-79 reflected XSS — request value in an HTML response body" + mode: taint + pattern-sources: + - pattern-either: + - pattern: $REQ.query.$P + - pattern: $REQ.params.$P + - pattern: $REQ.body.$P + - pattern: $REQ.query[$K] + - pattern: $REQ.params[$K] + pattern-sanitizers: + - pattern-either: + - pattern: escapeHtml(...) + - pattern: $X.escape(...) + - pattern: encodeURIComponent(...) + - pattern: JSON.stringify(...) + pattern-sinks: + - patterns: + - pattern-either: + - pattern: $RES.send(`...`) + - pattern: $RES.send("..." + $X) + - pattern: $RES.send($X + "...") + - pattern: $RES.status($C).send(`...`) + - pattern: $RES.status($C).send("..." + $X) + - pattern: $RES.write(`...`) + - pattern: $RES.end(`...`) diff --git a/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/open-redirect.ts b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/open-redirect.ts new file mode 100644 index 0000000..f61ac03 --- /dev/null +++ b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/open-redirect.ts @@ -0,0 +1,23 @@ +export function callback(req: any, res: any): void { + // ruleid: traust-ts-open-redirect-response-taint + res.redirect(req.query.next); + // ruleid: traust-ts-open-redirect-response-taint + res.redirect(302, req.query.returnTo); + // ok: traust-ts-open-redirect-response-taint + res.redirect("/login"); + // ruleid: traust-ts-open-redirect-response-taint + res.setHeader("Location", req.query.next); +} + +export function afterLogin(): void { + const params = new URLSearchParams(window.location.search); + const target = params.get("redirect"); + // ruleid: traust-ts-open-redirect-client-navigation + window.location.assign(target); + // ok: traust-ts-open-redirect-client-navigation + window.location.assign("/home"); + // ruleid: traust-ts-open-redirect-client-navigation + window.open(window.location.hash.slice(1), "_blank"); + // ok: traust-ts-open-redirect-client-navigation + window.open("/graph", params.get("target") || "_top"); +} diff --git a/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/open-redirect.yaml b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/open-redirect.yaml new file mode 100644 index 0000000..016b544 --- /dev/null +++ b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/open-redirect.yaml @@ -0,0 +1,96 @@ +rules: + - id: traust-ts-open-redirect-response-taint + languages: [typescript, javascript] + paths: + exclude: + - "*.test.*" + - "*.spec.*" + - "*/__tests__/*" + - "*/__mocks__/*" + - "*/mock-backend/*" + - "*/mocks/*" + - "*/test/*" + - "*/tests/*" + severity: ERROR + message: >- + A request value is used as a redirect target — the caller can send users + to any site with this host's link (phishing, OAuth code or token + theft). Redirect only to relative paths or an allowlisted origin. + CWE-601. + metadata: + cwe: ["CWE-601"] + category: open-redirect + asvs: ["V5"] + confidence: HIGH + references: + - "CWE-601 open redirect — request value as the redirect target" + mode: taint + pattern-sources: + - pattern-either: + - pattern: $REQ.query.$P + - pattern: $REQ.params.$P + - pattern: $REQ.body.$P + - pattern: $REQ.query[$K] + pattern-sinks: + - patterns: + - pattern-either: + - pattern: $RES.redirect($URL) + - pattern: $RES.redirect($CODE, $URL) + - patterns: + - pattern: $RES.setHeader($H, $URL) + - metavariable-regex: + metavariable: $H + regex: (?i)^["']location["']$ + - focus-metavariable: $URL + + - id: traust-ts-open-redirect-client-navigation + languages: [typescript, javascript] + paths: + exclude: + - "*.test.*" + - "*.spec.*" + - "*/__tests__/*" + - "*/__mocks__/*" + - "*/mock-backend/*" + - "*/mocks/*" + - "*/test/*" + - "*/tests/*" + severity: WARNING + message: >- + The page navigates to a URL taken from the address bar (query string or + fragment) — a crafted link sends the user to any site, or runs a + javascript: URL in this origin. Navigate only to relative paths or an + allowlisted origin and scheme. CWE-601. + metadata: + cwe: ["CWE-601", "CWE-79"] + category: open-redirect + asvs: ["V5"] + confidence: MEDIUM + references: + - "CWE-601 client-side open redirect — address-bar value as the navigation target" + mode: taint + pattern-sources: + - pattern-either: + - pattern: new URLSearchParams(...).get(...) + - pattern: $U.searchParams.get(...) + - patterns: + - pattern: $PARAMS.get(...) + - metavariable-regex: + metavariable: $PARAMS + regex: (?i)^(params|searchParams|urlParams|queryParams|query|search)$ + - pattern: window.location.search + - pattern: window.location.hash + - pattern: location.search + - pattern: location.hash + pattern-sinks: + - patterns: + - pattern-either: + - pattern: window.location.href = $URL + - pattern: window.location = $URL + - pattern: location.href = $URL + - pattern: window.location.assign($URL) + - pattern: window.location.replace($URL) + - pattern: location.assign($URL) + - pattern: location.replace($URL) + - pattern: window.open($URL, ...) + - focus-metavariable: $URL diff --git a/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/path-traversal.ts b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/path-traversal.ts new file mode 100644 index 0000000..bb6953b --- /dev/null +++ b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/path-traversal.ts @@ -0,0 +1,38 @@ +import fs, { createReadStream } from "fs"; +import fsp from "fs/promises"; +import path from "path"; + +export async function saveConfig(req: any, res: any): Promise { + const { config, name } = req.body; + const filepath = path.join(CONFIGS_DIR, name); + // ruleid: traust-ts-path-traversal-fs-taint + await fsp.writeFile(filepath, config); + // ok: traust-ts-path-traversal-fs-taint + await fsp.writeFile(path.join(CONFIGS_DIR, "latest.yaml"), req.body.config); + res.json({ ok: true }); +} + +export async function readLog(req: any, res: any): Promise { + const logFile = path.join(LOGS_DIR, `${req.params.id}.log`); + // ruleid: traust-ts-path-traversal-fs-taint + const logs = await fsp.readFile(logFile, "utf8"); + // ok: traust-ts-path-traversal-fs-taint + const safe = await fsp.readFile(path.join(LOGS_DIR, path.basename(req.params.id)), "utf8"); + res.send(logs + safe); +} + +export function servePublic(req: any, res: any): void { + const url = req.url.split("?")[0]; + const filePath = `${publicFolder}${url}`; + // ruleid: traust-ts-path-traversal-fs-taint + createReadStream(filePath).pipe(res); +} + +export function serve(req: any, res: any): void { + // ruleid: traust-ts-path-traversal-fs-taint + res.sendFile(path.join(STATIC_DIR, req.query.file)); + // ok: traust-ts-path-traversal-fs-taint + res.sendFile(path.join(STATIC_DIR, "index.html")); + // ok: traust-ts-path-traversal-fs-taint + fs.readFileSync(path.join(__dirname, "template.html"), "utf8"); +} diff --git a/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/path-traversal.yaml b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/path-traversal.yaml new file mode 100644 index 0000000..3d30829 --- /dev/null +++ b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/path-traversal.yaml @@ -0,0 +1,88 @@ +rules: + - id: traust-ts-path-traversal-fs-taint + languages: [typescript, javascript] + paths: + exclude: + - "*.test.*" + - "*.spec.*" + - "*/__tests__/*" + - "*/__mocks__/*" + - "*/mock-backend/*" + - "*/mocks/*" + - "*/test/*" + - "*/tests/*" + severity: ERROR + message: >- + A request value becomes part of a filesystem path that is read, written, + removed or served — `../` segments in it reach files outside the + intended directory. Reduce it to a basename, or resolve the joined path + and check it is still inside the base directory before using it. CWE-22. + metadata: + cwe: ["CWE-22", "CWE-73"] + category: path-traversal + asvs: ["V12"] + confidence: HIGH + references: + - "CWE-22 path traversal — request value joined into a filesystem path" + mode: taint + pattern-sources: + - pattern-either: + - pattern: $REQ.query.$P + - pattern: $REQ.params.$P + - pattern: $REQ.body.$P + - pattern: $REQ.query[$K] + - pattern: $REQ.params[$K] + - patterns: + - pattern-either: + - pattern: $R.url + - pattern: $R.originalUrl + - pattern: $R.path + - metavariable-regex: + metavariable: $R + regex: ^(req|request)$ + - patterns: + - pattern-inside: "const {...} = $REQ.$PART;" + - pattern: $V + - metavariable-regex: + metavariable: $PART + regex: ^(query|params|body)$ + pattern-sanitizers: + - pattern-either: + - pattern: path.basename(...) + - pattern: basename(...) + - pattern: sanitizeFilename(...) + - pattern: sanitize(...) + pattern-sinks: + - patterns: + - pattern-either: + - pattern: $FS.readFile($P, ...) + - pattern: $FS.readFileSync($P, ...) + - pattern: $FS.writeFile($P, ...) + - pattern: $FS.writeFileSync($P, ...) + - pattern: $FS.appendFile($P, ...) + - pattern: $FS.createReadStream($P, ...) + - pattern: $FS.createWriteStream($P, ...) + - pattern: $FS.unlink($P, ...) + - pattern: $FS.unlinkSync($P, ...) + - pattern: $FS.rm($P, ...) + - pattern: $FS.rmSync($P, ...) + - pattern: $FS.promises.readFile($P, ...) + - pattern: $FS.promises.writeFile($P, ...) + - pattern: readFile($P, ...) + - pattern: readFileSync($P, ...) + - pattern: writeFile($P, ...) + - pattern: writeFileSync($P, ...) + - pattern: createReadStream($P, ...) + - pattern: createWriteStream($P, ...) + - pattern: unlink($P, ...) + - pattern: unlinkSync($P, ...) + - pattern: rmSync($P, ...) + - focus-metavariable: $P + - patterns: + - pattern-either: + - pattern: $RES.sendFile($P, ...) + - pattern: $RES.download($P, ...) + - metavariable-regex: + metavariable: $RES + regex: ^(res|response|ctx)$ + - focus-metavariable: $P diff --git a/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/ssrf.ts b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/ssrf.ts index 194ce83..f6f4846 100644 --- a/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/ssrf.ts +++ b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/ssrf.ts @@ -10,4 +10,13 @@ export async function proxy(req: any, res: any): Promise { // ok: traust-ts-ssrf-request-taint await fetch("https://api.openshift.com/healthz"); + + // ruleid: traust-ts-ssrf-request-taint + await got.get(req.body.webhookUrl); + + // ruleid: traust-ts-ssrf-request-taint + https.request(req.query.callback, () => undefined); + + // ruleid: traust-ts-ssrf-request-taint + await axios.request({ url: req.body.target }); } diff --git a/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/ssrf.yaml b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/ssrf.yaml index beba6e5..b9820b9 100644 --- a/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/ssrf.yaml +++ b/src/traust_engine/data/secure-code-audit/opengrep-rules/typescript/ssrf.yaml @@ -28,3 +28,19 @@ rules: - pattern: axios(...) - pattern: http.get(...) - pattern: https.get(...) + - pattern: http.request(...) + - pattern: https.request(...) + - pattern: axios.put(...) + - pattern: axios.patch(...) + - pattern: axios.delete(...) + - pattern: axios.head(...) + - pattern: axios.request(...) + - pattern: got(...) + - pattern: got.get(...) + - pattern: got.post(...) + - pattern: superagent.get(...) + - pattern: superagent.post(...) + - pattern: needle(...) + - pattern: needle.get(...) + - pattern: undici.request(...) + - pattern: undici.fetch(...) diff --git a/uv.lock b/uv.lock index bb553a6..6fd181a 100644 --- a/uv.lock +++ b/uv.lock @@ -2043,7 +2043,7 @@ dependencies = [ [[package]] name = "traust-engine" -version = "0.39.3" +version = "0.40.0" source = { editable = "." } dependencies = [ { name = "pyyaml" },