use tldr_core::{Language, TaintInfo, TaintSinkType, TaintSourceType};
#[path = "common/integ_helpers.rs"]
mod common;
use common::{analyze_with_ssa, assert_source_to_eval_flow as common_assert_source_to_eval_flow};
#[test]
fn nextjs_response_redirect_open_redirect_via_compute_taint() {
let src = "\
export async function POST(request) {
const body = await request.json();
return NextResponse.redirect(body.url);
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "POST", false);
let sink_lines: Vec<_> = result
.sinks
.iter()
.filter(|s| matches!(s.sink_type, TaintSinkType::OpenRedirect))
.map(|s| s.line)
.collect();
assert!(
!sink_lines.is_empty(),
"expected at least one OpenRedirect sink for NextResponse.redirect; \
got sinks={:?}",
result.sinks
);
}
#[test]
fn nextjs_response_json_reflected_xss_via_compute_taint() {
let src = "\
export async function POST(request) {
const data = await request.json();
return NextResponse.json(data);
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "POST", false);
let sink_lines: Vec<_> = result
.sinks
.iter()
.filter(|s| matches!(s.sink_type, TaintSinkType::HtmlOutput))
.map(|s| s.line)
.collect();
assert!(
!sink_lines.is_empty(),
"expected at least one HtmlOutput sink for NextResponse.json; \
got sinks={:?}",
result.sinks
);
}
#[test]
fn nextjs_redirect_helper_via_compute_taint() {
let src = "\
import { redirect } from 'next/navigation';
export async function action(formData) {
const url = formData.get('next');
redirect(url);
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "action", false);
let sink_lines: Vec<_> = result
.sinks
.iter()
.filter(|s| matches!(s.sink_type, TaintSinkType::OpenRedirect))
.map(|s| s.line)
.collect();
assert!(
!sink_lines.is_empty(),
"expected at least one OpenRedirect sink for bare redirect() helper; \
got sinks={:?}",
result.sinks
);
}
#[test]
fn nextjs_dangerously_set_inner_html_via_compute_taint() {
let src = "\
export default function Page({ params }) {
const html = params.html;
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "Page", false);
let sink_lines: Vec<_> = result
.sinks
.iter()
.filter(|s| matches!(s.sink_type, TaintSinkType::FileWrite))
.map(|s| s.line)
.collect();
assert!(
!sink_lines.is_empty(),
"expected at least one FileWrite sink for dangerouslySetInnerHTML; \
got sinks={:?}",
result.sinks
);
}
#[test]
fn fastify_reply_send_reflected_via_compute_taint() {
let src = "\
async function echo(request, reply) {
const data = request.body;
reply.send(data);
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "echo", false);
let sink_lines: Vec<_> = result
.sinks
.iter()
.filter(|s| matches!(s.sink_type, TaintSinkType::HtmlOutput))
.map(|s| s.line)
.collect();
assert!(
!sink_lines.is_empty(),
"expected at least one HtmlOutput sink for reply.send; \
got sinks={:?}",
result.sinks
);
}
#[test]
fn fastify_reply_redirect_via_compute_taint() {
let src = "\
async function go(request, reply) {
const target = request.body.next;
reply.redirect(target);
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "go", false);
let sink_lines: Vec<_> = result
.sinks
.iter()
.filter(|s| matches!(s.sink_type, TaintSinkType::OpenRedirect))
.map(|s| s.line)
.collect();
assert!(
!sink_lines.is_empty(),
"expected at least one OpenRedirect sink for reply.redirect; \
got sinks={:?}",
result.sinks
);
}
#[test]
fn fastify_reply_header_injection_via_compute_taint() {
let src = "\
async function setHeader(request, reply) {
const v = request.headers['x-forwarded'];
reply.header('X-Echo', v);
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "setHeader", false);
let sink_lines: Vec<_> = result
.sinks
.iter()
.filter(|s| matches!(s.sink_type, TaintSinkType::FileWrite))
.map(|s| s.line)
.collect();
assert!(
!sink_lines.is_empty(),
"expected at least one FileWrite sink for reply.header; \
got sinks={:?}",
result.sinks
);
}
#[test]
fn nestjs_res_send_reflected_via_compute_taint() {
let src = "\
async function handler(req, res) {
const v = req.body.v;
res.send(v);
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "handler", false);
let sink_lines: Vec<_> = result
.sinks
.iter()
.filter(|s| matches!(s.sink_type, TaintSinkType::HtmlOutput))
.map(|s| s.line)
.collect();
assert!(
!sink_lines.is_empty(),
"expected at least one HtmlOutput sink for res.send; \
got sinks={:?}",
result.sinks
);
}
#[test]
fn nestjs_res_redirect_open_redirect_via_compute_taint() {
let src = "\
async function handler(req, res) {
const next = req.query.next;
res.redirect(next);
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "handler", false);
let sink_lines: Vec<_> = result
.sinks
.iter()
.filter(|s| matches!(s.sink_type, TaintSinkType::OpenRedirect))
.map(|s| s.line)
.collect();
assert!(
!sink_lines.is_empty(),
"expected at least one OpenRedirect sink for res.redirect; \
got sinks={:?}",
result.sinks
);
}
#[test]
fn nestjs_response_builder_send_via_compute_taint() {
let src = "\
async function handler(req, response) {
const v = req.body.v;
response.send(v);
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "handler", false);
let sink_lines: Vec<_> = result
.sinks
.iter()
.filter(|s| matches!(s.sink_type, TaintSinkType::HtmlOutput))
.map(|s| s.line)
.collect();
assert!(
!sink_lines.is_empty(),
"expected at least one HtmlOutput sink for Response.send (builder); \
got sinks={:?}",
result.sinks
);
}
#[test]
fn nestjs_response_builder_redirect_via_compute_taint() {
let src = "\
async function handler(req, Response) {
const next = req.query.url;
Response.redirect(next);
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "handler", false);
let sink_lines: Vec<_> = result
.sinks
.iter()
.filter(|s| matches!(s.sink_type, TaintSinkType::OpenRedirect))
.map(|s| s.line)
.collect();
assert!(
!sink_lines.is_empty(),
"expected at least one OpenRedirect sink for Response.redirect (builder); \
got sinks={:?}",
result.sinks
);
}
fn assert_source_to_eval_flow(result: &TaintInfo, expected_source: TaintSourceType) {
common_assert_source_to_eval_flow(result, expected_source);
}
#[test]
fn nextjs_request_json_to_eval_via_compute_taint() {
let src = "\
export async function POST(request) {
const data = await request.json();
eval(data.code);
return new Response(JSON.stringify({ok: true}));
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "POST", false);
assert_source_to_eval_flow(&result, TaintSourceType::HttpBody);
}
#[test]
fn nextjs_request_text_to_eval_via_compute_taint() {
let src = "\
export async function POST(request) {
const raw = await request.text();
eval(raw);
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "POST", false);
assert_source_to_eval_flow(&result, TaintSourceType::HttpBody);
}
#[test]
fn nextjs_request_formdata_to_eval_via_compute_taint() {
let src = "\
export async function POST(request) {
const fd = await request.formData();
eval(fd.get('script'));
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "POST", false);
assert_source_to_eval_flow(&result, TaintSourceType::HttpBody);
}
#[test]
fn nextjs_searchparams_get_to_eval_via_compute_taint() {
let src = "\
export async function GET(request) {
const q = request.nextUrl.searchParams.get('q');
eval(q);
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "GET", false);
assert_source_to_eval_flow(&result, TaintSourceType::HttpParam);
}
#[test]
fn fastify_request_body_to_eval_via_compute_taint() {
let src = "\
import Fastify from 'fastify';
async function fastifyEcho(request, reply) {
const cmd = request.body.cmd;
eval(cmd);
}
";
let result = analyze_with_ssa(
src,
Language::TypeScript,
"fastifyEcho",
false,
);
assert_source_to_eval_flow(&result, TaintSourceType::HttpBody);
}
#[test]
fn fastify_request_query_to_eval_via_compute_taint() {
let src = "\
async function handler(request, reply) {
const q = request.query.q;
eval(q);
}
";
let result = analyze_with_ssa(src, Language::TypeScript, "handler", false);
assert_source_to_eval_flow(&result, TaintSourceType::HttpParam);
}
#[test]
fn nestjs_request_body_to_eval_manual_unwrap_via_compute_taint() {
let src = "\
import { Controller, Post, Req } from '@nestjs/common';
async function nestCreate(request) {
const body = request.body;
eval(body.script);
}
";
let result = analyze_with_ssa(
src,
Language::TypeScript,
"nestCreate",
false,
);
assert_source_to_eval_flow(&result, TaintSourceType::HttpBody);
}