Merge branch 'main' into redsun82/rust-regenerate-models

This commit is contained in:
Paolo Tranquilli
2025-06-20 17:22:49 +02:00
committed by GitHub
13 changed files with 181 additions and 17 deletions

View File

@@ -39,6 +39,8 @@ import java.util.stream.Stream;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.semmle.js.extractor.tsconfig.TsConfigJson;
import com.semmle.js.extractor.tsconfig.CompilerOptions;
import com.semmle.js.dependencies.AsyncFetcher;
import com.semmle.js.dependencies.DependencyResolver;
import com.semmle.js.dependencies.packument.PackageJson;
@@ -745,6 +747,26 @@ public class AutoBuild {
.filter(p -> !isFileTooLarge(p))
.sorted(PATH_ORDERING)
.collect(Collectors.toCollection(() -> new LinkedHashSet<>()));
// gather all output directories specified in tsconfig.json files
final List<Path> outDirs = new ArrayList<>();
for (Path cfg : tsconfigFiles) {
try {
String txt = new WholeIO().read(cfg);
TsConfigJson root = new Gson().fromJson(txt, TsConfigJson.class);
if (root != null && root.getCompilerOptions() != null) {
if (root.getCompilerOptions().getOutDir() == null) {
// no outDir specified, so skip this tsconfig.json
continue;
}
Path odir = cfg.getParent().resolve(root.getCompilerOptions().getOutDir()).toAbsolutePath().normalize();
outDirs.add(odir);
}
} catch (Exception e) {
// ignore malformed tsconfig or missing fields
}
}
// exclude files in output directories as configured in tsconfig.json
filesToExtract.removeIf(f -> outDirs.stream().anyMatch(od -> f.startsWith(od)));
DependencyInstallationResult dependencyInstallationResult = DependencyInstallationResult.empty;
if (!tsconfigFiles.isEmpty()) {
@@ -796,9 +818,19 @@ public class AutoBuild {
*/
private boolean isFileDerivedFromTypeScriptFile(Path path, Set<Path> extractedFiles) {
String name = path.getFileName().toString();
if (!name.endsWith(".js"))
// only skip JS variants when a corresponding TS/TSX file was already extracted
if (!(name.endsWith(".js")
|| name.endsWith(".cjs")
|| name.endsWith(".mjs")
|| name.endsWith(".jsx")
|| name.endsWith(".cjsx")
|| name.endsWith(".mjsx"))) {
return false;
String stem = name.substring(0, name.length() - ".js".length());
}
// strip off extension
int dot = name.lastIndexOf('.');
String stem = dot != -1 ? name.substring(0, dot) : name;
// if a TS/TSX file with same base name was extracted, skip this file
for (String ext : FileType.TYPESCRIPT.getExtensions()) {
if (extractedFiles.contains(path.getParent().resolve(stem + ext))) {
return true;
@@ -1154,7 +1186,7 @@ protected DependencyInstallationResult preparePackagesAndDependencies(Set<Path>
}
// extract TypeScript projects from 'tsconfig.json'
if (typeScriptMode == TypeScriptMode.FULL
if (typeScriptMode != TypeScriptMode.NONE
&& treatAsTSConfig(file.getFileName().toString())
&& !excludes.contains(file)
&& isFileIncluded(file)) {

View File

@@ -0,0 +1,13 @@
package com.semmle.js.extractor.tsconfig;
public class CompilerOptions {
private String outDir;
public String getOutDir() {
return outDir;
}
public void setOutDir(String outDir) {
this.outDir = outDir;
}
}

View File

@@ -0,0 +1,13 @@
package com.semmle.js.extractor.tsconfig;
public class TsConfigJson {
private CompilerOptions compilerOptions;
public CompilerOptions getCompilerOptions() {
return compilerOptions;
}
public void setCompilerOptions(CompilerOptions compilerOptions) {
this.compilerOptions = compilerOptions;
}
}

View File

@@ -135,6 +135,7 @@ public class AutoBuildTests {
FileExtractors extractors) {
for (Path f : files) {
actual.add(f.toString());
extractedFiles.add(f);
}
}
@@ -175,7 +176,7 @@ public class AutoBuildTests {
@Test
public void basicTest() throws IOException {
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "tst.ts");
addFile(true, LGTM_SRC, "tst.html");
addFile(true, LGTM_SRC, "tst.xsjs");
@@ -203,6 +204,43 @@ public class AutoBuildTests {
runTest();
}
@Test
public void skipJsFilesDerivedFromTypeScriptFiles() throws IOException {
// JS-derived files (.js, .cjs, .mjs, .jsx, .cjsx, .mjsx) should be skipped when TS indexing
envVars.put("LGTM_INDEX_TYPESCRIPT", "basic");
// Add TypeScript sources
addFile(true, LGTM_SRC, "foo.ts");
addFile(true, LGTM_SRC, "bar.tsx");
// Add derived JS variants (should be skipped)
addFile(false, LGTM_SRC, "foo.js");
addFile(false, LGTM_SRC, "bar.jsx");
addFile(false, LGTM_SRC, "foo.cjs");
addFile(false, LGTM_SRC, "foo.mjs");
addFile(false, LGTM_SRC, "bar.cjsx");
addFile(false, LGTM_SRC, "bar.mjsx");
// A normal JS file without TS counterpart should be extracted
addFile(true, LGTM_SRC, "normal.js");
runTest();
}
@Test
public void skipFilesInTsconfigOutDir() throws IOException {
envVars.put("LGTM_INDEX_TYPESCRIPT", "basic");
// Files under outDir in tsconfig.json should be excluded
// Create tsconfig.json with outDir set to "dist"
addFile(true, LGTM_SRC, "tsconfig.json");
Path config = Paths.get(LGTM_SRC.toString(), "tsconfig.json");
Files.write(config,
"{\"compilerOptions\":{\"outDir\":\"dist\"}}".getBytes(StandardCharsets.UTF_8));
// Add files outside outDir (should be extracted)
addFile(true, LGTM_SRC, "src", "app.ts");
addFile(true, LGTM_SRC, "main.js");
// Add files under dist/outDir (should be skipped)
addFile(false, LGTM_SRC, "dist", "generated.js");
addFile(false, LGTM_SRC, "dist", "sub", "x.js");
runTest();
}
@Test
public void includeFile() throws IOException {
envVars.put("LGTM_INDEX_INCLUDE", "tst.js");

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The JavaScript extractor now skips generated JavaScript files if the original TypeScript files are already present. It also skips any files in the output directory specified in the `compilerOptions` part of the `tsconfig.json` file.

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* Improved data flow tracking through middleware to handle default value and similar patterns.
* Added `req._parsedUrl` as a remote input source.

View File

@@ -925,7 +925,7 @@ module Routing {
private DataFlow::Node getAnAccessPathRhs(Node base, int n, string path) {
// Assigned in the body of a route handler function, which is a middleware
exists(RouteHandler handler | base = handler |
result = AccessPath::getAnAssignmentTo(handler.getParameter(n).ref(), path) and
result = AccessPath::getAnAssignmentTo(handler.getParameter(n).ref(), path).getALocalSource() and
(
exists(handler.getAContinuationInvocation())
or

View File

@@ -618,9 +618,9 @@ module Express {
kind = "body" and
this = ref.getAPropertyRead("body")
or
// `req.path`
// `req.path` and `req._parsedUrl`
kind = "url" and
this = ref.getAPropertyRead("path")
this = ref.getAPropertyRead(["path", "_parsedUrl"])
)
}

View File

@@ -6,6 +6,10 @@
| apollo.serverSide.ts:8:39:8:64 | get(fil ... => {}) | apollo.serverSide.ts:7:36:7:44 | { files } | apollo.serverSide.ts:8:43:8:50 | file.url | The $@ of this request depends on a $@. | apollo.serverSide.ts:8:43:8:50 | file.url | URL | apollo.serverSide.ts:7:36:7:44 | { files } | user-provided value |
| apollo.serverSide.ts:18:37:18:62 | get(fil ... => {}) | apollo.serverSide.ts:17:34:17:42 | { files } | apollo.serverSide.ts:18:41:18:48 | file.url | The $@ of this request depends on a $@. | apollo.serverSide.ts:18:41:18:48 | file.url | URL | apollo.serverSide.ts:17:34:17:42 | { files } | user-provided value |
| axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | The $@ of this request depends on a $@. | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | endpoint | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | user-provided value |
| serverSide2.js:17:28:17:47 | axios.get(targetUrl) | serverSide2.js:10:25:10:31 | req.url | serverSide2.js:17:38:17:46 | targetUrl | The $@ of this request depends on a $@. | serverSide2.js:17:38:17:46 | targetUrl | URL | serverSide2.js:10:25:10:31 | req.url | user-provided value |
| serverSide2.js:20:29:20:49 | axios.g ... etUrl1) | serverSide2.js:9:43:9:56 | req._parsedUrl | serverSide2.js:20:39:20:48 | targetUrl1 | The $@ of this request depends on a $@. | serverSide2.js:20:39:20:48 | targetUrl1 | URL | serverSide2.js:9:43:9:56 | req._parsedUrl | user-provided value |
| serverSide2.js:23:29:23:49 | axios.g ... etUrl2) | serverSide2.js:22:24:22:30 | req.url | serverSide2.js:23:39:23:48 | targetUrl2 | The $@ of this request depends on a $@. | serverSide2.js:23:39:23:48 | targetUrl2 | URL | serverSide2.js:22:24:22:30 | req.url | user-provided value |
| serverSide2.js:26:29:26:49 | axios.g ... etUrl3) | serverSide2.js:11:24:11:30 | req.url | serverSide2.js:26:39:26:48 | targetUrl3 | The $@ of this request depends on a $@. | serverSide2.js:26:39:26:48 | targetUrl3 | URL | serverSide2.js:11:24:11:30 | req.url | user-provided value |
| serverSide.js:18:5:18:20 | request(tainted) | serverSide.js:14:29:14:35 | req.url | serverSide.js:18:13:18:19 | tainted | The $@ of this request depends on a $@. | serverSide.js:18:13:18:19 | tainted | URL | serverSide.js:14:29:14:35 | req.url | user-provided value |
| serverSide.js:20:5:20:24 | request.get(tainted) | serverSide.js:14:29:14:35 | req.url | serverSide.js:20:17:20:23 | tainted | The $@ of this request depends on a $@. | serverSide.js:20:17:20:23 | tainted | URL | serverSide.js:14:29:14:35 | req.url | user-provided value |
| serverSide.js:24:5:24:20 | request(options) | serverSide.js:14:29:14:35 | req.url | serverSide.js:23:19:23:25 | tainted | The $@ of this request depends on a $@. | serverSide.js:23:19:23:25 | tainted | URL | serverSide.js:14:29:14:35 | req.url | user-provided value |
@@ -63,6 +67,18 @@ edges
| axiosInterceptors.serverSide.js:19:21:19:28 | req.body | axiosInterceptors.serverSide.js:19:11:19:17 | { url } | provenance | |
| axiosInterceptors.serverSide.js:20:5:20:25 | userProvidedUrl | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | provenance | |
| axiosInterceptors.serverSide.js:20:23:20:25 | url | axiosInterceptors.serverSide.js:20:5:20:25 | userProvidedUrl | provenance | |
| serverSide2.js:9:34:9:63 | qs.pars ... .query) | serverSide2.js:19:24:19:51 | req.par ... rsedUrl | provenance | |
| serverSide2.js:9:43:9:56 | req._parsedUrl | serverSide2.js:9:34:9:63 | qs.pars ... .query) | provenance | |
| serverSide2.js:10:25:10:31 | req.url | serverSide2.js:16:23:16:41 | req.parsedQuery.url | provenance | |
| serverSide2.js:11:24:11:30 | req.url | serverSide2.js:25:24:25:41 | req.SomeObject.url | provenance | |
| serverSide2.js:16:11:16:41 | targetUrl | serverSide2.js:17:38:17:46 | targetUrl | provenance | |
| serverSide2.js:16:23:16:41 | req.parsedQuery.url | serverSide2.js:16:11:16:41 | targetUrl | provenance | |
| serverSide2.js:19:11:19:55 | targetUrl1 | serverSide2.js:20:39:20:48 | targetUrl1 | provenance | |
| serverSide2.js:19:24:19:51 | req.par ... rsedUrl | serverSide2.js:19:11:19:55 | targetUrl1 | provenance | |
| serverSide2.js:22:11:22:36 | targetUrl2 | serverSide2.js:23:39:23:48 | targetUrl2 | provenance | |
| serverSide2.js:22:24:22:30 | req.url | serverSide2.js:22:11:22:36 | targetUrl2 | provenance | |
| serverSide2.js:25:11:25:47 | targetUrl3 | serverSide2.js:26:39:26:48 | targetUrl3 | provenance | |
| serverSide2.js:25:24:25:41 | req.SomeObject.url | serverSide2.js:25:11:25:47 | targetUrl3 | provenance | |
| serverSide.js:14:9:14:52 | tainted | serverSide.js:18:13:18:19 | tainted | provenance | |
| serverSide.js:14:9:14:52 | tainted | serverSide.js:20:17:20:23 | tainted | provenance | |
| serverSide.js:14:9:14:52 | tainted | serverSide.js:23:19:23:25 | tainted | provenance | |
@@ -163,6 +179,22 @@ nodes
| axiosInterceptors.serverSide.js:19:21:19:28 | req.body | semmle.label | req.body |
| axiosInterceptors.serverSide.js:20:5:20:25 | userProvidedUrl | semmle.label | userProvidedUrl |
| axiosInterceptors.serverSide.js:20:23:20:25 | url | semmle.label | url |
| serverSide2.js:9:34:9:63 | qs.pars ... .query) | semmle.label | qs.pars ... .query) |
| serverSide2.js:9:43:9:56 | req._parsedUrl | semmle.label | req._parsedUrl |
| serverSide2.js:10:25:10:31 | req.url | semmle.label | req.url |
| serverSide2.js:11:24:11:30 | req.url | semmle.label | req.url |
| serverSide2.js:16:11:16:41 | targetUrl | semmle.label | targetUrl |
| serverSide2.js:16:23:16:41 | req.parsedQuery.url | semmle.label | req.parsedQuery.url |
| serverSide2.js:17:38:17:46 | targetUrl | semmle.label | targetUrl |
| serverSide2.js:19:11:19:55 | targetUrl1 | semmle.label | targetUrl1 |
| serverSide2.js:19:24:19:51 | req.par ... rsedUrl | semmle.label | req.par ... rsedUrl |
| serverSide2.js:20:39:20:48 | targetUrl1 | semmle.label | targetUrl1 |
| serverSide2.js:22:11:22:36 | targetUrl2 | semmle.label | targetUrl2 |
| serverSide2.js:22:24:22:30 | req.url | semmle.label | req.url |
| serverSide2.js:23:39:23:48 | targetUrl2 | semmle.label | targetUrl2 |
| serverSide2.js:25:11:25:47 | targetUrl3 | semmle.label | targetUrl3 |
| serverSide2.js:25:24:25:41 | req.SomeObject.url | semmle.label | req.SomeObject.url |
| serverSide2.js:26:39:26:48 | targetUrl3 | semmle.label | targetUrl3 |
| serverSide.js:14:9:14:52 | tainted | semmle.label | tainted |
| serverSide.js:14:19:14:42 | url.par ... , true) | semmle.label | url.par ... , true) |
| serverSide.js:14:29:14:35 | req.url | semmle.label | req.url |

View File

@@ -0,0 +1,27 @@
const express = require('express');
const axios = require('axios');
const qs = require('qs');
const app = express();
const PORT = 3000;
app.use((req, res, next) => {
req.parsedQueryFromParsedUrl = qs.parse(req._parsedUrl.query); // $Source[js/request-forgery]
req.parsedQuery.url = req.url || {}; // $Source[js/request-forgery]
req.SomeObject.url = req.url; // $Source[js/request-forgery]
next();
});
app.get('/proxy', async (req, res) => {
const targetUrl = req.parsedQuery.url;
const response = await axios.get(targetUrl); // $Alert[js/request-forgery]
const targetUrl1 = req.parsedQueryFromParsedUrl.url;
const response1 = await axios.get(targetUrl1); // $Alert[js/request-forgery]
const targetUrl2 = req.url || {}; // $Source[js/request-forgery]
const response2 = await axios.get(targetUrl2); // $Alert[js/request-forgery]
const targetUrl3 = req.SomeObject.url || {};
const response3 = await axios.get(targetUrl3); // $Alert[js/request-forgery]
});

View File

@@ -1,29 +1,29 @@
multiplePathResolutions
| main.rs:218:14:218:17 | libc | file://:0:0:0:0 | Crate(libc@0.2.173) |
| main.rs:218:14:218:17 | libc | file://:0:0:0:0 | Crate(libc@0.2.172) |
| main.rs:218:14:218:17 | libc | file://:0:0:0:0 | Crate(libc@0.2.174) |
| main.rs:218:14:218:25 | ...::malloc | file://:0:0:0:0 | fn malloc |
| main.rs:218:14:218:25 | ...::malloc | file://:0:0:0:0 | fn malloc |
| main.rs:219:13:219:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.173) |
| main.rs:219:13:219:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.172) |
| main.rs:219:13:219:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.174) |
| main.rs:219:13:219:24 | ...::malloc | file://:0:0:0:0 | fn malloc |
| main.rs:219:13:219:24 | ...::malloc | file://:0:0:0:0 | fn malloc |
| main.rs:220:13:220:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.173) |
| main.rs:220:13:220:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.172) |
| main.rs:220:13:220:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.174) |
| main.rs:220:13:220:31 | ...::aligned_alloc | file://:0:0:0:0 | fn aligned_alloc |
| main.rs:220:13:220:31 | ...::aligned_alloc | file://:0:0:0:0 | fn aligned_alloc |
| main.rs:221:13:221:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.173) |
| main.rs:221:13:221:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.172) |
| main.rs:221:13:221:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.174) |
| main.rs:221:13:221:31 | ...::aligned_alloc | file://:0:0:0:0 | fn aligned_alloc |
| main.rs:221:13:221:31 | ...::aligned_alloc | file://:0:0:0:0 | fn aligned_alloc |
| main.rs:222:13:222:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.173) |
| main.rs:222:13:222:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.172) |
| main.rs:222:13:222:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.174) |
| main.rs:222:13:222:24 | ...::calloc | file://:0:0:0:0 | fn calloc |
| main.rs:222:13:222:24 | ...::calloc | file://:0:0:0:0 | fn calloc |
| main.rs:223:13:223:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.173) |
| main.rs:223:13:223:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.172) |
| main.rs:223:13:223:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.174) |
| main.rs:223:13:223:24 | ...::calloc | file://:0:0:0:0 | fn calloc |
| main.rs:223:13:223:24 | ...::calloc | file://:0:0:0:0 | fn calloc |
| main.rs:224:13:224:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.173) |
| main.rs:224:13:224:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.172) |
| main.rs:224:13:224:16 | libc | file://:0:0:0:0 | Crate(libc@0.2.174) |
| main.rs:224:13:224:25 | ...::realloc | file://:0:0:0:0 | fn realloc |
| main.rs:224:13:224:25 | ...::realloc | file://:0:0:0:0 | fn realloc |

View File

@@ -4,9 +4,9 @@ version = 4
[[package]]
name = "libc"
version = "0.2.173"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "test"

View File

@@ -1,3 +1,3 @@
qltest_use_nightly: true
qltest_dependencies:
- libc = { version = "0.2.11" }
- libc = { version = "0.2.174" }