diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5133575..06f52b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,17 @@
All notable changes to traust-engine are documented here.
+## [0.47.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. The
+ same rules ship on `development` (0.40.0).
+
## [0.46.0]
- **Code-verified versions can count as affected, with evidence.**
diff --git a/VERSION b/VERSION
index 3010923..421ab54 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.46.0
+0.47.0
diff --git a/pyproject.toml b/pyproject.toml
index 5dc5fbf..8ef9586 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "traust-engine"
-version = "0.46.0"
+version = "0.47.0"
license = "Apache-2.0"
description = "Processing core for the AI security harness: scanners, data management, analysis, format handlers"
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 222834a..96b1820 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2086,7 +2086,7 @@ dependencies = [
[[package]]
name = "traust-engine"
-version = "0.46.0"
+version = "0.47.0"
source = { editable = "." }
dependencies = [
{ name = "jsonschema" },