mirror of
https://github.com/github/codeql.git
synced 2026-04-25 00:35:20 +02:00
Merge branch 'main' into amammad-js-CodeInjection_execa
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
load("@//:dist.bzl", "dist")
|
||||
load("@rules_pkg//pkg:mappings.bzl", "pkg_files")
|
||||
load("@//buildutils-internal:zipmerge.bzl", "zipmerge")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
alias(
|
||||
@@ -9,3 +13,36 @@ alias(
|
||||
name = "dbscheme-stats",
|
||||
actual = "//javascript/ql/lib:dbscheme-stats",
|
||||
)
|
||||
|
||||
pkg_files(
|
||||
name = "dbscheme-group",
|
||||
srcs = [
|
||||
":dbscheme",
|
||||
":dbscheme-stats",
|
||||
],
|
||||
strip_prefix = None,
|
||||
)
|
||||
|
||||
dist(
|
||||
name = "javascript-extractor-pack",
|
||||
srcs = [
|
||||
":dbscheme-group",
|
||||
"//javascript/downgrades",
|
||||
"//javascript/externs",
|
||||
"//javascript/extractor:tools-extractor",
|
||||
"@//language-packs/javascript:resources",
|
||||
],
|
||||
prefix = "javascript",
|
||||
)
|
||||
|
||||
# We have to zipmerge in the typescript parser wrapper, as it's generated by a genrule
|
||||
# and we don't know a list of its output files. Therefore, we sidestep the
|
||||
# rules_pkg tooling here, and generate the zip for the language pack manually.
|
||||
zipmerge(
|
||||
name = "javascript",
|
||||
srcs = [
|
||||
":javascript-extractor-pack.zip",
|
||||
"//javascript/extractor/lib/typescript",
|
||||
],
|
||||
out = "javascript.zip",
|
||||
)
|
||||
|
||||
11
javascript/downgrades/BUILD.bazel
Normal file
11
javascript/downgrades/BUILD.bazel
Normal file
@@ -0,0 +1,11 @@
|
||||
load("@//:dist.bzl", "pack_zip")
|
||||
|
||||
pack_zip(
|
||||
name = "downgrades",
|
||||
srcs = glob(
|
||||
["**/*"],
|
||||
exclude = ["BUILD.bazel"],
|
||||
),
|
||||
prefix = "downgrades",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
11
javascript/externs/BUILD.bazel
Normal file
11
javascript/externs/BUILD.bazel
Normal file
@@ -0,0 +1,11 @@
|
||||
load("@//:dist.bzl", "pack_zip")
|
||||
|
||||
pack_zip(
|
||||
name = "externs",
|
||||
srcs = glob(
|
||||
["**/*"],
|
||||
exclude = ["BUILD.bazel"],
|
||||
),
|
||||
prefix = "tools/data/externs",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
63
javascript/extractor/BUILD.bazel
Normal file
63
javascript/extractor/BUILD.bazel
Normal file
@@ -0,0 +1,63 @@
|
||||
load("@//:common.bzl", "codeql_fat_jar", "codeql_java_project")
|
||||
load("@rules_pkg//pkg:mappings.bzl", "pkg_files")
|
||||
|
||||
java_library(
|
||||
name = "deps",
|
||||
visibility = [":__subpackages__"],
|
||||
exports = [
|
||||
"@//extractor:html",
|
||||
"@//extractor:yaml",
|
||||
"@//resources/lib/java:commons-compress",
|
||||
"@//resources/lib/java:gson",
|
||||
"@//resources/lib/java:jericho-html",
|
||||
"@//resources/lib/java:slf4j-api",
|
||||
"@//resources/lib/java:snakeyaml",
|
||||
"@//third_party:jackson",
|
||||
"@//third_party:logback",
|
||||
"@//util-java7",
|
||||
"@//util-java8",
|
||||
],
|
||||
)
|
||||
|
||||
codeql_java_project(
|
||||
name = "extractor",
|
||||
deps = [
|
||||
":deps",
|
||||
],
|
||||
)
|
||||
|
||||
pkg_files(
|
||||
name = "javascript-extractor-resources",
|
||||
srcs = glob(["resources/**"]),
|
||||
strip_prefix = "resources",
|
||||
)
|
||||
|
||||
codeql_fat_jar(
|
||||
name = "extractor-javascript",
|
||||
srcs = [
|
||||
":extractor",
|
||||
"@//extractor:html",
|
||||
"@//extractor:xml-trap-writer",
|
||||
"@//extractor:yaml",
|
||||
"@//resources/lib/java:commons-compress",
|
||||
"@//resources/lib/java:gson",
|
||||
"@//resources/lib/java:jericho-html",
|
||||
"@//resources/lib/java:slf4j-api",
|
||||
"@//resources/lib/java:snakeyaml",
|
||||
"@//third_party:jackson",
|
||||
"@//third_party:logback",
|
||||
"@//util-java7",
|
||||
"@//util-java8",
|
||||
],
|
||||
files = [":javascript-extractor-resources"],
|
||||
main_class = "com.semmle.js.extractor.Main",
|
||||
)
|
||||
|
||||
pkg_files(
|
||||
name = "tools-extractor",
|
||||
srcs = [
|
||||
":extractor-javascript",
|
||||
],
|
||||
prefix = "tools",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
55
javascript/extractor/lib/typescript/BUILD.bazel
Normal file
55
javascript/extractor/lib/typescript/BUILD.bazel
Normal file
@@ -0,0 +1,55 @@
|
||||
load("@//:common.bzl", "on_windows")
|
||||
|
||||
# Builds a zip file of the compiled typscript-parser-wrapper and its dependencies.
|
||||
genrule(
|
||||
name = "typescript",
|
||||
srcs = [
|
||||
"tsconfig.json",
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
] + glob(["src/**"]),
|
||||
outs = ["javascript.zip"],
|
||||
cmd = "\n".join([
|
||||
# the original working directory is not preserved anywhere, but needs to be accessible, as
|
||||
# all paths are relative to this
|
||||
# unfortunately, we need to change the working directory to run npm.
|
||||
"export BAZEL_ROOT=$$(pwd)",
|
||||
# we need a temp dir, and unfortunately, $TMPDIR is not set on Windows
|
||||
"export TEMP=$$(mktemp -d)",
|
||||
# Add node to the path so that npm run can find it - it's calling env node
|
||||
"export PATH=$$BAZEL_ROOT/$$(dirname $(execpath @nodejs//:node_bin)):$$PATH",
|
||||
"export NPM=$$BAZEL_ROOT/$(execpath @nodejs//:npm_bin)",
|
||||
# npm has a global cache which doesn't work on macos, where absolute paths aren't filtered out by the sandbox.
|
||||
# Therefore, set a temporary cache directory.
|
||||
"export NPM_CONFIG_USERCONFIG=$$TEMP/npmrc",
|
||||
"$$NPM config set cache $$TEMP/npm",
|
||||
"$$NPM config set update-notifier false",
|
||||
"rm -rf $(RULEDIR)/inputs",
|
||||
"cp -L -R $$(dirname $(execpath package.json)) $(RULEDIR)/inputs",
|
||||
"cd $(RULEDIR)/inputs",
|
||||
"$$NPM install",
|
||||
"$$NPM run build",
|
||||
"rm -rf node_modules",
|
||||
# Install again with only runtime deps
|
||||
"$$NPM install --prod",
|
||||
"mv node_modules build/",
|
||||
"mkdir -p javascript/tools/typescript-parser-wrapper",
|
||||
"mv build/* javascript/tools/typescript-parser-wrapper",
|
||||
"",
|
||||
]) + on_windows(
|
||||
" && ".join([
|
||||
"$$BAZEL_ROOT/$(execpath @bazel_tools//tools/zip:zipper) cC $$(cygpath -w $$BAZEL_ROOT/$@) $$(find javascript -name '*' -print)",
|
||||
"rm -rf $$TEMP",
|
||||
]),
|
||||
" && ".join([
|
||||
"$$BAZEL_ROOT/$(execpath @bazel_tools//tools/zip:zipper) cC $$BAZEL_ROOT/$@ $$(find javascript -name '*' -print)",
|
||||
"rm -rf $$TEMP",
|
||||
]),
|
||||
),
|
||||
tools = [
|
||||
"@bazel_tools//tools/zip:zipper",
|
||||
"@nodejs//:node_bin",
|
||||
"@nodejs//:npm_bin",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -6,7 +6,7 @@
|
||||
"": {
|
||||
"name": "typescript-parser-wrapper",
|
||||
"dependencies": {
|
||||
"typescript": "5.2.2"
|
||||
"typescript": "5.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.15.3"
|
||||
@@ -20,9 +20,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
||||
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
|
||||
"integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "typescript-parser-wrapper",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"typescript": "5.2.2"
|
||||
"typescript": "5.3.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --project tsconfig.json",
|
||||
|
||||
@@ -224,7 +224,6 @@ const astProperties: string[] = [
|
||||
"argument",
|
||||
"argumentExpression",
|
||||
"arguments",
|
||||
"assertClause",
|
||||
"assertsModifier",
|
||||
"asteriskToken",
|
||||
"attributes",
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/node@18.15.3":
|
||||
version "18.15.3"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz"
|
||||
integrity sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==
|
||||
|
||||
typescript@5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz"
|
||||
integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
|
||||
@@ -1 +0,0 @@
|
||||
yarn-offline-mirror "./yarn-mirror"
|
||||
5
javascript/extractor/parser-tests/BUILD.bazel
Normal file
5
javascript/extractor/parser-tests/BUILD.bazel
Normal file
@@ -0,0 +1,5 @@
|
||||
filegroup(
|
||||
name = "parser-tests",
|
||||
srcs = glob(["**/*"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -314,8 +314,9 @@ public class ESNextParser extends JSXParser {
|
||||
this.parseExportSpecifiersMaybe(specifiers, exports);
|
||||
}
|
||||
Literal source = (Literal) this.parseExportFrom(specifiers, null, true);
|
||||
Expression assertion = this.parseImportOrExportAssertionAndSemicolon();
|
||||
return this.finishNode(new ExportNamedDeclaration(exportStart, null, specifiers, source, assertion));
|
||||
Expression attributes = this.parseImportOrExportAttributesAndSemicolon();
|
||||
return this.finishNode(
|
||||
new ExportNamedDeclaration(exportStart, null, specifiers, source, attributes));
|
||||
}
|
||||
|
||||
return super.parseExportRest(exportStart, exports);
|
||||
@@ -331,8 +332,9 @@ public class ESNextParser extends JSXParser {
|
||||
List<ExportSpecifier> specifiers = CollectionUtil.makeList(nsSpec);
|
||||
this.parseExportSpecifiersMaybe(specifiers, exports);
|
||||
Literal source = (Literal) this.parseExportFrom(specifiers, null, true);
|
||||
Expression assertion = this.parseImportOrExportAssertionAndSemicolon();
|
||||
return this.finishNode(new ExportNamedDeclaration(exportStart, null, specifiers, source, assertion));
|
||||
Expression attributes = this.parseImportOrExportAttributesAndSemicolon();
|
||||
return this.finishNode(
|
||||
new ExportNamedDeclaration(exportStart, null, specifiers, source, attributes));
|
||||
}
|
||||
|
||||
return super.parseExportAll(exportStart, starLoc, exports);
|
||||
|
||||
@@ -3447,7 +3447,7 @@ public class Parser {
|
||||
Statement declaration;
|
||||
List<ExportSpecifier> specifiers;
|
||||
Expression source = null;
|
||||
Expression assertion = null;
|
||||
Expression attributes = null;
|
||||
if (this.shouldParseExportStatement()) {
|
||||
declaration = this.parseStatement(true, false);
|
||||
if (declaration == null) return null;
|
||||
@@ -3463,10 +3463,10 @@ public class Parser {
|
||||
declaration = null;
|
||||
specifiers = this.parseExportSpecifiers(exports);
|
||||
source = parseExportFrom(specifiers, source, false);
|
||||
assertion = parseImportOrExportAssertionAndSemicolon();
|
||||
attributes = parseImportOrExportAttributesAndSemicolon();
|
||||
}
|
||||
return this.finishNode(
|
||||
new ExportNamedDeclaration(loc, declaration, specifiers, (Literal) source, assertion));
|
||||
new ExportNamedDeclaration(loc, declaration, specifiers, (Literal) source, attributes));
|
||||
}
|
||||
|
||||
/** Parses the 'from' clause of an export, not including the assertion or semicolon. */
|
||||
@@ -3494,8 +3494,8 @@ public class Parser {
|
||||
protected ExportDeclaration parseExportAll(
|
||||
SourceLocation loc, Position starLoc, Set<String> exports) {
|
||||
Expression source = parseExportFrom(null, null, true);
|
||||
Expression assertion = parseImportOrExportAssertionAndSemicolon();
|
||||
return this.finishNode(new ExportAllDeclaration(loc, (Literal) source, assertion));
|
||||
Expression attributes = parseImportOrExportAttributesAndSemicolon();
|
||||
return this.finishNode(new ExportAllDeclaration(loc, (Literal) source, attributes));
|
||||
}
|
||||
|
||||
private void checkExport(Set<String> exports, String name, Position pos) {
|
||||
@@ -3560,10 +3560,12 @@ public class Parser {
|
||||
return parseImportRest(loc);
|
||||
}
|
||||
|
||||
protected Expression parseImportOrExportAssertionAndSemicolon() {
|
||||
protected Expression parseImportOrExportAttributesAndSemicolon() {
|
||||
Expression result = null;
|
||||
if (!this.eagerlyTrySemicolon()) {
|
||||
this.expectContextual("assert");
|
||||
if (!this.eatContextual("assert")) {
|
||||
this.expect(TokenType._with);
|
||||
}
|
||||
result = this.parseObj(false, null);
|
||||
this.semicolon();
|
||||
}
|
||||
@@ -3583,9 +3585,9 @@ public class Parser {
|
||||
if (this.type != TokenType.string) this.unexpected();
|
||||
source = (Literal) this.parseExprAtom(null);
|
||||
}
|
||||
Expression assertion = this.parseImportOrExportAssertionAndSemicolon();
|
||||
Expression attributes = this.parseImportOrExportAttributesAndSemicolon();
|
||||
if (specifiers == null) return null;
|
||||
return this.finishNode(new ImportDeclaration(loc, specifiers, source, assertion));
|
||||
return this.finishNode(new ImportDeclaration(loc, specifiers, source, attributes));
|
||||
}
|
||||
|
||||
// Parses a comma-separated list of module imports.
|
||||
|
||||
@@ -943,12 +943,12 @@ public class FlowParser extends ESNextParser {
|
||||
// `export type { foo, bar };`
|
||||
List<ExportSpecifier> specifiers = this.parseExportSpecifiers(exports);
|
||||
this.parseExportFrom(specifiers, null, false);
|
||||
this.parseImportOrExportAssertionAndSemicolon();
|
||||
this.parseImportOrExportAttributesAndSemicolon();
|
||||
return null;
|
||||
} else if (this.eat(TokenType.star)) {
|
||||
if (this.eatContextual("as")) this.parseIdent(true);
|
||||
this.parseExportFrom(null, null, true);
|
||||
this.parseImportOrExportAssertionAndSemicolon();
|
||||
this.parseImportOrExportAttributesAndSemicolon();
|
||||
return null;
|
||||
} else {
|
||||
// `export type Foo = Bar;`
|
||||
|
||||
@@ -14,7 +14,10 @@ public class DynamicImport extends Expression {
|
||||
return source;
|
||||
}
|
||||
|
||||
/** Returns the second "argument" provided to the import, such as <code>{ assert: { type: "json" }}</code>. */
|
||||
/**
|
||||
* Returns the second "argument" provided to the import, such as <code>{ "with": { type: "json" }}
|
||||
* </code>.
|
||||
*/
|
||||
public Expression getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@@ -9,20 +9,20 @@ package com.semmle.js.ast;
|
||||
*/
|
||||
public class ExportAllDeclaration extends ExportDeclaration {
|
||||
private final Literal source;
|
||||
private final Expression assertion;
|
||||
private final Expression attributes;
|
||||
|
||||
public ExportAllDeclaration(SourceLocation loc, Literal source, Expression assertion) {
|
||||
public ExportAllDeclaration(SourceLocation loc, Literal source, Expression attributes) {
|
||||
super("ExportAllDeclaration", loc);
|
||||
this.source = source;
|
||||
this.assertion = assertion;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public Literal getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public Expression getAssertion() {
|
||||
return assertion;
|
||||
public Expression getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,22 +15,30 @@ public class ExportNamedDeclaration extends ExportDeclaration {
|
||||
private final Statement declaration;
|
||||
private final List<ExportSpecifier> specifiers;
|
||||
private final Literal source;
|
||||
private final Expression assertion;
|
||||
private final Expression attributes;
|
||||
private final boolean hasTypeKeyword;
|
||||
|
||||
public ExportNamedDeclaration(
|
||||
SourceLocation loc, Statement declaration, List<ExportSpecifier> specifiers, Literal source, Expression assertion) {
|
||||
this(loc, declaration, specifiers, source, assertion, false);
|
||||
SourceLocation loc,
|
||||
Statement declaration,
|
||||
List<ExportSpecifier> specifiers,
|
||||
Literal source,
|
||||
Expression attributes) {
|
||||
this(loc, declaration, specifiers, source, attributes, false);
|
||||
}
|
||||
|
||||
public ExportNamedDeclaration(
|
||||
SourceLocation loc, Statement declaration, List<ExportSpecifier> specifiers, Literal source,
|
||||
Expression assertion, boolean hasTypeKeyword) {
|
||||
SourceLocation loc,
|
||||
Statement declaration,
|
||||
List<ExportSpecifier> specifiers,
|
||||
Literal source,
|
||||
Expression attributes,
|
||||
boolean hasTypeKeyword) {
|
||||
super("ExportNamedDeclaration", loc);
|
||||
this.declaration = declaration;
|
||||
this.specifiers = specifiers;
|
||||
this.source = source;
|
||||
this.assertion = assertion;
|
||||
this.attributes = attributes;
|
||||
this.hasTypeKeyword = hasTypeKeyword;
|
||||
}
|
||||
|
||||
@@ -59,9 +67,12 @@ public class ExportNamedDeclaration extends ExportDeclaration {
|
||||
return v.visit(this, c);
|
||||
}
|
||||
|
||||
/** Returns the expression after the <code>assert</code> keyword, if any, such as <code>{ type: "json" }</code>. */
|
||||
public Expression getAssertion() {
|
||||
return assertion;
|
||||
/**
|
||||
* Returns the expression after the <code>with</code> keyword, if any, such as <code>
|
||||
* { type: "json" }</code>.
|
||||
*/
|
||||
public Expression getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/** Returns true if this is an <code>export type</code> declaration. */
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.semmle.js.ast;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.semmle.ts.ast.INodeWithSymbol;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An import declaration, which can be of one of the following forms:
|
||||
@@ -23,21 +22,27 @@ public class ImportDeclaration extends Statement implements INodeWithSymbol {
|
||||
/** The module from which declarations are imported. */
|
||||
private final Literal source;
|
||||
|
||||
private final Expression assertion;
|
||||
private final Expression attributes;
|
||||
|
||||
private int symbol = -1;
|
||||
|
||||
private boolean hasTypeKeyword;
|
||||
|
||||
public ImportDeclaration(SourceLocation loc, List<ImportSpecifier> specifiers, Literal source, Expression assertion) {
|
||||
this(loc, specifiers, source, assertion, false);
|
||||
public ImportDeclaration(
|
||||
SourceLocation loc, List<ImportSpecifier> specifiers, Literal source, Expression attributes) {
|
||||
this(loc, specifiers, source, attributes, false);
|
||||
}
|
||||
|
||||
public ImportDeclaration(SourceLocation loc, List<ImportSpecifier> specifiers, Literal source, Expression assertion, boolean hasTypeKeyword) {
|
||||
public ImportDeclaration(
|
||||
SourceLocation loc,
|
||||
List<ImportSpecifier> specifiers,
|
||||
Literal source,
|
||||
Expression attributes,
|
||||
boolean hasTypeKeyword) {
|
||||
super("ImportDeclaration", loc);
|
||||
this.specifiers = specifiers;
|
||||
this.source = source;
|
||||
this.assertion = assertion;
|
||||
this.attributes = attributes;
|
||||
this.hasTypeKeyword = hasTypeKeyword;
|
||||
}
|
||||
|
||||
@@ -49,9 +54,12 @@ public class ImportDeclaration extends Statement implements INodeWithSymbol {
|
||||
return specifiers;
|
||||
}
|
||||
|
||||
/** Returns the expression after the <code>assert</code> keyword, if any, such as <code>{ type: "json" }</code>. */
|
||||
public Expression getAssertion() {
|
||||
return assertion;
|
||||
/**
|
||||
* Returns the expression after the <code>with</code> keyword, if any, such as <code>
|
||||
* { type: "json" }</code>.
|
||||
*/
|
||||
public Expression getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package com.semmle.js.ast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.semmle.js.ast.jsx.JSXAttribute;
|
||||
import com.semmle.js.ast.jsx.JSXClosingElement;
|
||||
import com.semmle.js.ast.jsx.JSXElement;
|
||||
@@ -42,16 +39,18 @@ import com.semmle.ts.ast.OptionalTypeExpr;
|
||||
import com.semmle.ts.ast.ParenthesizedTypeExpr;
|
||||
import com.semmle.ts.ast.PredicateTypeExpr;
|
||||
import com.semmle.ts.ast.RestTypeExpr;
|
||||
import com.semmle.ts.ast.SatisfiesExpr;
|
||||
import com.semmle.ts.ast.TemplateLiteralTypeExpr;
|
||||
import com.semmle.ts.ast.TupleTypeExpr;
|
||||
import com.semmle.ts.ast.TypeAliasDeclaration;
|
||||
import com.semmle.ts.ast.TypeAssertion;
|
||||
import com.semmle.ts.ast.SatisfiesExpr;
|
||||
import com.semmle.ts.ast.TypeParameter;
|
||||
import com.semmle.ts.ast.TypeofTypeExpr;
|
||||
import com.semmle.ts.ast.UnaryTypeExpr;
|
||||
import com.semmle.ts.ast.UnionTypeExpr;
|
||||
import com.semmle.util.data.IntList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Deep cloning of AST nodes. */
|
||||
public class NodeCopier implements Visitor<Void, INode> {
|
||||
@@ -429,7 +428,8 @@ public class NodeCopier implements Visitor<Void, INode> {
|
||||
|
||||
@Override
|
||||
public TemplateLiteralTypeExpr visit(TemplateLiteralTypeExpr nd, Void q) {
|
||||
return new TemplateLiteralTypeExpr(visit(nd.getLoc()), copy(nd.getExpressions()), copy(nd.getQuasis()));
|
||||
return new TemplateLiteralTypeExpr(
|
||||
visit(nd.getLoc()), copy(nd.getExpressions()), copy(nd.getQuasis()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -523,7 +523,8 @@ public class NodeCopier implements Visitor<Void, INode> {
|
||||
|
||||
@Override
|
||||
public ExportAllDeclaration visit(ExportAllDeclaration nd, Void c) {
|
||||
return new ExportAllDeclaration(visit(nd.getLoc()), copy(nd.getSource()), copy(nd.getAssertion()));
|
||||
return new ExportAllDeclaration(
|
||||
visit(nd.getLoc()), copy(nd.getSource()), copy(nd.getAttributes()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -538,7 +539,7 @@ public class NodeCopier implements Visitor<Void, INode> {
|
||||
copy(nd.getDeclaration()),
|
||||
copy(nd.getSpecifiers()),
|
||||
copy(nd.getSource()),
|
||||
copy(nd.getAssertion()));
|
||||
copy(nd.getAttributes()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -559,7 +560,11 @@ public class NodeCopier implements Visitor<Void, INode> {
|
||||
@Override
|
||||
public ImportDeclaration visit(ImportDeclaration nd, Void c) {
|
||||
return new ImportDeclaration(
|
||||
visit(nd.getLoc()), copy(nd.getSpecifiers()), copy(nd.getSource()), copy(nd.getAssertion()), nd.hasTypeKeyword());
|
||||
visit(nd.getLoc()),
|
||||
copy(nd.getSpecifiers()),
|
||||
copy(nd.getSource()),
|
||||
copy(nd.getAttributes()),
|
||||
nd.hasTypeKeyword());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -725,7 +730,8 @@ public class NodeCopier implements Visitor<Void, INode> {
|
||||
|
||||
@Override
|
||||
public INode visit(TupleTypeExpr nd, Void c) {
|
||||
return new TupleTypeExpr(visit(nd.getLoc()), copy(nd.getElementTypes()), copy(nd.getElementNames()));
|
||||
return new TupleTypeExpr(
|
||||
visit(nd.getLoc()), copy(nd.getElementTypes()), copy(nd.getElementNames()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -787,9 +793,7 @@ public class NodeCopier implements Visitor<Void, INode> {
|
||||
@Override
|
||||
public INode visit(SatisfiesExpr nd, Void c) {
|
||||
return new SatisfiesExpr(
|
||||
visit(nd.getLoc()),
|
||||
copy(nd.getExpression()),
|
||||
copy(nd.getTypeAnnotation()));
|
||||
visit(nd.getLoc()), copy(nd.getExpression()), copy(nd.getTypeAnnotation()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -907,7 +911,8 @@ public class NodeCopier implements Visitor<Void, INode> {
|
||||
|
||||
@Override
|
||||
public INode visit(GeneratedCodeExpr nd, Void c) {
|
||||
return new GeneratedCodeExpr(visit(nd.getLoc()), nd.getOpeningDelimiter(), nd.getClosingDelimiter(), nd.getBody());
|
||||
return new GeneratedCodeExpr(
|
||||
visit(nd.getLoc()), nd.getOpeningDelimiter(), nd.getClosingDelimiter(), nd.getBody());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
package com.semmle.js.extractor;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import com.semmle.js.ast.AClass;
|
||||
import com.semmle.js.ast.AFunction;
|
||||
import com.semmle.js.ast.AFunctionExpression;
|
||||
@@ -150,11 +140,11 @@ import com.semmle.ts.ast.OptionalTypeExpr;
|
||||
import com.semmle.ts.ast.ParenthesizedTypeExpr;
|
||||
import com.semmle.ts.ast.PredicateTypeExpr;
|
||||
import com.semmle.ts.ast.RestTypeExpr;
|
||||
import com.semmle.ts.ast.SatisfiesExpr;
|
||||
import com.semmle.ts.ast.TemplateLiteralTypeExpr;
|
||||
import com.semmle.ts.ast.TupleTypeExpr;
|
||||
import com.semmle.ts.ast.TypeAliasDeclaration;
|
||||
import com.semmle.ts.ast.TypeAssertion;
|
||||
import com.semmle.ts.ast.SatisfiesExpr;
|
||||
import com.semmle.ts.ast.TypeExpression;
|
||||
import com.semmle.ts.ast.TypeParameter;
|
||||
import com.semmle.ts.ast.TypeofTypeExpr;
|
||||
@@ -166,6 +156,13 @@ import com.semmle.util.locations.OffsetTranslation;
|
||||
import com.semmle.util.locations.SourceMap;
|
||||
import com.semmle.util.trap.TrapWriter;
|
||||
import com.semmle.util.trap.TrapWriter.Label;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/** Extractor for AST-based information; invoked by the {@link JSExtractor}. */
|
||||
public class ASTExtractor {
|
||||
@@ -387,7 +384,8 @@ public class ASTExtractor {
|
||||
return visit(child, parent, childIndex, IdContext.VAR_BIND, binopOperand);
|
||||
}
|
||||
|
||||
private Label visit(INode child, Label parent, int childIndex, IdContext idContext, boolean binopOperand) {
|
||||
private Label visit(
|
||||
INode child, Label parent, int childIndex, IdContext idContext, boolean binopOperand) {
|
||||
if (child == null) return null;
|
||||
return child.accept(this, new Context(parent, childIndex, idContext, binopOperand));
|
||||
}
|
||||
@@ -590,15 +588,28 @@ public class ASTExtractor {
|
||||
|
||||
trapwriter.addTuple("literals", valueString, source, key);
|
||||
Position start = nd.getLoc().getStart();
|
||||
com.semmle.util.locations.Position startPos = new com.semmle.util.locations.Position(start.getLine(), start.getColumn() + 1 /* Convert from 0-based to 1-based. */, start.getOffset());
|
||||
com.semmle.util.locations.Position startPos =
|
||||
new com.semmle.util.locations.Position(
|
||||
start.getLine(),
|
||||
start.getColumn() + 1 /* Convert from 0-based to 1-based. */,
|
||||
start.getOffset());
|
||||
|
||||
if (nd.isRegExp()) {
|
||||
OffsetTranslation offsets = new OffsetTranslation();
|
||||
offsets.set(0, 1); // skip the initial '/'
|
||||
SourceMap sourceMap = SourceMap.legacyWithStartPos(SourceMap.fromString(nd.getRaw()).offsetBy(0, offsets), startPos);
|
||||
SourceMap sourceMap =
|
||||
SourceMap.legacyWithStartPos(
|
||||
SourceMap.fromString(nd.getRaw()).offsetBy(0, offsets), startPos);
|
||||
regexpExtractor.extract(source.substring(1, source.lastIndexOf('/')), sourceMap, nd, false);
|
||||
} else if (nd.isStringLiteral() && !c.isInsideType() && nd.getRaw().length() < 1000 && !c.isBinopOperand()) {
|
||||
SourceMap sourceMap = SourceMap.legacyWithStartPos(SourceMap.fromString(nd.getRaw()).offsetBy(0, makeStringLiteralOffsets(nd.getRaw())), startPos);
|
||||
} else if (nd.isStringLiteral()
|
||||
&& !c.isInsideType()
|
||||
&& nd.getRaw().length() < 1000
|
||||
&& !c.isBinopOperand()) {
|
||||
SourceMap sourceMap =
|
||||
SourceMap.legacyWithStartPos(
|
||||
SourceMap.fromString(nd.getRaw())
|
||||
.offsetBy(0, makeStringLiteralOffsets(nd.getRaw())),
|
||||
startPos);
|
||||
regexpExtractor.extract(valueString, sourceMap, nd, true);
|
||||
|
||||
// Scan the string for template tags, if we're in a context where such tags are relevant.
|
||||
@@ -621,8 +632,8 @@ public class ASTExtractor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant-folds simple string concatenations in `exp` while keeping an offset translation
|
||||
* that tracks back to the original source.
|
||||
* Constant-folds simple string concatenations in `exp` while keeping an offset translation that
|
||||
* tracks back to the original source.
|
||||
*/
|
||||
private Pair<String, OffsetTranslation> getStringConcatResult(Expression exp) {
|
||||
if (exp instanceof BinaryExpression) {
|
||||
@@ -638,7 +649,9 @@ public class ASTExtractor {
|
||||
return null;
|
||||
}
|
||||
|
||||
int delta = be.getRight().getLoc().getStart().getOffset() - be.getLeft().getLoc().getStart().getOffset();
|
||||
int delta =
|
||||
be.getRight().getLoc().getStart().getOffset()
|
||||
- be.getLeft().getLoc().getStart().getOffset();
|
||||
int offset = left.fst().length();
|
||||
return Pair.make(str, left.snd().append(right.snd(), offset, delta));
|
||||
}
|
||||
@@ -748,7 +761,9 @@ public class ASTExtractor {
|
||||
visit(nd.getProperty(), key, 1, IdContext.TYPE_LABEL);
|
||||
} else {
|
||||
IdContext baseIdContext =
|
||||
(c.idcontext == IdContext.EXPORT || c.idcontext == IdContext.EXPORT_BASE) ? IdContext.EXPORT_BASE : IdContext.VAR_BIND;
|
||||
(c.idcontext == IdContext.EXPORT || c.idcontext == IdContext.EXPORT_BASE)
|
||||
? IdContext.EXPORT_BASE
|
||||
: IdContext.VAR_BIND;
|
||||
visit(nd.getObject(), key, 0, baseIdContext);
|
||||
visit(nd.getProperty(), key, 1, nd.isComputed() ? IdContext.VAR_BIND : IdContext.LABEL);
|
||||
}
|
||||
@@ -848,8 +863,11 @@ public class ASTExtractor {
|
||||
@Override
|
||||
public Label visit(BinaryExpression nd, Context c) {
|
||||
Label key = super.visit(nd, c);
|
||||
if (nd.getOperator().equals("in") && nd.getLeft() instanceof Identifier && ((Identifier)nd.getLeft()).getName().startsWith("#")) {
|
||||
// this happens with Ergonomic brand checks for Private Fields (see https://github.com/tc39/proposal-private-fields-in-in).
|
||||
if (nd.getOperator().equals("in")
|
||||
&& nd.getLeft() instanceof Identifier
|
||||
&& ((Identifier) nd.getLeft()).getName().startsWith("#")) {
|
||||
// this happens with Ergonomic brand checks for Private Fields (see
|
||||
// https://github.com/tc39/proposal-private-fields-in-in).
|
||||
// it's the only case where private field identifiers are used not as a field.
|
||||
visit(nd.getLeft(), key, 0, IdContext.LABEL, true);
|
||||
} else {
|
||||
@@ -875,8 +893,14 @@ public class ASTExtractor {
|
||||
}
|
||||
OffsetTranslation offsets = concatResult.snd();
|
||||
Position start = nd.getLoc().getStart();
|
||||
com.semmle.util.locations.Position startPos = new com.semmle.util.locations.Position(start.getLine(), start.getColumn() + 1 /* Convert from 0-based to 1-based. */, start.getOffset());
|
||||
SourceMap sourceMap = SourceMap.legacyWithStartPos(SourceMap.fromString(nd.getLoc().getSource()).offsetBy(0, offsets), startPos);
|
||||
com.semmle.util.locations.Position startPos =
|
||||
new com.semmle.util.locations.Position(
|
||||
start.getLine(),
|
||||
start.getColumn() + 1 /* Convert from 0-based to 1-based. */,
|
||||
start.getOffset());
|
||||
SourceMap sourceMap =
|
||||
SourceMap.legacyWithStartPos(
|
||||
SourceMap.fromString(nd.getLoc().getSource()).offsetBy(0, offsets), startPos);
|
||||
regexpExtractor.extract(foldedString, sourceMap, nd, true);
|
||||
return;
|
||||
}
|
||||
@@ -1759,7 +1783,7 @@ public class ASTExtractor {
|
||||
public Label visit(ExportAllDeclaration nd, Context c) {
|
||||
Label lbl = super.visit(nd, c);
|
||||
visit(nd.getSource(), lbl, 0);
|
||||
visit(nd.getAssertion(), lbl, -10);
|
||||
visit(nd.getAttributes(), lbl, -10);
|
||||
return lbl;
|
||||
}
|
||||
|
||||
@@ -1775,7 +1799,7 @@ public class ASTExtractor {
|
||||
Label lbl = super.visit(nd, c);
|
||||
visit(nd.getDeclaration(), lbl, -1);
|
||||
visit(nd.getSource(), lbl, -2);
|
||||
visit(nd.getAssertion(), lbl, -10);
|
||||
visit(nd.getAttributes(), lbl, -10);
|
||||
IdContext childContext =
|
||||
nd.hasSource()
|
||||
? IdContext.LABEL
|
||||
@@ -1799,7 +1823,7 @@ public class ASTExtractor {
|
||||
public Label visit(ImportDeclaration nd, Context c) {
|
||||
Label lbl = super.visit(nd, c);
|
||||
visit(nd.getSource(), lbl, -1);
|
||||
visit(nd.getAssertion(), lbl, -10);
|
||||
visit(nd.getAttributes(), lbl, -10);
|
||||
IdContext childContext =
|
||||
nd.hasTypeKeyword()
|
||||
? IdContext.TYPE_ONLY_IMPORT
|
||||
|
||||
@@ -153,7 +153,7 @@ import com.semmle.util.trap.TrapWriter;
|
||||
* <li>All JavaScript files, that is, files with one of the extensions supported by {@link
|
||||
* FileType#JS} (currently ".js", ".jsx", ".mjs", ".cjs", ".es6", ".es").
|
||||
* <li>All HTML files, that is, files with with one of the extensions supported by {@link
|
||||
* FileType#HTML} (currently ".htm", ".html", ".xhtm", ".xhtml", ".vue", ".html.erb").
|
||||
* FileType#HTML} (currently ".htm", ".html", ".xhtm", ".xhtml", ".vue", ".html.erb", ".jsp").
|
||||
* <li>All YAML files, that is, files with one of the extensions supported by {@link
|
||||
* FileType#YAML} (currently ".raml", ".yaml", ".yml").
|
||||
* <li>Files with base name "package.json" or "tsconfig.json", and files whose base name
|
||||
@@ -892,10 +892,15 @@ protected DependencyInstallationResult preparePackagesAndDependencies(Set<Path>
|
||||
// For named packages, find the main file.
|
||||
String name = packageJson.getName();
|
||||
if (name != null) {
|
||||
Path entryPoint = guessPackageMainFile(path, packageJson, FileType.TYPESCRIPT.getExtensions());
|
||||
if (entryPoint == null) {
|
||||
// Try a TypeScript-recognized JS extension instead
|
||||
entryPoint = guessPackageMainFile(path, packageJson, Arrays.asList(".js", ".jsx"));
|
||||
Path entryPoint = null;
|
||||
try {
|
||||
entryPoint = guessPackageMainFile(path, packageJson, FileType.TYPESCRIPT.getExtensions());
|
||||
if (entryPoint == null) {
|
||||
// Try a TypeScript-recognized JS extension instead
|
||||
entryPoint = guessPackageMainFile(path, packageJson, Arrays.asList(".js", ".jsx"));
|
||||
}
|
||||
} catch (InvalidPathException ignore) {
|
||||
// can happen if the `main:` field is invalid. E.g. on Windows a path like `dist/*.js` will crash.
|
||||
}
|
||||
if (entryPoint != null) {
|
||||
System.out.println(relativePath + ": Main file set to " + sourceRoot.relativize(entryPoint));
|
||||
|
||||
@@ -103,7 +103,7 @@ public class FileExtractor {
|
||||
|
||||
/** Information about supported file types. */
|
||||
public static enum FileType {
|
||||
HTML(".htm", ".html", ".xhtm", ".xhtml", ".vue", ".hbs", ".ejs", ".njk", ".erb") {
|
||||
HTML(".htm", ".html", ".xhtm", ".xhtml", ".vue", ".hbs", ".ejs", ".njk", ".erb", ".jsp") {
|
||||
@Override
|
||||
public IExtractor mkExtractor(ExtractorConfig config, ExtractorState state) {
|
||||
return new HTMLExtractor(config, state);
|
||||
|
||||
@@ -41,7 +41,7 @@ public class Main {
|
||||
* A version identifier that should be updated every time the extractor changes in such a way that
|
||||
* it may produce different tuples for the same file under the same {@link ExtractorConfig}.
|
||||
*/
|
||||
public static final String EXTRACTOR_VERSION = "2023-08-10";
|
||||
public static final String EXTRACTOR_VERSION = "2023-10-13";
|
||||
|
||||
public static final Pattern NEWLINE = Pattern.compile("\n");
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.semmle.js.ast.Identifier;
|
||||
import com.semmle.js.ast.Literal;
|
||||
import com.semmle.js.ast.MemberExpression;
|
||||
import com.semmle.js.ast.TemplateElement;
|
||||
import com.semmle.js.ast.ThisExpression;
|
||||
import com.semmle.js.extractor.ASTExtractor.IdContext;
|
||||
import com.semmle.ts.ast.ArrayTypeExpr;
|
||||
import com.semmle.ts.ast.ConditionalTypeExpr;
|
||||
@@ -100,6 +101,11 @@ public class TypeExprKinds {
|
||||
return keywordTypeExpr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer visit(ThisExpression nd, Void c) {
|
||||
return thisVarTypeAccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer visit(ArrayTypeExpr nd, Void c) {
|
||||
return arrayTypeExpr;
|
||||
|
||||
@@ -79,7 +79,7 @@ public class JSONParser {
|
||||
}
|
||||
|
||||
private char peek() {
|
||||
return offset < length ? src.charAt(offset) : (char) -1;
|
||||
return offset < length ? src.charAt(offset) : Character.MAX_VALUE;
|
||||
}
|
||||
|
||||
private JSONValue readValue() throws ParseError {
|
||||
@@ -356,7 +356,7 @@ public class JSONParser {
|
||||
char c;
|
||||
next();
|
||||
next();
|
||||
while ((c = peek()) != '\r' && c != '\n' && c != -1) next();
|
||||
while ((c = peek()) != '\r' && c != '\n' && c != Character.MAX_VALUE) next();
|
||||
}
|
||||
|
||||
/** Skips the block comment starting at the current position. */
|
||||
@@ -367,7 +367,7 @@ public class JSONParser {
|
||||
next();
|
||||
do {
|
||||
c = peek();
|
||||
if (c < 0) raise("Unterminated comment.");
|
||||
if (c == Character.MAX_VALUE) raise("Unterminated comment");
|
||||
next();
|
||||
if (c == '*' && peek() == '/') {
|
||||
next();
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
package com.semmle.ts.extractor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonNull;
|
||||
@@ -145,17 +139,22 @@ import com.semmle.ts.ast.OptionalTypeExpr;
|
||||
import com.semmle.ts.ast.ParenthesizedTypeExpr;
|
||||
import com.semmle.ts.ast.PredicateTypeExpr;
|
||||
import com.semmle.ts.ast.RestTypeExpr;
|
||||
import com.semmle.ts.ast.SatisfiesExpr;
|
||||
import com.semmle.ts.ast.TemplateLiteralTypeExpr;
|
||||
import com.semmle.ts.ast.TupleTypeExpr;
|
||||
import com.semmle.ts.ast.TypeAliasDeclaration;
|
||||
import com.semmle.ts.ast.TypeAssertion;
|
||||
import com.semmle.ts.ast.SatisfiesExpr;
|
||||
import com.semmle.ts.ast.TypeParameter;
|
||||
import com.semmle.ts.ast.TypeofTypeExpr;
|
||||
import com.semmle.ts.ast.UnaryTypeExpr;
|
||||
import com.semmle.ts.ast.UnionTypeExpr;
|
||||
import com.semmle.util.collections.CollectionUtil;
|
||||
import com.semmle.util.data.IntList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utility class for converting a <a
|
||||
@@ -177,7 +176,8 @@ public class TypeScriptASTConverter {
|
||||
private static final Pattern EXPORT_DECL_START =
|
||||
Pattern.compile("^export" + "(" + WHITESPACE_CHAR + "+default)?" + WHITESPACE_CHAR + "+");
|
||||
private static final Pattern TYPEOF_START = Pattern.compile("^typeof" + WHITESPACE_CHAR + "+");
|
||||
private static final Pattern ASSERT_START = Pattern.compile("^assert" + WHITESPACE_CHAR + "+");
|
||||
private static final Pattern IMPORT_ATTRIBUTE_START =
|
||||
Pattern.compile("^(assert|with)" + WHITESPACE_CHAR + "+");
|
||||
private static final Pattern WHITESPACE_END_PAREN =
|
||||
Pattern.compile("^" + WHITESPACE_CHAR + "*\\)");
|
||||
|
||||
@@ -343,10 +343,10 @@ public class TypeScriptASTConverter {
|
||||
return convertArrowFunction(node, loc);
|
||||
case "AsExpression":
|
||||
return convertTypeAssertionExpression(node, loc);
|
||||
case "AssertClause":
|
||||
return convertAssertClause(node, loc);
|
||||
case "AssertEntry":
|
||||
return convertAssertEntry(node, loc);
|
||||
case "ImportAttributes":
|
||||
return convertImportAttributes(node, loc);
|
||||
case "ImportAttribute":
|
||||
return convertImportAttribute(node, loc);
|
||||
case "SatisfiesExpression":
|
||||
return convertSatisfiesExpression(node, loc);
|
||||
case "AwaitExpression":
|
||||
@@ -877,8 +877,10 @@ public class TypeScriptASTConverter {
|
||||
}
|
||||
}
|
||||
|
||||
private Node convertStaticInitializerBlock(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
BlockStatement body = new BlockStatement(loc, convertChildren(node.get("body").getAsJsonObject(), "statements"));
|
||||
private Node convertStaticInitializerBlock(JsonObject node, SourceLocation loc)
|
||||
throws ParseError {
|
||||
BlockStatement body =
|
||||
new BlockStatement(loc, convertChildren(node.get("body").getAsJsonObject(), "statements"));
|
||||
return new StaticInitializer(loc, body);
|
||||
}
|
||||
|
||||
@@ -893,7 +895,8 @@ public class TypeScriptASTConverter {
|
||||
private Node convertCallExpression(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
List<Expression> arguments = convertChildren(node, "arguments");
|
||||
if (arguments.size() >= 1 && hasKind(node.get("expression"), "ImportKeyword")) {
|
||||
return new DynamicImport(loc, arguments.get(0), arguments.size() > 1 ? arguments.get(1) : null);
|
||||
return new DynamicImport(
|
||||
loc, arguments.get(0), arguments.size() > 1 ? arguments.get(1) : null);
|
||||
}
|
||||
Expression callee = convertChild(node, "expression");
|
||||
List<ITypeExpression> typeArguments = convertChildrenAsTypes(node, "typeArguments");
|
||||
@@ -1198,16 +1201,16 @@ public class TypeScriptASTConverter {
|
||||
|
||||
private Node convertExportDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
Literal source = tryConvertChild(node, "moduleSpecifier", Literal.class);
|
||||
Expression assertion = convertChild(node, "assertClause");
|
||||
Expression attributes = convertChild(node, "attributes");
|
||||
if (hasChild(node, "exportClause")) {
|
||||
boolean hasTypeKeyword = node.get("isTypeOnly").getAsBoolean();
|
||||
List<ExportSpecifier> specifiers =
|
||||
hasKind(node.get("exportClause"), "NamespaceExport")
|
||||
? Collections.singletonList(convertChild(node, "exportClause"))
|
||||
: convertChildren(node.get("exportClause").getAsJsonObject(), "elements");
|
||||
return new ExportNamedDeclaration(loc, null, specifiers, source, assertion, hasTypeKeyword);
|
||||
return new ExportNamedDeclaration(loc, null, specifiers, source, attributes, hasTypeKeyword);
|
||||
} else {
|
||||
return new ExportAllDeclaration(loc, source, assertion);
|
||||
return new ExportAllDeclaration(loc, source, attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1238,7 +1241,8 @@ public class TypeScriptASTConverter {
|
||||
|
||||
private Node convertExternalModuleReference(JsonObject node, SourceLocation loc)
|
||||
throws ParseError {
|
||||
ExternalModuleReference moduleRef = new ExternalModuleReference(loc, convertChild(node, "expression"));
|
||||
ExternalModuleReference moduleRef =
|
||||
new ExternalModuleReference(loc, convertChild(node, "expression"));
|
||||
attachSymbolInformation(moduleRef, node);
|
||||
return moduleRef;
|
||||
}
|
||||
@@ -1389,7 +1393,7 @@ public class TypeScriptASTConverter {
|
||||
|
||||
private Node convertImportDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
Literal src = tryConvertChild(node, "moduleSpecifier", Literal.class);
|
||||
Expression assertion = convertChild(node, "assertClause");
|
||||
Expression attributes = convertChild(node, "attributes");
|
||||
List<ImportSpecifier> specifiers = new ArrayList<>();
|
||||
boolean hasTypeKeyword = false;
|
||||
if (hasChild(node, "importClause")) {
|
||||
@@ -1407,7 +1411,8 @@ public class TypeScriptASTConverter {
|
||||
}
|
||||
hasTypeKeyword = importClause.get("isTypeOnly").getAsBoolean();
|
||||
}
|
||||
ImportDeclaration importDecl = new ImportDeclaration(loc, specifiers, src, assertion, hasTypeKeyword);
|
||||
ImportDeclaration importDecl =
|
||||
new ImportDeclaration(loc, specifiers, src, attributes, hasTypeKeyword);
|
||||
attachSymbolInformation(importDecl, node);
|
||||
return importDecl;
|
||||
}
|
||||
@@ -1558,7 +1563,9 @@ public class TypeScriptASTConverter {
|
||||
nameNode = nameNode.get("name").getAsJsonObject();
|
||||
}
|
||||
return new JSXAttribute(
|
||||
loc, convertJSXName(((Expression)convertNode(nameNode, null))), convertChild(node, "initializer")); // 2
|
||||
loc,
|
||||
convertJSXName(((Expression) convertNode(nameNode, null))),
|
||||
convertChild(node, "initializer")); // 2
|
||||
}
|
||||
|
||||
private Node convertJsxClosingElement(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
@@ -1649,8 +1656,10 @@ public class TypeScriptASTConverter {
|
||||
}
|
||||
}
|
||||
if (literal instanceof TemplateLiteral) {
|
||||
// A LiteralType containing a NoSubstitutionTemplateLiteral must produce a TemplateLiteralTypeExpr
|
||||
return new TemplateLiteralTypeExpr(literal.getLoc(), new ArrayList<>(), ((TemplateLiteral)literal).getQuasis());
|
||||
// A LiteralType containing a NoSubstitutionTemplateLiteral must produce a
|
||||
// TemplateLiteralTypeExpr
|
||||
return new TemplateLiteralTypeExpr(
|
||||
literal.getLoc(), new ArrayList<>(), ((TemplateLiteral) literal).getQuasis());
|
||||
}
|
||||
return literal;
|
||||
}
|
||||
@@ -2254,7 +2263,7 @@ public class TypeScriptASTConverter {
|
||||
for (JsonElement element : node.get("elements").getAsJsonArray()) {
|
||||
Identifier id = null;
|
||||
if (getKind(element).equals("NamedTupleMember")) {
|
||||
id = (Identifier)convertNode(element.getAsJsonObject().get("name").getAsJsonObject());
|
||||
id = (Identifier) convertNode(element.getAsJsonObject().get("name").getAsJsonObject());
|
||||
}
|
||||
names.add(id);
|
||||
}
|
||||
@@ -2262,7 +2271,8 @@ public class TypeScriptASTConverter {
|
||||
return new TupleTypeExpr(loc, convertChildrenAsTypes(node, "elements"), names);
|
||||
}
|
||||
|
||||
// This method just does a trivial forward to the type. The names have already been extracted in `convertTupleType`.
|
||||
// This method just does a trivial forward to the type. The names have already been extracted in
|
||||
// `convertTupleType`.
|
||||
private Node convertNamedTupleMember(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
return convertChild(node, "type");
|
||||
}
|
||||
@@ -2288,27 +2298,22 @@ public class TypeScriptASTConverter {
|
||||
return new TypeAssertion(loc, convertChild(node, "expression"), type, false);
|
||||
}
|
||||
|
||||
private Node convertAssertClause(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
private Node convertImportAttributes(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
List<Property> properties = new ArrayList<>();
|
||||
for (INode child : convertChildren(node, "elements")) {
|
||||
properties.add((Property)child);
|
||||
properties.add((Property) child);
|
||||
}
|
||||
// Adjust location to skip over the `assert` keyword.
|
||||
Matcher m = ASSERT_START.matcher(loc.getSource());
|
||||
// Adjust location to skip over the `with` or `assert` keyword.
|
||||
Matcher m = IMPORT_ATTRIBUTE_START.matcher(loc.getSource());
|
||||
if (m.find()) {
|
||||
advance(loc, m.group(0));
|
||||
}
|
||||
return new ObjectExpression(loc, properties);
|
||||
}
|
||||
|
||||
private Node convertAssertEntry(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
private Node convertImportAttribute(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
return new Property(
|
||||
loc,
|
||||
convertChild(node, "key"),
|
||||
convertChild(node, "value"),
|
||||
"init",
|
||||
false,
|
||||
false);
|
||||
loc, convertChild(node, "key"), convertChild(node, "value"), "init", false, false);
|
||||
}
|
||||
|
||||
private Node convertSatisfiesExpression(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
@@ -2490,7 +2495,8 @@ public class TypeScriptASTConverter {
|
||||
advance(loc, skipped);
|
||||
// capture group 1 is `default`, if present
|
||||
if (m.group(1) == null)
|
||||
return new ExportNamedDeclaration(outerLoc, (Statement) decl, new ArrayList<>(), null, null);
|
||||
return new ExportNamedDeclaration(
|
||||
outerLoc, (Statement) decl, new ArrayList<>(), null, null);
|
||||
return new ExportDefaultDeclaration(outerLoc, decl);
|
||||
}
|
||||
return decl;
|
||||
@@ -2586,8 +2592,9 @@ public class TypeScriptASTConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific modifier from the given node (or <code>null</code> if absent), as defined by its
|
||||
* <code>modifiers</code> property and the <code>kind</code> property of the modifier AST node.
|
||||
* Returns a specific modifier from the given node (or <code>null</code> if absent), as defined by
|
||||
* its <code>modifiers</code> property and the <code>kind</code> property of the modifier AST
|
||||
* node.
|
||||
*/
|
||||
private JsonObject getModifier(JsonObject node, String modKind) {
|
||||
for (JsonElement mod : getModifiers(node))
|
||||
@@ -2597,8 +2604,8 @@ public class TypeScriptASTConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a node has a particular modifier, as defined by its <code>modifiers</code> property
|
||||
* and the <code>kind</code> property of the modifier AST node.
|
||||
* Check whether a node has a particular modifier, as defined by its <code>modifiers</code>
|
||||
* property and the <code>kind</code> property of the modifier AST node.
|
||||
*/
|
||||
private boolean hasModifier(JsonObject node, String modKind) {
|
||||
return getModifier(node, modKind) != null;
|
||||
@@ -2639,8 +2646,8 @@ public class TypeScriptASTConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a node has a particular flag, as defined by its <code>flags</code> property and the
|
||||
* <code>ts.NodeFlags</code> in enum.
|
||||
* Check whether a node has a particular flag, as defined by its <code>flags</code> property and
|
||||
* the <code>ts.NodeFlags</code> in enum.
|
||||
*/
|
||||
private boolean hasFlag(JsonObject node, String flagName) {
|
||||
int flagId = metadata.getNodeFlagId(flagName);
|
||||
@@ -2683,7 +2690,7 @@ public class TypeScriptASTConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the declaration kind of the given node, which is one of {@code "var"}, {@code "let"},
|
||||
* Gets the declaration kind of the given node, which is one of {@code "var"}, {@code "let"},
|
||||
* {@code "const"}, or {@code "using"}.
|
||||
*/
|
||||
private String getDeclarationKind(JsonObject declarationList) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.semmle.jcorn.SyntaxError;
|
||||
import com.semmle.js.ast.AST2JSON;
|
||||
import com.semmle.js.ast.Program;
|
||||
import com.semmle.util.io.WholeIO;
|
||||
import com.semmle.util.tests.TestPaths;
|
||||
import java.io.File;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
@@ -54,7 +55,7 @@ public abstract class ASTMatchingTests {
|
||||
}
|
||||
}
|
||||
|
||||
private static final File BABYLON_BASE = new File("parser-tests/babylon").getAbsoluteFile();
|
||||
private static final File BABYLON_BASE = TestPaths.get("parser-tests/babylon").toAbsolutePath().toFile();
|
||||
|
||||
protected void babylonTest(String dir) {
|
||||
babylonTest(dir, new Options().esnext(true));
|
||||
@@ -30,6 +30,7 @@ import com.semmle.js.extractor.FileExtractor;
|
||||
import com.semmle.js.extractor.FileExtractor.FileType;
|
||||
import com.semmle.js.extractor.VirtualSourceRoot;
|
||||
import com.semmle.util.data.StringUtil;
|
||||
import com.semmle.util.exception.Exceptions;
|
||||
import com.semmle.util.exception.UserError;
|
||||
import com.semmle.util.files.FileUtil;
|
||||
import com.semmle.util.files.FileUtil8;
|
||||
@@ -443,8 +444,12 @@ public class AutoBuildTests {
|
||||
|
||||
/** Hide {@code p} on using {@link DosFileAttributeView} if available; otherwise do nothing. */
|
||||
private void hide(Path p) throws IOException {
|
||||
try {
|
||||
DosFileAttributeView attrs = Files.getFileAttributeView(p, DosFileAttributeView.class);
|
||||
if (attrs != null) attrs.setHidden(true);
|
||||
} catch (IOException e) {
|
||||
Exceptions.ignore(e, "On Linux within the bazel sandbox, we get a DosFileAttributeView that then throws an exception upon use");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -0,0 +1,33 @@
|
||||
java_test(
|
||||
name = "test_jar",
|
||||
srcs = glob(["**/*.java"]),
|
||||
test_class = "com.semmle.js.extractor.test.AllTests",
|
||||
deps = [
|
||||
"//javascript/extractor",
|
||||
"//javascript/extractor:deps",
|
||||
"@//resources/lib/java/DO_NOT_DISTRIBUTE:junit",
|
||||
],
|
||||
)
|
||||
|
||||
# We need to unzip the typescript wrapper, and provide node on the path.
|
||||
# Therefore, we're wrapping the java_test inside a sh_test.
|
||||
sh_test(
|
||||
name = "test",
|
||||
size = "small",
|
||||
srcs = ["run_tests.sh"],
|
||||
args = [
|
||||
"$(execpath @nodejs//:node_bin)",
|
||||
"$(JAVABASE)/bin/java",
|
||||
"$(rootpath //javascript/extractor/lib/typescript)",
|
||||
"$(rootpath test_jar_deploy.jar)",
|
||||
],
|
||||
data = [
|
||||
":test_jar_deploy.jar",
|
||||
"//javascript/extractor/lib/typescript",
|
||||
"//javascript/extractor/parser-tests",
|
||||
"//javascript/extractor/tests",
|
||||
"@bazel_tools//tools/jdk:current_java_runtime",
|
||||
"@nodejs//:node_bin",
|
||||
],
|
||||
toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"],
|
||||
)
|
||||
@@ -10,6 +10,7 @@ import com.semmle.js.ast.AST2JSON;
|
||||
import com.semmle.js.ast.Program;
|
||||
import com.semmle.util.files.FileUtil;
|
||||
import com.semmle.util.io.WholeIO;
|
||||
import com.semmle.util.tests.TestPaths;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -25,7 +26,7 @@ import org.junit.runners.Parameterized.Parameters;
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class JSXTests extends ASTMatchingTests {
|
||||
private static final File BASE = new File("parser-tests/jcorn-jsx").getAbsoluteFile();
|
||||
private static final File BASE = TestPaths.get("parser-tests/jcorn-jsx").toAbsolutePath().toFile();
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Iterable<Object[]> tests() {
|
||||
@@ -3,6 +3,7 @@ package com.semmle.js.extractor.test;
|
||||
import com.semmle.jcorn.Options;
|
||||
import com.semmle.jcorn.Parser;
|
||||
import com.semmle.util.io.WholeIO;
|
||||
import com.semmle.util.tests.TestPaths;
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.junit.Test;
|
||||
@@ -11,7 +12,7 @@ public class RobustnessTests {
|
||||
|
||||
@Test
|
||||
public void letLookheadTest() {
|
||||
File test = new File("parser-tests/robustness/letLookahead.js");
|
||||
File test = TestPaths.get("parser-tests/robustness/letLookahead.js").toFile();
|
||||
String src = new WholeIO(StandardCharsets.UTF_8.name()).strictread(test);
|
||||
new Parser(new Options(), src, 0).parse();
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import com.semmle.util.extraction.ExtractorOutputConfig;
|
||||
import com.semmle.util.io.WholeIO;
|
||||
import com.semmle.util.process.Env;
|
||||
import com.semmle.util.srcarchive.DummySourceArchive;
|
||||
import com.semmle.util.tests.TestPaths;
|
||||
import com.semmle.util.trap.ITrapWriterFactory;
|
||||
import com.semmle.util.trap.TrapWriter;
|
||||
import com.semmle.util.trap.pathtransformers.ProjectLayoutTransformer;
|
||||
@@ -35,7 +36,7 @@ import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class TrapTests {
|
||||
private static final File BASE = new File("tests").getAbsoluteFile();
|
||||
private static final File BASE = TestPaths.get("tests").toAbsolutePath().toFile();
|
||||
|
||||
@Parameters(name = "{0}:{1}")
|
||||
public static Iterable<Object[]> tests() {
|
||||
31
javascript/extractor/test/com/semmle/js/extractor/test/run_tests.sh
Executable file
31
javascript/extractor/test/com/semmle/js/extractor/test/run_tests.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
NODE=$1
|
||||
JAVA=$2
|
||||
PARSER_WRAPPER=$3
|
||||
TEST_JAR=$4
|
||||
|
||||
TEMP=$(mktemp -d)
|
||||
|
||||
UNAME=$(uname -s)
|
||||
echo $UNAME
|
||||
# On Windows, the symlink set up by bazel that points at the test jar is a msys2/linux-style path
|
||||
# The JVM can't resolve that, therefore copy the jar to the temp directory, and then set the
|
||||
# windows path to it
|
||||
if [[ "$UNAME" =~ _NT ]]; then
|
||||
cp $TEST_JAR $TEMP/test.jar
|
||||
TEST_JAR=$(cygpath -w $TEMP/test.jar)
|
||||
echo "On Windows, new test jar: $TEST_JAR"
|
||||
fi
|
||||
|
||||
# unpack parser wrapper
|
||||
unzip -q $PARSER_WRAPPER -d $TEMP/parser_wrapper
|
||||
export SEMMLE_TYPESCRIPT_PARSER_WRAPPER=$TEMP/parser_wrapper/javascript/tools/typescript-parser-wrapper/main.js
|
||||
|
||||
# setup node on path
|
||||
NODE=$(realpath $NODE)
|
||||
export PATH="$PATH:$(dirname $NODE)"
|
||||
|
||||
$JAVA -Dbazel.test_suite=com.semmle.js.extractor.test.AllTests -jar $TEST_JAR
|
||||
EXIT_CODE=$?
|
||||
|
||||
rm -rf $TEMP
|
||||
exit $EXIT_CODE
|
||||
5
javascript/extractor/tests/BUILD.bazel
Normal file
5
javascript/extractor/tests/BUILD.bazel
Normal file
@@ -0,0 +1,5 @@
|
||||
filegroup(
|
||||
name = "tests",
|
||||
srcs = glob(["**/*"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -1,3 +1,17 @@
|
||||
import "module" with { type: "json" };
|
||||
import * as v1 from "module" with { type: "json" };
|
||||
import { v2 } from "module" with { type: "json" };
|
||||
import v3 from "module" with { type: "json" };
|
||||
|
||||
export { v4 } from "module" with { type: "json" };
|
||||
export * from "module" with { type: "json" };
|
||||
export * as v5 from "module" with { type: "json" };
|
||||
|
||||
const v6 = import("module", { with: { type: "json" } });
|
||||
|
||||
import "module" // missing semicolon
|
||||
assert({type: "json"}); // function call, not import assertion
|
||||
|
||||
import "module" assert { type: "json" };
|
||||
import * as v1 from "module" assert { type: "json" };
|
||||
import { v2 } from "module" assert { type: "json" };
|
||||
@@ -8,6 +22,3 @@ export * from "module" assert { type: "json" };
|
||||
export * as v5 from "module" assert { type: "json" };
|
||||
|
||||
const v6 = import("module", { assert: { type: "json" } });
|
||||
|
||||
import "module" // missing semicolon
|
||||
assert({type: "json"}); // function call, not import assertion
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,17 @@
|
||||
import "module" with { type: "json" };
|
||||
import * as v1 from "module" with { type: "json" };
|
||||
import { v2 } from "module" with { type: "json" };
|
||||
import v3 from "module" with { type: "json" };
|
||||
|
||||
export { v4 } from "module" with { type: "json" };
|
||||
export * from "module" with { type: "json" };
|
||||
export * as v5 from "module" with { type: "json" };
|
||||
|
||||
const v6 = import("module", { "with": { type: "json" } });
|
||||
|
||||
import "module"; // missing semicolon
|
||||
assert({ type: "json" }); // function call, not import assertion
|
||||
|
||||
import "module" assert { type: "json" };
|
||||
import * as v1 from "module" assert { type: "json" };
|
||||
import { v2 } from "module" assert { type: "json" };
|
||||
@@ -8,6 +22,3 @@ export * from "module" assert { type: "json" };
|
||||
export * as v5 from "module" assert { type: "json" };
|
||||
|
||||
const v6 = import("module", { assert: { type: "json" } });
|
||||
|
||||
import "module"; // missing semicolon
|
||||
assert({ type: "json" }); // function call, not import assertion
|
||||
File diff suppressed because it is too large
Load Diff
2145
javascript/extractor/tests/ts/output/trap/import-attributes.ts.trap
Normal file
2145
javascript/extractor/tests/ts/output/trap/import-attributes.ts.trap
Normal file
File diff suppressed because it is too large
Load Diff
14
javascript/extractor/tests/vue/input/simple-jsp.jsp
Normal file
14
javascript/extractor/tests/vue/input/simple-jsp.jsp
Normal file
@@ -0,0 +1,14 @@
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Hello World JSP</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<% String name = "John"; %>
|
||||
<p>Welcome <%= name %>!</p>
|
||||
<script>
|
||||
console.log(123);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
232
javascript/extractor/tests/vue/output/trap/simple-jsp.jsp.trap
Normal file
232
javascript/extractor/tests/vue/output/trap/simple-jsp.jsp.trap
Normal file
@@ -0,0 +1,232 @@
|
||||
#10000=@"/simple-jsp.jsp;sourcefile"
|
||||
files(#10000,"/simple-jsp.jsp")
|
||||
#10001=@"/;folder"
|
||||
folders(#10001,"/")
|
||||
containerparent(#10001,#10000)
|
||||
#10002=@"loc,{#10000},0,0,0,0"
|
||||
locations_default(#10002,#10000,0,0,0,0)
|
||||
hasLocation(#10000,#10002)
|
||||
#20000=@"global_scope"
|
||||
scopes(#20000,0)
|
||||
#20001=*
|
||||
#20002=@"script;{#10000},10,13"
|
||||
#20003=*
|
||||
lines(#20003,#20002,"","
|
||||
")
|
||||
#20004=@"loc,{#10000},10,13,10,12"
|
||||
locations_default(#20004,#10000,10,13,10,12)
|
||||
hasLocation(#20003,#20004)
|
||||
#20005=*
|
||||
lines(#20005,#20002," console.log(123);","
|
||||
")
|
||||
#20006=@"loc,{#10000},11,1,11,23"
|
||||
locations_default(#20006,#10000,11,1,11,23)
|
||||
hasLocation(#20005,#20006)
|
||||
indentation(#10000,11," ",6)
|
||||
#20007=*
|
||||
lines(#20007,#20002," ","")
|
||||
#20008=@"loc,{#10000},12,1,12,4"
|
||||
locations_default(#20008,#10000,12,1,12,4)
|
||||
hasLocation(#20007,#20008)
|
||||
numlines(#20002,3,1,0)
|
||||
#20009=*
|
||||
tokeninfo(#20009,6,#20002,0,"console")
|
||||
#20010=@"loc,{#10000},11,7,11,13"
|
||||
locations_default(#20010,#10000,11,7,11,13)
|
||||
hasLocation(#20009,#20010)
|
||||
#20011=*
|
||||
tokeninfo(#20011,8,#20002,1,".")
|
||||
#20012=@"loc,{#10000},11,14,11,14"
|
||||
locations_default(#20012,#10000,11,14,11,14)
|
||||
hasLocation(#20011,#20012)
|
||||
#20013=*
|
||||
tokeninfo(#20013,6,#20002,2,"log")
|
||||
#20014=@"loc,{#10000},11,15,11,17"
|
||||
locations_default(#20014,#10000,11,15,11,17)
|
||||
hasLocation(#20013,#20014)
|
||||
#20015=*
|
||||
tokeninfo(#20015,8,#20002,3,"(")
|
||||
#20016=@"loc,{#10000},11,18,11,18"
|
||||
locations_default(#20016,#10000,11,18,11,18)
|
||||
hasLocation(#20015,#20016)
|
||||
#20017=*
|
||||
tokeninfo(#20017,3,#20002,4,"123")
|
||||
#20018=@"loc,{#10000},11,19,11,21"
|
||||
locations_default(#20018,#10000,11,19,11,21)
|
||||
hasLocation(#20017,#20018)
|
||||
#20019=*
|
||||
tokeninfo(#20019,8,#20002,5,")")
|
||||
#20020=@"loc,{#10000},11,22,11,22"
|
||||
locations_default(#20020,#10000,11,22,11,22)
|
||||
hasLocation(#20019,#20020)
|
||||
#20021=*
|
||||
tokeninfo(#20021,8,#20002,6,";")
|
||||
#20022=@"loc,{#10000},11,23,11,23"
|
||||
locations_default(#20022,#10000,11,23,11,23)
|
||||
hasLocation(#20021,#20022)
|
||||
#20023=*
|
||||
tokeninfo(#20023,0,#20002,7,"")
|
||||
#20024=@"loc,{#10000},12,5,12,4"
|
||||
locations_default(#20024,#10000,12,5,12,4)
|
||||
hasLocation(#20023,#20024)
|
||||
toplevels(#20002,1)
|
||||
#20025=@"loc,{#10000},10,13,12,4"
|
||||
locations_default(#20025,#10000,10,13,12,4)
|
||||
hasLocation(#20002,#20025)
|
||||
#20026=*
|
||||
stmts(#20026,2,#20002,0,"console.log(123);")
|
||||
#20027=@"loc,{#10000},11,7,11,23"
|
||||
locations_default(#20027,#10000,11,7,11,23)
|
||||
hasLocation(#20026,#20027)
|
||||
stmt_containers(#20026,#20002)
|
||||
#20028=*
|
||||
exprs(#20028,13,#20026,0,"console.log(123)")
|
||||
#20029=@"loc,{#10000},11,7,11,22"
|
||||
locations_default(#20029,#10000,11,7,11,22)
|
||||
hasLocation(#20028,#20029)
|
||||
enclosing_stmt(#20028,#20026)
|
||||
expr_containers(#20028,#20002)
|
||||
#20030=*
|
||||
exprs(#20030,14,#20028,-1,"console.log")
|
||||
#20031=@"loc,{#10000},11,7,11,17"
|
||||
locations_default(#20031,#10000,11,7,11,17)
|
||||
hasLocation(#20030,#20031)
|
||||
enclosing_stmt(#20030,#20026)
|
||||
expr_containers(#20030,#20002)
|
||||
#20032=*
|
||||
exprs(#20032,79,#20030,0,"console")
|
||||
hasLocation(#20032,#20010)
|
||||
enclosing_stmt(#20032,#20026)
|
||||
expr_containers(#20032,#20002)
|
||||
literals("console","console",#20032)
|
||||
#20033=@"var;{console};{#20000}"
|
||||
variables(#20033,"console",#20000)
|
||||
bind(#20032,#20033)
|
||||
#20034=*
|
||||
exprs(#20034,0,#20030,1,"log")
|
||||
hasLocation(#20034,#20014)
|
||||
enclosing_stmt(#20034,#20026)
|
||||
expr_containers(#20034,#20002)
|
||||
literals("log","log",#20034)
|
||||
#20035=*
|
||||
exprs(#20035,3,#20028,0,"123")
|
||||
hasLocation(#20035,#20018)
|
||||
enclosing_stmt(#20035,#20026)
|
||||
expr_containers(#20035,#20002)
|
||||
literals("123","123",#20035)
|
||||
#20036=*
|
||||
entry_cfg_node(#20036,#20002)
|
||||
hasLocation(#20036,#20004)
|
||||
#20037=*
|
||||
exit_cfg_node(#20037,#20002)
|
||||
hasLocation(#20037,#20024)
|
||||
successor(#20026,#20032)
|
||||
successor(#20035,#20028)
|
||||
successor(#20034,#20030)
|
||||
successor(#20032,#20034)
|
||||
successor(#20030,#20035)
|
||||
successor(#20028,#20037)
|
||||
successor(#20036,#20026)
|
||||
toplevel_parent_xml_node(#20002,#20001)
|
||||
#20038=*
|
||||
template_placeholder_tag_info(#20038,#10000,"<%@ page contentType=""text/html;charset=UTF-8"" language=""java"" %>")
|
||||
#20039=@"loc,{#10000},1,1,1,65"
|
||||
locations_default(#20039,#10000,1,1,1,65)
|
||||
hasLocation(#20038,#20039)
|
||||
#20040=*
|
||||
xmlElements(#20040,"html",#10000,0,#10000)
|
||||
#20041=@"loc,{#10000},2,1,14,7"
|
||||
locations_default(#20041,#10000,2,1,14,7)
|
||||
xmllocations(#20040,#20041)
|
||||
#20042=*
|
||||
xmlElements(#20042,"body",#20040,1,#10000)
|
||||
#20043=@"loc,{#10000},6,3,13,9"
|
||||
locations_default(#20043,#10000,6,3,13,9)
|
||||
xmllocations(#20042,#20043)
|
||||
#20044=*
|
||||
template_placeholder_tag_info(#20044,#20042,"<% String name = ""John""; %>")
|
||||
#20045=@"loc,{#10000},8,5,8,31"
|
||||
locations_default(#20045,#10000,8,5,8,31)
|
||||
hasLocation(#20044,#20045)
|
||||
xmlElements(#20001,"script",#20042,2,#10000)
|
||||
#20046=@"loc,{#10000},10,5,12,13"
|
||||
locations_default(#20046,#10000,10,5,12,13)
|
||||
xmllocations(#20001,#20046)
|
||||
#20047=*
|
||||
xmlElements(#20047,"p",#20042,1,#10000)
|
||||
#20048=@"loc,{#10000},9,5,9,31"
|
||||
locations_default(#20048,#10000,9,5,9,31)
|
||||
xmllocations(#20047,#20048)
|
||||
#20049=*
|
||||
template_placeholder_tag_info(#20049,#20047,"<%= name %>")
|
||||
#20050=@"loc,{#10000},9,16,9,26"
|
||||
locations_default(#20050,#10000,9,16,9,26)
|
||||
hasLocation(#20049,#20050)
|
||||
scopes(#20000,0)
|
||||
#20051=@"script;{#10000},9,19"
|
||||
#20052=*
|
||||
lines(#20052,#20051," name ","")
|
||||
#20053=@"loc,{#10000},9,19,9,24"
|
||||
locations_default(#20053,#10000,9,19,9,24)
|
||||
hasLocation(#20052,#20053)
|
||||
indentation(#10000,9," ",1)
|
||||
numlines(#20051,1,1,0)
|
||||
#20054=*
|
||||
tokeninfo(#20054,6,#20051,0,"name")
|
||||
#20055=@"loc,{#10000},9,20,9,23"
|
||||
locations_default(#20055,#10000,9,20,9,23)
|
||||
hasLocation(#20054,#20055)
|
||||
#20056=*
|
||||
tokeninfo(#20056,0,#20051,1,"")
|
||||
#20057=@"loc,{#10000},9,25,9,24"
|
||||
locations_default(#20057,#10000,9,25,9,24)
|
||||
hasLocation(#20056,#20057)
|
||||
toplevels(#20051,4)
|
||||
hasLocation(#20051,#20053)
|
||||
#20058=@"module;{#10000},9,19"
|
||||
scopes(#20058,3)
|
||||
scopenodes(#20051,#20058)
|
||||
scopenesting(#20058,#20000)
|
||||
is_module(#20051)
|
||||
#20059=*
|
||||
stmts(#20059,2,#20051,0,"name")
|
||||
hasLocation(#20059,#20055)
|
||||
stmt_containers(#20059,#20051)
|
||||
#20060=*
|
||||
exprs(#20060,79,#20059,0,"name")
|
||||
hasLocation(#20060,#20055)
|
||||
enclosing_stmt(#20060,#20059)
|
||||
expr_containers(#20060,#20051)
|
||||
literals("name","name",#20060)
|
||||
#20061=@"var;{name};{#20058}"
|
||||
variables(#20061,"name",#20058)
|
||||
bind(#20060,#20061)
|
||||
#20062=*
|
||||
entry_cfg_node(#20062,#20051)
|
||||
#20063=@"loc,{#10000},9,19,9,18"
|
||||
locations_default(#20063,#10000,9,19,9,18)
|
||||
hasLocation(#20062,#20063)
|
||||
#20064=*
|
||||
exit_cfg_node(#20064,#20051)
|
||||
hasLocation(#20064,#20057)
|
||||
successor(#20059,#20060)
|
||||
successor(#20060,#20064)
|
||||
successor(#20062,#20059)
|
||||
toplevel_parent_xml_node(#20051,#20049)
|
||||
#20065=*
|
||||
xmlElements(#20065,"h1",#20042,0,#10000)
|
||||
#20066=@"loc,{#10000},7,5,7,25"
|
||||
locations_default(#20066,#10000,7,5,7,25)
|
||||
xmllocations(#20065,#20066)
|
||||
#20067=*
|
||||
xmlElements(#20067,"head",#20040,0,#10000)
|
||||
#20068=@"loc,{#10000},3,3,5,9"
|
||||
locations_default(#20068,#10000,3,3,5,9)
|
||||
xmllocations(#20067,#20068)
|
||||
#20069=*
|
||||
xmlElements(#20069,"title",#20067,0,#10000)
|
||||
#20070=@"loc,{#10000},4,5,4,34"
|
||||
locations_default(#20070,#10000,4,5,4,34)
|
||||
xmllocations(#20069,#20070)
|
||||
numlines(#10000,14,2,0)
|
||||
filetype(#10000,"html")
|
||||
@@ -1,3 +1,33 @@
|
||||
## 0.8.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.8.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.8.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The contents of `.jsp` files are now extracted, and any `<script>` tags inside these files will be parsed as JavaScript.
|
||||
* [Import attributes](https://github.com/tc39/proposal-import-attributes) are now supported in JavaScript code.
|
||||
Note that import attributes are an evolution of an earlier proposal called "import assertions", which were implemented in TypeScript 4.5.
|
||||
The QL library includes new predicates named `getImportAttributes()` that should be used in favor of the now deprecated `getImportAssertion()`;
|
||||
in addition, the `getImportAttributes()` method of the `DynamicImportExpr` has been renamed to `getImportOptions()`.
|
||||
* Deleted the deprecated `getAnImmediateUse`, `getAUse`, `getARhs`, and `getAValueReachingRhs` predicates from the `API::Node` class.
|
||||
* Deleted the deprecated `mayReferToParameter` predicate from `DataFlow::Node`.
|
||||
* Deleted the deprecated `getStaticMethod` and `getAStaticMethod` predicates from `DataFlow::ClassNode`.
|
||||
* Deleted the deprecated `isLibaryFile` predicate from `ClassifyFiles.qll`, use `isLibraryFile` instead.
|
||||
* Deleted many library models that were build on the AST. Use the new models that are build on the dataflow library instead.
|
||||
* Deleted the deprecated `semmle.javascript.security.performance` folder, use `semmle.javascript.security.regexp` instead.
|
||||
* Tagged template literals have been added to `DataFlow::CallNode`. This allows the analysis to find flow into functions called with a tagged template literal,
|
||||
and the arguments to a tagged template literal are part of the API-graph in `ApiGraphs.qll`.
|
||||
|
||||
## 0.8.0
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.7.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Tagged template literals have been added to `DataFlow::CallNode`. This allows the analysis to find flow into functions called with a tagged template literal,
|
||||
and the arguments to a tagged template literal are part of the API-graph in `ApiGraphs.qll`.
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Deleted the deprecated `getAnImmediateUse`, `getAUse`, `getARhs`, and `getAValueReachingRhs` predicates from the `API::Node` class.
|
||||
* Deleted the deprecated `mayReferToParameter` predicate from `DataFlow::Node`.
|
||||
* Deleted the deprecated `getStaticMethod` and `getAStaticMethod` predicates from `DataFlow::ClassNode`.
|
||||
* Deleted the deprecated `isLibaryFile` predicate from `ClassifyFiles.qll`, use `isLibraryFile` instead.
|
||||
* Deleted many library models that were build on the AST. Use the new models that are build on the dataflow library instead.
|
||||
* Deleted the deprecated `semmle.javascript.security.performance` folder, use `semmle.javascript.security.regexp` instead.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* TypeScript 5.3 is now supported.
|
||||
4
javascript/ql/lib/change-notes/2023-11-23-sqllite.md
Normal file
4
javascript/ql/lib/change-notes/2023-11-23-sqllite.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added models for the `sqlite` and `better-sqlite3` npm packages.
|
||||
3
javascript/ql/lib/change-notes/released/0.8.0.md
Normal file
3
javascript/ql/lib/change-notes/released/0.8.0.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.0
|
||||
|
||||
No user-facing changes.
|
||||
17
javascript/ql/lib/change-notes/released/0.8.1.md
Normal file
17
javascript/ql/lib/change-notes/released/0.8.1.md
Normal file
@@ -0,0 +1,17 @@
|
||||
## 0.8.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The contents of `.jsp` files are now extracted, and any `<script>` tags inside these files will be parsed as JavaScript.
|
||||
* [Import attributes](https://github.com/tc39/proposal-import-attributes) are now supported in JavaScript code.
|
||||
Note that import attributes are an evolution of an earlier proposal called "import assertions", which were implemented in TypeScript 4.5.
|
||||
The QL library includes new predicates named `getImportAttributes()` that should be used in favor of the now deprecated `getImportAssertion()`;
|
||||
in addition, the `getImportAttributes()` method of the `DynamicImportExpr` has been renamed to `getImportOptions()`.
|
||||
* Deleted the deprecated `getAnImmediateUse`, `getAUse`, `getARhs`, and `getAValueReachingRhs` predicates from the `API::Node` class.
|
||||
* Deleted the deprecated `mayReferToParameter` predicate from `DataFlow::Node`.
|
||||
* Deleted the deprecated `getStaticMethod` and `getAStaticMethod` predicates from `DataFlow::ClassNode`.
|
||||
* Deleted the deprecated `isLibaryFile` predicate from `ClassifyFiles.qll`, use `isLibraryFile` instead.
|
||||
* Deleted many library models that were build on the AST. Use the new models that are build on the dataflow library instead.
|
||||
* Deleted the deprecated `semmle.javascript.security.performance` folder, use `semmle.javascript.security.regexp` instead.
|
||||
* Tagged template literals have been added to `DataFlow::CallNode`. This allows the analysis to find flow into functions called with a tagged template literal,
|
||||
and the arguments to a tagged template literal are part of the API-graph in `ApiGraphs.qll`.
|
||||
3
javascript/ql/lib/change-notes/released/0.8.2.md
Normal file
3
javascript/ql/lib/change-notes/released/0.8.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.2
|
||||
|
||||
No user-facing changes.
|
||||
3
javascript/ql/lib/change-notes/released/0.8.3.md
Normal file
3
javascript/ql/lib/change-notes/released/0.8.3.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.3
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.7.5
|
||||
lastReleaseVersion: 0.8.3
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-all
|
||||
version: 0.8.0-dev
|
||||
version: 0.8.4-dev
|
||||
groups: javascript
|
||||
dbscheme: semmlecode.javascript.dbscheme
|
||||
extractor: javascript
|
||||
|
||||
@@ -91,14 +91,27 @@ class ImportDeclaration extends Stmt, Import, @import_declaration {
|
||||
override PathExpr getImportedPath() { result = this.getChildExpr(-1) }
|
||||
|
||||
/**
|
||||
* Gets the object literal passed as part of the `assert` clause in this import declaration.
|
||||
* Gets the object literal passed as part of the `with` (or `assert`) clause in this import declaration.
|
||||
*
|
||||
* For example, this gets the `{ type: "json" }` object literal in the following:
|
||||
* ```js
|
||||
* import foo from "foo" with { type: "json" };
|
||||
* import foo from "foo" assert { type: "json" };
|
||||
* ```
|
||||
*/
|
||||
ObjectExpr getImportAssertion() { result = this.getChildExpr(-10) }
|
||||
ObjectExpr getImportAttributes() { result = this.getChildExpr(-10) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: use `getImportAttributes` instead.
|
||||
* Gets the object literal passed as part of the `with` (or `assert`) clause in this import declaration.
|
||||
*
|
||||
* For example, this gets the `{ type: "json" }` object literal in the following:
|
||||
* ```js
|
||||
* import foo from "foo" with { type: "json" };
|
||||
* import foo from "foo" assert { type: "json" };
|
||||
* ```
|
||||
*/
|
||||
deprecated ObjectExpr getImportAssertion() { result = this.getImportAttributes() }
|
||||
|
||||
/** Gets the `i`th import specifier of this import declaration. */
|
||||
ImportSpecifier getSpecifier(int i) { result = this.getChildExpr(i) }
|
||||
@@ -322,17 +335,33 @@ abstract class ExportDeclaration extends Stmt, @export_declaration {
|
||||
override string getAPrimaryQlClass() { result = "ExportDeclaration" }
|
||||
|
||||
/**
|
||||
* Gets the object literal passed as part of the `assert` clause, if this is
|
||||
* Gets the object literal passed as part of the `with` (or `assert`) clause, if this is
|
||||
* a re-export declaration.
|
||||
*
|
||||
* For example, this gets the `{ type: "json" }` expression in each of the following:
|
||||
* ```js
|
||||
* export { x } from 'foo' assert { type: "json" };
|
||||
* export { x } from 'foo' with { type: "json" };
|
||||
* export * from 'foo' with { type: "json" };
|
||||
* export * as x from 'foo' with { type: "json" };
|
||||
* export * from 'foo' assert { type: "json" };
|
||||
* export * as x from 'foo' assert { type: "json" };
|
||||
* ```
|
||||
*/
|
||||
ObjectExpr getImportAssertion() { result = this.getChildExpr(-10) }
|
||||
ObjectExpr getImportAttributes() { result = this.getChildExpr(-10) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: use `getImportAttributes` instead.
|
||||
* Gets the object literal passed as part of the `with` (or `assert`) clause, if this is
|
||||
* a re-export declaration.
|
||||
*
|
||||
* For example, this gets the `{ type: "json" }` expression in each of the following:
|
||||
* ```js
|
||||
* export { x } from 'foo' with { type: "json" };
|
||||
* export * from 'foo' with { type: "json" };
|
||||
* export * as x from 'foo' with { type: "json" };
|
||||
* export * from 'foo' assert { type: "json" };
|
||||
* ```
|
||||
*/
|
||||
deprecated ObjectExpr getImportAssertion() { result = this.getImportAttributes() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2807,7 +2807,7 @@ class FunctionBindExpr extends @bind_expr, Expr {
|
||||
*
|
||||
* ```
|
||||
* import("fs")
|
||||
* import("foo", { assert: { type: "json" }})
|
||||
* import("foo", { with: { type: "json" }})
|
||||
* ```
|
||||
*/
|
||||
class DynamicImportExpr extends @dynamic_import, Expr, Import {
|
||||
@@ -2823,12 +2823,23 @@ class DynamicImportExpr extends @dynamic_import, Expr, Import {
|
||||
/**
|
||||
* Gets the second "argument" to the import expression, that is, the `Y` in `import(X, Y)`.
|
||||
*
|
||||
* For example, gets the `{ assert: { type: "json" }}` expression in the following:
|
||||
* For example, gets the `{ with: { type: "json" }}` expression in the following:
|
||||
* ```js
|
||||
* import('foo', { assert: { type: "json" }})
|
||||
* import('foo', { with: { type: "json" }})
|
||||
* ```
|
||||
*/
|
||||
Expr getImportAttributes() { result = this.getChildExpr(1) }
|
||||
Expr getImportOptions() { result = this.getChildExpr(1) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: use `getImportOptions` instead.
|
||||
* Gets the second "argument" to the import expression, that is, the `Y` in `import(X, Y)`.
|
||||
*
|
||||
* For example, gets the `{ with: { type: "json" }}` expression in the following:
|
||||
* ```js
|
||||
* import('foo', { with: { type: "json" }})
|
||||
* ```
|
||||
*/
|
||||
deprecated Expr getImportAttributes() { result = this.getImportOptions() }
|
||||
|
||||
override Module getEnclosingModule() { result = this.getTopLevel() }
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ private module AsmCrypto {
|
||||
private class Apply extends CryptographicOperation::Range instanceof DataFlow::CallNode {
|
||||
DataFlow::Node input;
|
||||
CryptographicAlgorithm algorithm; // non-functional
|
||||
DataFlow::PropRead algorithmSelection;
|
||||
private string algorithmName;
|
||||
private string methodName;
|
||||
|
||||
@@ -68,11 +69,14 @@ private module AsmCrypto {
|
||||
exists(DataFlow::SourceNode asmCrypto |
|
||||
asmCrypto = DataFlow::globalVarRef("asmCrypto") and
|
||||
algorithm.matchesName(algorithmName) and
|
||||
this = asmCrypto.getAPropertyRead(algorithmName).getAMemberCall(methodName) and
|
||||
algorithmSelection = asmCrypto.getAPropertyRead(algorithmName) and
|
||||
this = algorithmSelection.getAMemberCall(methodName) and
|
||||
input = this.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getInitialization() { result = algorithmSelection }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = input }
|
||||
|
||||
override CryptographicAlgorithm getAlgorithm() { result = algorithm }
|
||||
@@ -103,6 +107,7 @@ private module BrowserIdCrypto {
|
||||
|
||||
private class Apply extends CryptographicOperation::Range instanceof DataFlow::MethodCallNode {
|
||||
CryptographicAlgorithm algorithm; // non-functional
|
||||
DataFlow::CallNode keygen;
|
||||
|
||||
Apply() {
|
||||
/*
|
||||
@@ -122,8 +127,7 @@ private module BrowserIdCrypto {
|
||||
*/
|
||||
|
||||
exists(
|
||||
DataFlow::SourceNode mod, DataFlow::Node algorithmNameNode, DataFlow::CallNode keygen,
|
||||
DataFlow::FunctionNode callback
|
||||
DataFlow::SourceNode mod, DataFlow::Node algorithmNameNode, DataFlow::FunctionNode callback
|
||||
|
|
||||
mod = DataFlow::moduleImport("browserid-crypto") and
|
||||
keygen = mod.getAMemberCall("generateKeypair") and
|
||||
@@ -134,6 +138,8 @@ private module BrowserIdCrypto {
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getInitialization() { result = keygen }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = super.getArgument(0) }
|
||||
|
||||
override CryptographicAlgorithm getAlgorithm() { result = algorithm }
|
||||
@@ -239,6 +245,8 @@ private module NodeJSCrypto {
|
||||
|
||||
Apply() { this = instantiation.getAMethodCall(any(string m | m = "update" or m = "write")) }
|
||||
|
||||
override DataFlow::Node getInitialization() { result = instantiation }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = super.getArgument(0) }
|
||||
|
||||
override CryptographicAlgorithm getAlgorithm() { result = instantiation.getAlgorithm() }
|
||||
@@ -324,7 +332,9 @@ private module CryptoJS {
|
||||
)
|
||||
}
|
||||
|
||||
private API::CallNode getEncryptionApplication(API::Node input, CryptographicAlgorithm algorithm) {
|
||||
private API::CallNode getEncryptionApplication(
|
||||
API::Node input, API::Node algorithmNode, CryptographicAlgorithm algorithm
|
||||
) {
|
||||
/*
|
||||
* ```
|
||||
* var CryptoJS = require("crypto-js");
|
||||
@@ -338,11 +348,14 @@ private module CryptoJS {
|
||||
* Also matches where `CryptoJS.<algorithmName>` has been replaced by `require("crypto-js/<algorithmName>")`
|
||||
*/
|
||||
|
||||
result = getAlgorithmNode(algorithm).getMember("encrypt").getACall() and
|
||||
algorithmNode = getAlgorithmNode(algorithm) and
|
||||
result = algorithmNode.getMember("encrypt").getACall() and
|
||||
input = result.getParameter(0)
|
||||
}
|
||||
|
||||
private API::CallNode getDirectApplication(API::Node input, CryptographicAlgorithm algorithm) {
|
||||
private API::CallNode getDirectApplication(
|
||||
API::Node input, API::Node algorithmNode, CryptographicAlgorithm algorithm
|
||||
) {
|
||||
/*
|
||||
* ```
|
||||
* var CryptoJS = require("crypto-js");
|
||||
@@ -357,7 +370,8 @@ private module CryptoJS {
|
||||
* Also matches where `CryptoJS.<algorithmName>` has been replaced by `require("crypto-js/<algorithmName>")`
|
||||
*/
|
||||
|
||||
result = getAlgorithmNode(algorithm).getACall() and
|
||||
algorithmNode = getAlgorithmNode(algorithm) and
|
||||
result = algorithmNode.getACall() and
|
||||
input = result.getParameter(0)
|
||||
}
|
||||
|
||||
@@ -389,18 +403,23 @@ private module CryptoJS {
|
||||
private class Apply extends CryptographicOperation::Range instanceof API::CallNode {
|
||||
API::Node input;
|
||||
CryptographicAlgorithm algorithm; // non-functional
|
||||
DataFlow::Node instantiation;
|
||||
|
||||
Apply() {
|
||||
this = getEncryptionApplication(input, algorithm)
|
||||
or
|
||||
this = getDirectApplication(input, algorithm)
|
||||
or
|
||||
exists(InstantiatedAlgorithm instantiation |
|
||||
this = getUpdatedApplication(input, instantiation) and
|
||||
algorithm = instantiation.getAlgorithm()
|
||||
exists(API::Node algorithmNode |
|
||||
this = getEncryptionApplication(input, algorithmNode, algorithm)
|
||||
or
|
||||
this = getDirectApplication(input, algorithmNode, algorithm)
|
||||
|
|
||||
instantiation = algorithmNode.asSource()
|
||||
)
|
||||
or
|
||||
this = getUpdatedApplication(input, instantiation) and
|
||||
algorithm = instantiation.(InstantiatedAlgorithm).getAlgorithm()
|
||||
}
|
||||
|
||||
override DataFlow::Node getInitialization() { result = instantiation }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = input.asSink() }
|
||||
|
||||
override CryptographicAlgorithm getAlgorithm() { result = algorithm }
|
||||
@@ -504,6 +523,8 @@ private module TweetNaCl {
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getInitialization() { result = this }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = input }
|
||||
|
||||
override CryptographicAlgorithm getAlgorithm() { result = algorithm }
|
||||
@@ -539,6 +560,7 @@ private module HashJs {
|
||||
private class Apply extends CryptographicOperation::Range instanceof DataFlow::CallNode {
|
||||
DataFlow::Node input;
|
||||
CryptographicAlgorithm algorithm; // non-functional
|
||||
DataFlow::CallNode init;
|
||||
|
||||
Apply() {
|
||||
/*
|
||||
@@ -554,10 +576,13 @@ private module HashJs {
|
||||
* Also matches where `hash.<algorithmName>()` has been replaced by a more specific require a la `require("hash.js/lib/hash/sha/512")`
|
||||
*/
|
||||
|
||||
this = getAlgorithmNode(algorithm).getAMemberCall("update") and
|
||||
init = getAlgorithmNode(algorithm) and
|
||||
this = init.getAMemberCall("update") and
|
||||
input = super.getArgument(0)
|
||||
}
|
||||
|
||||
override DataFlow::Node getInitialization() { result = init }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = input }
|
||||
|
||||
override CryptographicAlgorithm getAlgorithm() { result = algorithm }
|
||||
@@ -653,6 +678,8 @@ private module Forge {
|
||||
algorithm = cipher.getAlgorithm()
|
||||
}
|
||||
|
||||
override DataFlow::Node getInitialization() { result = cipher }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = input }
|
||||
|
||||
override CryptographicAlgorithm getAlgorithm() { result = algorithm }
|
||||
@@ -715,6 +742,8 @@ private module Md5 {
|
||||
super.getArgument(0) = input
|
||||
}
|
||||
|
||||
override DataFlow::Node getInitialization() { result = this }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = input }
|
||||
|
||||
override CryptographicAlgorithm getAlgorithm() { result = algorithm }
|
||||
@@ -731,17 +760,18 @@ private module Bcrypt {
|
||||
private class Apply extends CryptographicOperation::Range instanceof DataFlow::CallNode {
|
||||
DataFlow::Node input;
|
||||
CryptographicAlgorithm algorithm;
|
||||
API::Node init;
|
||||
|
||||
Apply() {
|
||||
// `require("bcrypt").hash(password);` with minor naming variations
|
||||
algorithm.matchesName("BCRYPT") and
|
||||
this =
|
||||
API::moduleImport(["bcrypt", "bcryptjs", "bcrypt-nodejs"])
|
||||
.getMember(["hash", "hashSync"])
|
||||
.getACall() and
|
||||
init = API::moduleImport(["bcrypt", "bcryptjs", "bcrypt-nodejs"]) and
|
||||
this = init.getMember(["hash", "hashSync"]).getACall() and
|
||||
super.getArgument(0) = input
|
||||
}
|
||||
|
||||
override DataFlow::Node getInitialization() { result = init.asSource() }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = input }
|
||||
|
||||
override CryptographicAlgorithm getAlgorithm() { result = algorithm }
|
||||
@@ -769,6 +799,8 @@ private module Hasha {
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getInitialization() { result = this }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = input }
|
||||
|
||||
override CryptographicAlgorithm getAlgorithm() { result = algorithm }
|
||||
|
||||
@@ -618,6 +618,10 @@ module Express {
|
||||
or
|
||||
kind = "body" and
|
||||
this = ref.getAPropertyRead("body")
|
||||
or
|
||||
// `req.path`
|
||||
kind = "url" and
|
||||
this = ref.getAPropertyRead("path")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ private module Postgres {
|
||||
API::Node clientOrPool() { result = API::Node::ofType("pg", ["Client", "PoolClient", "Pool"]) }
|
||||
|
||||
/** A call to the Postgres `query` method. */
|
||||
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
|
||||
private class QueryCall extends DatabaseAccess, API::CallNode {
|
||||
QueryCall() { this = clientOrPool().getMember(["execute", "query"]).getACall() }
|
||||
|
||||
override DataFlow::Node getAResult() {
|
||||
@@ -117,15 +117,25 @@ private module Postgres {
|
||||
PromiseFlow::loadStep(this.getALocalUse(), result, Promises::valueProp())
|
||||
}
|
||||
|
||||
override DataFlow::Node getAQueryArgument() { result = this.getArgument(0) }
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
result = this.getArgument(0) or result = this.getParameter(0).getMember("text").asSink()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Postgres Query class.
|
||||
* This class can be used to create reusable query objects (see https://node-postgres.com/apis/client).
|
||||
*/
|
||||
API::Node query() { result = API::moduleImport("pg").getMember("Query") }
|
||||
|
||||
/** An expression that is passed to the `query` method and hence interpreted as SQL. */
|
||||
class QueryString extends SQL::SqlString {
|
||||
QueryString() {
|
||||
this = any(QueryCall qc).getAQueryArgument()
|
||||
or
|
||||
this = API::moduleImport("pg-cursor").getParameter(0).asSink()
|
||||
or
|
||||
this = query().getParameter(0).asSink()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +253,7 @@ private module Postgres {
|
||||
/**
|
||||
* Provides classes modeling the `sqlite3` package.
|
||||
*/
|
||||
private module Sqlite {
|
||||
private module Sqlite3 {
|
||||
/** Gets an expression that constructs or returns a Sqlite database instance. */
|
||||
API::Node database() { result = API::Node::ofType("sqlite3", "Database") }
|
||||
|
||||
@@ -267,6 +277,62 @@ private module Sqlite {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides classes modeling the `sqlite` package.
|
||||
*/
|
||||
private module Sqlite {
|
||||
/** Gets an expression that constructs or returns a Sqlite database instance. */
|
||||
API::Node database() {
|
||||
result = API::moduleImport("sqlite").getMember("open").getReturn().getPromised()
|
||||
}
|
||||
|
||||
/** A call to a Sqlite query method. */
|
||||
private class QueryCall extends DatabaseAccess, API::CallNode {
|
||||
QueryCall() {
|
||||
this = database().getMember(["all", "each", "exec", "get", "prepare", "run"]).getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAResult() { result = this }
|
||||
|
||||
override DataFlow::Node getAQueryArgument() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/** An expression that is passed to the `query` method and hence interpreted as SQL. */
|
||||
class QueryString extends SQL::SqlString {
|
||||
QueryString() { this = any(QueryCall qc).getAQueryArgument() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides classes modeling the `better-sqlite3` package.
|
||||
*/
|
||||
private module BetterSqlite3 {
|
||||
/**
|
||||
* Gets a `better-sqlite3` database instance.
|
||||
*/
|
||||
API::Node database() {
|
||||
result =
|
||||
[
|
||||
API::moduleImport("better-sqlite3").getInstance(),
|
||||
API::moduleImport("better-sqlite3").getReturn()
|
||||
]
|
||||
or
|
||||
result = database().getMember("exec").getReturn()
|
||||
}
|
||||
|
||||
/** A call to a better-sqlite3 query method. */
|
||||
private class QueryCall extends DatabaseAccess, API::CallNode {
|
||||
QueryCall() { this = database().getMember(["exec", "prepare"]).getACall() }
|
||||
|
||||
override DataFlow::Node getAQueryArgument() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/** An expression that is passed to the `query` method and hence interpreted as SQL. */
|
||||
class QueryString extends SQL::SqlString {
|
||||
QueryString() { this = any(QueryCall qc).getAQueryArgument() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides classes modeling the `mssql` package.
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,7 @@ extensible predicate sourceModel(string type, string path, string kind);
|
||||
extensible predicate sinkModel(string type, string path, string kind);
|
||||
|
||||
/**
|
||||
* Holds if calls to `(type, path)`, the value referred to by `input`
|
||||
* Holds if in calls to `(type, path)`, the value referred to by `input`
|
||||
* can flow to the value referred to by `output`.
|
||||
*
|
||||
* `kind` should be either `value` or `taint`, for value-preserving or taint-preserving steps,
|
||||
@@ -25,6 +25,13 @@ extensible predicate sinkModel(string type, string path, string kind);
|
||||
*/
|
||||
extensible predicate summaryModel(string type, string path, string input, string output, string kind);
|
||||
|
||||
/**
|
||||
* Holds if calls to `(type, path)` should be considered neutral. The meaning of this depends on the `kind`.
|
||||
* If `kind` is `summary`, the call does not propagate data flow. If `kind` is `source`, the call is not a source.
|
||||
* If `kind` is `sink`, the call is not a sink.
|
||||
*/
|
||||
extensible predicate neutralModel(string type, string path, string kind);
|
||||
|
||||
/**
|
||||
* Holds if `(type2, path)` should be seen as an instance of `type1`.
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,11 @@ extensions:
|
||||
extensible: summaryModel
|
||||
data: []
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: neutralModel
|
||||
data: []
|
||||
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: typeModel
|
||||
|
||||
@@ -40,6 +40,9 @@ module Cryptography {
|
||||
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
|
||||
CryptographicAlgorithm getAlgorithm() { result = super.getAlgorithm() }
|
||||
|
||||
/** Gets the data-flow node where the cryptographic algorithm used in this operation is configured. */
|
||||
DataFlow::Node getInitialization() { result = super.getInitialization() }
|
||||
|
||||
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
|
||||
DataFlow::Node getAnInput() { result = super.getAnInput() }
|
||||
|
||||
@@ -65,6 +68,9 @@ module Cryptography {
|
||||
* extend `CryptographicOperation` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the data-flow node where the cryptographic algorithm used in this operation is configured. */
|
||||
abstract DataFlow::Node getInitialization();
|
||||
|
||||
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
|
||||
abstract CryptographicAlgorithm getAlgorithm();
|
||||
|
||||
|
||||
@@ -19,7 +19,10 @@ module BrokenCryptoAlgorithm {
|
||||
/**
|
||||
* A data flow sink for sensitive information in broken or weak cryptographic algorithms.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
abstract class Sink extends DataFlow::Node {
|
||||
/** Gets the data-flow node where the cryptographic algorithm used in this operation is configured. */
|
||||
abstract DataFlow::Node getInitialization();
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer for sensitive information in broken or weak cryptographic algorithms.
|
||||
@@ -38,15 +41,17 @@ module BrokenCryptoAlgorithm {
|
||||
* An expression used by a broken or weak cryptographic algorithm.
|
||||
*/
|
||||
class WeakCryptographicOperationSink extends Sink {
|
||||
CryptographicOperation application;
|
||||
|
||||
WeakCryptographicOperationSink() {
|
||||
exists(CryptographicOperation application |
|
||||
(
|
||||
application.getAlgorithm().isWeak()
|
||||
or
|
||||
application.getBlockMode().isWeak()
|
||||
) and
|
||||
this = application.getAnInput()
|
||||
)
|
||||
(
|
||||
application.getAlgorithm().isWeak()
|
||||
or
|
||||
application.getBlockMode().isWeak()
|
||||
) and
|
||||
this = application.getAnInput()
|
||||
}
|
||||
|
||||
override DataFlow::Node getInitialization() { result = application.getInitialization() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,13 @@ abstract class RateLimitingMiddleware extends DataFlow::SourceNode {
|
||||
* A rate limiter constructed using the `express-rate-limit` package.
|
||||
*/
|
||||
class ExpressRateLimit extends RateLimitingMiddleware {
|
||||
ExpressRateLimit() { this = API::moduleImport("express-rate-limit").getReturn().asSource() }
|
||||
ExpressRateLimit() {
|
||||
this =
|
||||
[
|
||||
API::moduleImport("express-rate-limit"),
|
||||
API::moduleImport("express-rate-limit").getMember("rateLimit")
|
||||
].getReturn().asSource()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,11 +9,14 @@ private import semmle.javascript.security.dataflow.UnsafeJQueryPluginCustomizati
|
||||
import UnsafeHtmlConstructionCustomizations::UnsafeHtmlConstruction
|
||||
import semmle.javascript.security.TaintedObject
|
||||
|
||||
/** DEPRECATED: Mis-spelled class name, alias for Configuration. */
|
||||
deprecated class Configration = Configuration;
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about unsafe HTML constructed from library input vulnerabilities.
|
||||
*/
|
||||
class Configration extends TaintTracking::Configuration {
|
||||
Configration() { this = "UnsafeHtmlConstruction" }
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnsafeHtmlConstruction" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) {
|
||||
source instanceof Source and
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* James Kirrage, Asiri Rathnayake, Hayo Thielecke: Static Analysis for
|
||||
* Regular Expression Denial-of-Service Attacks. NSS 2013.
|
||||
* (http://www.cs.bham.ac.uk/~hxt/research/reg-exp-sec.pdf)
|
||||
* (https://arxiv.org/abs/1301.0849)
|
||||
* Asiri Rathnayake, Hayo Thielecke: Static Analysis for Regular Expression
|
||||
* Exponential Runtime via Substructural Logics. 2014.
|
||||
* (https://www.cs.bham.ac.uk/~hxt/research/redos_full.pdf)
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
## 0.8.3
|
||||
|
||||
### Query Metadata Changes
|
||||
|
||||
* Lower the security severity of log-injection to medium.
|
||||
* Increase the security severity of XSS to high.
|
||||
|
||||
## 0.8.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added modeling for importing `express-rate-limit` using a named import.
|
||||
|
||||
## 0.8.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added the `AmdModuleDefinition::Range` class, making it possible to define custom aliases for the AMD `define` function.
|
||||
|
||||
## 0.8.0
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.7.5
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -44,7 +44,9 @@ predicate hasDynamicHrefHostAttributeValue(DOM::ElementDefinition elem) {
|
||||
// ... that does not start with a fixed host or a relative path (common formats)
|
||||
not url.regexpMatch("(?i)((https?:)?//)?[-a-z0-9.]*/.*") and
|
||||
// .. that is not a call to `url_for` in a Flask / nunjucks application
|
||||
not url.regexpMatch("\\{\\{\\s*url(_for)?\\(.+\\).*")
|
||||
not url.regexpMatch("\\{\\{\\s*url(_for)?\\(.+\\).*") and
|
||||
// .. that is not a call to `url` in a Django application
|
||||
not url.regexpMatch("\\{%\\s*url.*")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/ReDoS">ReDoS</a>.</li>
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Time_complexity">Time complexity</a>.</li>
|
||||
<li>James Kirrage, Asiri Rathnayake, Hayo Thielecke:
|
||||
<a href="http://www.cs.bham.ac.uk/~hxt/research/reg-exp-sec.pdf">Static Analysis for Regular Expression Denial-of-Service Attack</a>.
|
||||
<a href="https://arxiv.org/abs/1301.0849">Static Analysis for Regular Expression Denial-of-Service Attack</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @description Replacing a substring with itself has no effect and may indicate a mistake.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 7.8
|
||||
* @security-severity 5.0
|
||||
* @id js/identity-replacement
|
||||
* @precision very-high
|
||||
* @tags correctness
|
||||
|
||||
@@ -13,40 +13,47 @@ attacker being able to influence behavior by modifying unexpected files.
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Validate user input before using it to construct a file path, either using an off-the-shelf library
|
||||
like the <code>sanitize-filename</code> npm package, or by performing custom validation.
|
||||
Validate user input before using it to construct a file path.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Ideally, follow these rules:
|
||||
The validation method you should use depends on whether you want to allow the user to specify complex paths with multiple components that may span multiple folders, or only simple filenames without a path component.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>Do not allow more than a single "." character.</li>
|
||||
<li>Do not allow directory separators such as "/" or "\" (depending on the file system).</li>
|
||||
<li>Do not rely on simply replacing problematic sequences such as "../". For example, after
|
||||
applying this filter to ".../...//", the resulting string would still be "../".</li>
|
||||
<li>Use a whitelist of known good patterns.</li>
|
||||
</ul>
|
||||
<p>
|
||||
In the former case, a common strategy is to make sure that the constructed file path is contained within a safe root folder.
|
||||
First, normalize the path using <code>path.resolve</code> or <code>fs.realpathSync</code> to remove any ".." segments.
|
||||
You should always normalize the file path since an unnormalized path that starts with the root folder can still be used to access files outside the root folder.
|
||||
Then, after you have normalized the path, check that the path starts with the root folder.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In the latter case, you can use a library like the <code>sanitize-filename</code> npm package to eliminate any special characters from the file path.
|
||||
Note that it is <i>not</i> sufficient to only remove "../" sequences: for example, applying this filter to ".../...//" would still result in the string "../".
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Finally, the simplest (but most restrictive) option is to use an allow list of safe patterns and make sure that the user input matches one of these patterns.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the first example, a file name is read from an HTTP request and then used to access a file.
|
||||
However, a malicious user could enter a file name which is an absolute path, such as
|
||||
<code>"/etc/passwd"</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In the second example, it appears that the user is restricted to opening a file within the
|
||||
<code>"user"</code> home directory. However, a malicious user could enter a file name containing
|
||||
special characters. For example, the string <code>"../../etc/passwd"</code> will result in the code
|
||||
reading the file located at <code>"/home/user/../../etc/passwd"</code>, which is the system's
|
||||
password file. This file would then be sent back to the user, giving them access to all the
|
||||
system's passwords.
|
||||
In the first (bad) example, the code reads the file name from an HTTP request, then accesses that file within a root folder.
|
||||
A malicious user could enter a file name containing "../" segments to navigate outside the root folder and access sensitive files.
|
||||
</p>
|
||||
|
||||
<sample src="examples/TaintedPath.js" />
|
||||
|
||||
<p>
|
||||
The second (good) example shows how to avoid access to sensitive files by sanitizing the file path.
|
||||
First, the code resolves the file name relative to a root folder, normalizing the path and removing any "../" segments in the process.
|
||||
Then, the code calls <code>fs.realpathSync</code> to resolve any symbolic links in the path.
|
||||
Finally, the code checks that the normalized path starts with the path of the root folder, ensuring the file is contained within the root folder.
|
||||
</p>
|
||||
|
||||
<sample src="examples/TaintedPathGood.js" />
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
var fs = require('fs'),
|
||||
http = require('http'),
|
||||
url = require('url');
|
||||
const fs = require('fs'),
|
||||
http = require('http'),
|
||||
url = require('url');
|
||||
|
||||
const ROOT = "/var/www/";
|
||||
|
||||
var server = http.createServer(function(req, res) {
|
||||
let path = url.parse(req.url, true).query.path;
|
||||
let filePath = url.parse(req.url, true).query.path;
|
||||
|
||||
// BAD: This could read any file on the file system
|
||||
res.write(fs.readFileSync(path));
|
||||
|
||||
// BAD: This could still read any file on the file system
|
||||
res.write(fs.readFileSync("/home/user/" + path));
|
||||
});
|
||||
// BAD: This function uses unsanitized input that can read any file on the file system.
|
||||
res.write(fs.readFileSync(ROOT + filePath, 'utf8'));
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
const fs = require('fs'),
|
||||
http = require('http'),
|
||||
path = require('path'),
|
||||
url = require('url');
|
||||
|
||||
const ROOT = "/var/www/";
|
||||
|
||||
var server = http.createServer(function(req, res) {
|
||||
let filePath = url.parse(req.url, true).query.path;
|
||||
|
||||
// GOOD: Verify that the file path is under the root directory
|
||||
filePath = fs.realpathSync(path.resolve(ROOT, filePath));
|
||||
if (!filePath.startsWith(ROOT)) {
|
||||
res.statusCode = 403;
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
res.write(fs.readFileSync(filePath, 'utf8'));
|
||||
});
|
||||
@@ -4,7 +4,7 @@
|
||||
* a cross-site scripting vulnerability.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 6.1
|
||||
* @security-severity 7.8
|
||||
* @precision high
|
||||
* @id js/reflected-xss
|
||||
* @tags security
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* a stored cross-site scripting vulnerability.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 6.1
|
||||
* @security-severity 7.8
|
||||
* @precision high
|
||||
* @id js/stored-xss
|
||||
* @tags security
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* a cross-site scripting vulnerability.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 6.1
|
||||
* @security-severity 7.8
|
||||
* @precision high
|
||||
* @id js/xss
|
||||
* @tags security
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* insertion of forged log entries by a malicious user.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 7.8
|
||||
* @security-severity 6.1
|
||||
* @precision medium
|
||||
* @id js/log-injection
|
||||
* @tags security
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @name Creating biased random numbers from a cryptographically secure source.
|
||||
* @name Creating biased random numbers from a cryptographically secure source
|
||||
* @description Some mathematical operations on random numbers can cause bias in
|
||||
* the results and compromise security.
|
||||
* @kind problem
|
||||
|
||||
@@ -16,9 +16,14 @@ import semmle.javascript.security.dataflow.BrokenCryptoAlgorithmQuery
|
||||
import semmle.javascript.security.SensitiveActions
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from
|
||||
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Source sourceNode,
|
||||
Sink sinkNode
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
not source.getNode() instanceof CleartextPasswordExpr // flagged by js/insufficient-password-hash
|
||||
select sink.getNode(), source, sink, "A broken or weak cryptographic algorithm depends on $@.",
|
||||
source.getNode(), "sensitive data from " + source.getNode().(Source).describe()
|
||||
sourceNode = source.getNode() and
|
||||
sinkNode = sink.getNode() and
|
||||
not sourceNode instanceof CleartextPasswordExpr // flagged by js/insufficient-password-hash
|
||||
select sinkNode, source, sink, "$@ depends on $@.", sinkNode.getInitialization(),
|
||||
"A broken or weak cryptographic algorithm", sourceNode,
|
||||
"sensitive data from " + sourceNode.describe()
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @description The total number of lines of JavaScript or TypeScript code across all files checked into the repository, except in `node_modules`. This is a useful metric of the size of a database. For all files that were seen during extraction, this query counts the lines of code, excluding whitespace or comments.
|
||||
* @kind metric
|
||||
* @tags summary
|
||||
* telemetry
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
4
javascript/ql/src/change-notes/2023-11-28-django-urls.md
Normal file
4
javascript/ql/src/change-notes/2023-11-28-django-urls.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added django URLs to detected "safe" URL patterns in `js/unsafe-external-link`.
|
||||
3
javascript/ql/src/change-notes/released/0.8.0.md
Normal file
3
javascript/ql/src/change-notes/released/0.8.0.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.0
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
## 0.8.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added the `AmdModuleDefinition::Range` class, making it possible to define custom aliases for the AMD `define` function.
|
||||
5
javascript/ql/src/change-notes/released/0.8.2.md
Normal file
5
javascript/ql/src/change-notes/released/0.8.2.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.8.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Added modeling for importing `express-rate-limit` using a named import.
|
||||
6
javascript/ql/src/change-notes/released/0.8.3.md
Normal file
6
javascript/ql/src/change-notes/released/0.8.3.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## 0.8.3
|
||||
|
||||
### Query Metadata Changes
|
||||
|
||||
* Lower the security severity of log-injection to medium.
|
||||
* Increase the security severity of XSS to high.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.7.5
|
||||
lastReleaseVersion: 0.8.3
|
||||
|
||||
157
javascript/ql/src/experimental/semmle/javascript/SQL.qll
Normal file
157
javascript/ql/src/experimental/semmle/javascript/SQL.qll
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Provides classes for working with SQL connectors.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
module ExperimentalSql {
|
||||
/**
|
||||
* Provides SQL injection Sinks for the [TypeORM](https://www.npmjs.com/package/typeorm) package
|
||||
*/
|
||||
private module TypeOrm {
|
||||
/**
|
||||
* Gets a `DataSource` instance
|
||||
*
|
||||
* `DataSource` is a pre-defined connection configuration to a specific database.
|
||||
*/
|
||||
API::Node dataSource() {
|
||||
result = API::moduleImport("typeorm").getMember("DataSource").getInstance()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a `QueryRunner` instance
|
||||
*/
|
||||
API::Node queryRunner() { result = dataSource().getMember("createQueryRunner").getReturn() }
|
||||
|
||||
/**
|
||||
* Gets a `*QueryBuilder` instance
|
||||
*/
|
||||
API::Node queryBuilderInstance() {
|
||||
// a `*QueryBuilder` instance of a Data Mapper based Entity
|
||||
result =
|
||||
[
|
||||
// Using DataSource
|
||||
dataSource(),
|
||||
// Using repository
|
||||
dataSource().getMember("getRepository").getReturn(),
|
||||
// Using entity manager
|
||||
dataSource().getMember("manager"), queryRunner().getMember("manager")
|
||||
].getMember("createQueryBuilder").getReturn()
|
||||
or
|
||||
// A `*QueryBuilder` instance of an Active record based Entity
|
||||
result =
|
||||
API::moduleImport("typeorm")
|
||||
.getMember("Entity")
|
||||
.getReturn()
|
||||
.getADecoratedClass()
|
||||
.getMember("createQueryBuilder")
|
||||
.getReturn()
|
||||
or
|
||||
// A WhereExpressionBuilder can be used in complex WHERE expression
|
||||
result =
|
||||
API::moduleImport("typeorm")
|
||||
.getMember(["Brackets", "NotBrackets"])
|
||||
.getParameter(0)
|
||||
.getParameter(0)
|
||||
or
|
||||
// In case of custom query builders
|
||||
result =
|
||||
API::moduleImport("typeorm")
|
||||
.getMember([
|
||||
"SelectQueryBuilder", "InsertQueryBuilder", "RelationQueryBuilder",
|
||||
"UpdateQueryBuilder", "WhereExpressionBuilder"
|
||||
])
|
||||
.getInstance()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets function names which create any type of `QueryBuilder` like `WhereExpressionBuilder` or `InsertQueryBuilder`
|
||||
*/
|
||||
string queryBuilderMethods() {
|
||||
result =
|
||||
[
|
||||
"select", "addSelect", "where", "andWhere", "orWhere", "having", "orHaving", "andHaving",
|
||||
"orderBy", "addOrderBy", "distinctOn", "groupBy", "addCommonTableExpression",
|
||||
"leftJoinAndSelect", "innerJoinAndSelect", "leftJoin", "innerJoin", "leftJoinAndMapOne",
|
||||
"innerJoinAndMapOne", "leftJoinAndMapMany", "innerJoinAndMapMany", "orUpdate", "orIgnore",
|
||||
"values", "set"
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets function names that the return values of these functions can be the results of a database query run
|
||||
*/
|
||||
string queryBuilderResult() {
|
||||
result = ["getOne", "getOneOrFail", "getMany", "getRawOne", "getRawMany", "stream"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a QueryBuilder instance that has a query builder function
|
||||
*/
|
||||
API::Node getASuccessorOfBuilderInstance(string queryBuilderMethod) {
|
||||
result.getMember(queryBuilderMethod) = queryBuilderInstance().getASuccessor*()
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to some successor functions of TypeORM `createQueryBuilder` function which are dangerous
|
||||
*/
|
||||
private class QueryBuilderCall extends DatabaseAccess, DataFlow::Node {
|
||||
API::Node queryBuilder;
|
||||
|
||||
QueryBuilderCall() {
|
||||
queryBuilder = getASuccessorOfBuilderInstance(queryBuilderMethods()) and
|
||||
this = queryBuilder.asSource()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAResult() {
|
||||
result = queryBuilder.getMember(queryBuilderResult()).getReturn().asSource()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
exists(string memberName | memberName = queryBuilderMethods() |
|
||||
memberName = ["leftJoinAndSelect", "innerJoinAndSelect", "leftJoin", "innerJoin"] and
|
||||
result = queryBuilder.getMember(memberName).getParameter(2).asSink()
|
||||
or
|
||||
memberName =
|
||||
["leftJoinAndMapOne", "innerJoinAndMapOne", "leftJoinAndMapMany", "innerJoinAndMapMany"] and
|
||||
result = queryBuilder.getMember(memberName).getParameter(3).asSink()
|
||||
or
|
||||
memberName =
|
||||
[
|
||||
"select", "addSelect", "where", "andWhere", "orWhere", "having", "orHaving",
|
||||
"andHaving", "orderBy", "addOrderBy", "distinctOn", "groupBy",
|
||||
"addCommonTableExpression"
|
||||
] and
|
||||
result = queryBuilder.getMember(memberName).getParameter(0).asSink()
|
||||
or
|
||||
memberName = ["orIgnore", "orUpdate"] and
|
||||
result = queryBuilder.getMember(memberName).getParameter([0, 1]).asSink()
|
||||
or
|
||||
// following functions if use a function as their input fields,called function parameters which are vulnerable
|
||||
memberName = ["values", "set"] and
|
||||
result =
|
||||
queryBuilder.getMember(memberName).getParameter(0).getAMember().getReturn().asSink()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the TypeORM `query` function of a `QueryRunner`
|
||||
*/
|
||||
private class QueryRunner extends DatabaseAccess, API::CallNode {
|
||||
QueryRunner() { queryRunner().getMember("query").getACall() = this }
|
||||
|
||||
override DataFlow::Node getAResult() { result = this }
|
||||
|
||||
override DataFlow::Node getAQueryArgument() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/** An expression that is passed to the `query` function and hence interpreted as SQL. */
|
||||
class QueryString extends SQL::SqlString {
|
||||
QueryString() {
|
||||
this = any(QueryRunner qr).getAQueryArgument() or
|
||||
this = any(QueryBuilderCall qb).getAQueryArgument()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-queries
|
||||
version: 0.8.0-dev
|
||||
version: 0.8.4-dev
|
||||
groups:
|
||||
- javascript
|
||||
- queries
|
||||
|
||||
220
javascript/ql/test/experimental/TypeOrm/test.ts
Normal file
220
javascript/ql/test/experimental/TypeOrm/test.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import {
|
||||
BaseEntity, Brackets, DataSource, JoinColumn, NotBrackets
|
||||
, OneToOne, Entity, PrimaryGeneratedColumn, Column, SelectQueryBuilder, InsertQueryBuilder
|
||||
} from "typeorm";
|
||||
|
||||
@Entity()
|
||||
export class UserActiveRecord extends BaseEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
@Column()
|
||||
firstName: string
|
||||
@Column()
|
||||
lastName: string
|
||||
@Column()
|
||||
age: number
|
||||
|
||||
static findByName(firstName: string, lastName: string) {
|
||||
return this.createQueryBuilder("user")
|
||||
.where("user.firstName = " + firstName) // test: SQLInjectionPoint
|
||||
.andWhere("user.lastName = " + lastName) // test: SQLInjectionPoint
|
||||
.getMany()
|
||||
}
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class Profile {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
@Column()
|
||||
gender: string
|
||||
@Column()
|
||||
photo: string
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
@Column()
|
||||
name: string
|
||||
@OneToOne(() => Profile)
|
||||
@JoinColumn()
|
||||
profile: Profile
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class User2 {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
@Column()
|
||||
firstName: string
|
||||
@Column()
|
||||
lastName: string
|
||||
@Column()
|
||||
age: number
|
||||
|
||||
}
|
||||
|
||||
export const AppDataSource = new DataSource({
|
||||
type: "sqlite",
|
||||
database: "database.sqlite",
|
||||
synchronize: true,
|
||||
logging: false,
|
||||
entities: [User, User2, Profile, UserActiveRecord],
|
||||
migrations: [],
|
||||
subscribers: [],
|
||||
})
|
||||
|
||||
function makePaginationQuery<T>(q: SelectQueryBuilder<T>): SelectQueryBuilder<T> {
|
||||
return q;
|
||||
}
|
||||
|
||||
AppDataSource.initialize().then(async () => {
|
||||
const BadInput = "A user controllable Remote Source like `' 1=1 --` "
|
||||
|
||||
// Active record
|
||||
await UserActiveRecord.findByName(BadInput, "Saw")
|
||||
|
||||
// data mapper
|
||||
const selectQueryBuilder = makePaginationQuery<User>(AppDataSource
|
||||
.createQueryBuilder(User, "User").select());
|
||||
selectQueryBuilder.where(BadInput).getMany().then(result => { // test: SQLInjectionPoint
|
||||
console.log(result)
|
||||
});
|
||||
|
||||
const selectQueryBuilder2 = makePaginationQuery<User>(AppDataSource
|
||||
.createQueryBuilder(User, "User"));
|
||||
selectQueryBuilder2.where(BadInput).getMany().then(result => { // test: SQLInjectionPoint
|
||||
console.log(result)
|
||||
});
|
||||
|
||||
const insertQueryBuilder: InsertQueryBuilder<User2> = AppDataSource
|
||||
.createQueryBuilder(User2, "User2").insert();
|
||||
insertQueryBuilder.into(User2)
|
||||
.values({
|
||||
firstName: "Timber",
|
||||
lastName: () => BadInput, // test: SQLInjectionPoint
|
||||
age: 33,
|
||||
}).execute().then(result => {
|
||||
console.log(result)
|
||||
|
||||
|
||||
})
|
||||
|
||||
AppDataSource
|
||||
.createQueryBuilder(User2, "User")
|
||||
.insert()
|
||||
.into(User2)
|
||||
.values({
|
||||
firstName: "Timber",
|
||||
lastName: () => BadInput, // test: SQLInjectionPoint
|
||||
age: 33,
|
||||
})
|
||||
.orUpdate(
|
||||
[BadInput, BadInput], // test: SQLInjectionPoint
|
||||
[BadInput], // test: SQLInjectionPoint
|
||||
)
|
||||
.getQueryAndParameters()
|
||||
|
||||
await AppDataSource.getRepository(User2).createQueryBuilder("user2")
|
||||
.update(User2)
|
||||
.set({ firstName: () => BadInput, lastName: "Saw2", age: 12 }) // test: SQLInjectionPoint
|
||||
.where(BadInput,) // test: SQLInjectionPoint
|
||||
.execute()
|
||||
|
||||
await AppDataSource.getRepository(User2).createQueryBuilder('user2')
|
||||
.delete()
|
||||
.from(User2)
|
||||
.where(BadInput) // test: SQLInjectionPoint
|
||||
.execute()
|
||||
|
||||
|
||||
const queryRunner = AppDataSource.createQueryRunner()
|
||||
await queryRunner.query(BadInput) // test: SQLInjectionPoint
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder(User2, "User")
|
||||
.select(BadInput) // test: SQLInjectionPoint
|
||||
.where(BadInput).execute() // test: SQLInjectionPoint
|
||||
|
||||
await AppDataSource
|
||||
.createQueryBuilder(User, "User")
|
||||
.innerJoin("User.profile", "profile", BadInput, { // test: SQLInjectionPoint
|
||||
id: 2,
|
||||
}).getMany().then(res => console.log(res))
|
||||
|
||||
await AppDataSource
|
||||
.createQueryBuilder(User, "User")
|
||||
.leftJoinAndMapOne("User.profile", "profile", "profile", BadInput, { // test: SQLInjectionPoint
|
||||
id: 2,
|
||||
}).getMany().then(res => console.log(res))
|
||||
|
||||
|
||||
await AppDataSource
|
||||
.createQueryBuilder(User2, "User2")
|
||||
.where((qb) => {
|
||||
const subQuery = qb
|
||||
.subQuery()
|
||||
.select(BadInput) // test: SQLInjectionPoint
|
||||
.from(User2, "user2")
|
||||
.where(BadInput) // test: SQLInjectionPoint
|
||||
.getQuery()
|
||||
return "User2.id IN " + subQuery
|
||||
})
|
||||
.setParameter("registered", true)
|
||||
.getMany()
|
||||
|
||||
|
||||
// Using repository
|
||||
await AppDataSource.getRepository(User2).createQueryBuilder("User2").where("User2.id =:kind" + BadInput, { kind: 1 }).getMany()
|
||||
|
||||
// Using DataSource
|
||||
await AppDataSource
|
||||
.createQueryBuilder()
|
||||
.select(BadInput) // test: SQLInjectionPoint
|
||||
.from(User2, "User2")
|
||||
.where(BadInput, { id: 1 }) // test: SQLInjectionPoint
|
||||
.getMany()
|
||||
|
||||
// Using entity manager
|
||||
await AppDataSource.manager
|
||||
.createQueryBuilder(User2, "User2").where("User2.id =:kind" + BadInput, { kind: '1' }).getMany() // test: SQLInjectionPoint
|
||||
await AppDataSource
|
||||
.createQueryBuilder(User2, "User2")
|
||||
.leftJoinAndSelect("user.photos", "photo", BadInput).getMany() // test: SQLInjectionPoint
|
||||
await AppDataSource
|
||||
.createQueryBuilder(User2, "User2").groupBy("User2.id").having(BadInput).getMany() // test: SQLInjectionPoint
|
||||
// orderBy
|
||||
// it is a little bit restrictive, e.g. sqlite don't support it at all
|
||||
await AppDataSource
|
||||
.createQueryBuilder(User2, "User2").where(BadInput, { // test: SQLInjectionPoint
|
||||
firstName: "Timber",
|
||||
})
|
||||
.where(
|
||||
new Brackets((qb) => {
|
||||
qb.where(BadInput).orWhere(BadInput); // test: SQLInjectionPoint
|
||||
})
|
||||
)
|
||||
.orderBy(BadInput).orWhere(BadInput).getMany() // test: SQLInjectionPoint
|
||||
|
||||
// relation
|
||||
AppDataSource.createQueryBuilder().relation(User, "name")
|
||||
.of(User)
|
||||
.select().where(BadInput).getMany().then(results => { // test: SQLInjectionPoint
|
||||
console.log(results)
|
||||
})
|
||||
|
||||
// Brackets
|
||||
await AppDataSource.createQueryBuilder(User2, "User2")
|
||||
.where(BadInput) // test: SQLInjectionPoint
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where(BadInput).orWhere(BadInput); // test: SQLInjectionPoint
|
||||
})
|
||||
).andWhere(
|
||||
new NotBrackets((qb) => {
|
||||
qb.where(BadInput).orWhere(BadInput) // test: SQLInjectionPoint
|
||||
}),
|
||||
).getMany()
|
||||
}).catch(error => console.log(error))
|
||||
32
javascript/ql/test/experimental/TypeOrm/tests.expected
Normal file
32
javascript/ql/test/experimental/TypeOrm/tests.expected
Normal file
@@ -0,0 +1,32 @@
|
||||
passingPositiveTests
|
||||
| PASSED | SQLInjectionPoint | test.ts:19:54:19:79 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:20:55:20:80 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:82:70:82:95 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:88:70:88:95 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:97:41:97:66 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:111:41:111:66 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:115:37:115:62 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:116:27:116:52 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:122:74:122:99 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:123:29:123:54 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:129:28:129:53 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:134:41:134:66 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:138:29:138:54 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:139:38:139:63 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:143:61:143:86 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:149:80:149:105 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:159:37:159:62 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:161:36:161:61 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:175:29:175:54 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:177:39:177:64 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:182:108:182:133 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:185:74:185:99 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:187:94:187:119 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:191:65:191:90 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:196:57:196:82 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:199:58:199:83 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:204:65:204:90 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:210:28:210:53 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:213:56:213:81 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:217:56:217:81 | // test ... onPoint |
|
||||
failingPositiveTests
|
||||
34
javascript/ql/test/experimental/TypeOrm/tests.ql
Normal file
34
javascript/ql/test/experimental/TypeOrm/tests.ql
Normal file
@@ -0,0 +1,34 @@
|
||||
import javascript
|
||||
|
||||
class InlineTest extends LineComment {
|
||||
string tests;
|
||||
|
||||
InlineTest() { tests = this.getText().regexpCapture("\\s*test:(.*)", 1) }
|
||||
|
||||
string getPositiveTest() {
|
||||
result = tests.trim().splitAt(",").trim() and not result.matches("!%")
|
||||
}
|
||||
|
||||
predicate hasPositiveTest(string test) { test = this.getPositiveTest() }
|
||||
|
||||
predicate inNode(DataFlow::Node n) {
|
||||
this.getLocation().getFile() = n.getFile() and
|
||||
this.getLocation().getStartLine() = n.getStartLine()
|
||||
}
|
||||
}
|
||||
|
||||
import experimental.semmle.javascript.SQL
|
||||
|
||||
query predicate passingPositiveTests(string res, string expectation, InlineTest t) {
|
||||
res = "PASSED" and
|
||||
t.hasPositiveTest(expectation) and
|
||||
expectation = "SQLInjectionPoint" and
|
||||
exists(SQL::SqlString n | t.inNode(n))
|
||||
}
|
||||
|
||||
query predicate failingPositiveTests(string res, string expectation, InlineTest t) {
|
||||
res = "FAILED" and
|
||||
t.hasPositiveTest(expectation) and
|
||||
expectation = "SQLInjectionPoint" and
|
||||
not exists(SQL::SqlString n | t.inNode(n))
|
||||
}
|
||||
15
javascript/ql/test/experimental/TypeOrm/tsconfig.json
Normal file
15
javascript/ql/test/experimental/TypeOrm/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es5",
|
||||
"es6"
|
||||
],
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./build",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"sourceMap": true
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,89 @@
|
||||
#select
|
||||
| tst2.ts:1:13:1:21 | <number>1 |
|
||||
| tst2.ts:1:21:1:21 | 1 |
|
||||
| tst.js:1:1:1:3 | "a" |
|
||||
| tst.js:2:1:2:3 | "b" |
|
||||
| tst.js:2:1:2:9 | "b" + "c" |
|
||||
| tst.js:2:7:2:9 | "c" |
|
||||
| tst.js:3:1:3:3 | "d" |
|
||||
| tst.js:3:1:3:9 | "d" + "e" |
|
||||
| tst.js:3:1:3:15 | "d" + "e" + "f" |
|
||||
| tst.js:3:7:3:9 | "e" |
|
||||
| tst.js:3:13:3:15 | "f" |
|
||||
| tst.js:4:1:4:3 | `g` |
|
||||
| tst.js:4:1:4:9 | `g` + `h` |
|
||||
| tst.js:4:2:4:2 | g |
|
||||
| tst.js:4:7:4:9 | `h` |
|
||||
| tst.js:4:8:4:8 | h |
|
||||
| tst.js:6:1:6:1 | 1 |
|
||||
| tst.js:8:1:8:5 | false |
|
||||
| tst.js:9:1:9:4 | true |
|
||||
| tst.js:11:1:11:2 | -1 |
|
||||
| tst.js:11:2:11:2 | 1 |
|
||||
| tst.js:12:1:12:2 | !0 |
|
||||
| tst.js:12:2:12:2 | 0 |
|
||||
| tst.js:14:1:14:4 | null |
|
||||
| tst.js:16:1:16:9 | undefined |
|
||||
| tst.js:24:2:24:9 | `${"x"}` |
|
||||
| tst.js:24:5:24:7 | "x" |
|
||||
| tst.js:26:1:26:3 | !!0 |
|
||||
| tst.js:26:2:26:3 | !0 |
|
||||
| tst.js:26:3:26:3 | 0 |
|
||||
| tst.js:27:1:27:4 | !!`` |
|
||||
| tst.js:27:2:27:4 | !`` |
|
||||
| tst.js:27:3:27:4 | `` |
|
||||
| tst.js:29:1:29:6 | void 0 |
|
||||
| tst.js:29:6:29:6 | 0 |
|
||||
| tst.js:30:1:30:8 | void f() |
|
||||
| tst.js:32:1:32:3 | NaN |
|
||||
| tst.js:33:1:33:8 | Infinity |
|
||||
| tst.js:35:1:35:1 | 1 |
|
||||
| tst.js:35:1:35:5 | 1 + 2 |
|
||||
| tst.js:35:1:35:9 | 1 + 2 + 3 |
|
||||
| tst.js:35:5:35:5 | 2 |
|
||||
| tst.js:35:9:35:9 | 3 |
|
||||
| tst.js:37:1:37:3 | (1) |
|
||||
| tst.js:37:2:37:2 | 1 |
|
||||
| tst.js:39:1:39:4 | f, 1 |
|
||||
| tst.js:39:4:39:4 | 1 |
|
||||
| tst.js:40:1:40:1 | 1 |
|
||||
| tst.js:42:1:42:1 | 1 |
|
||||
| tst.js:42:1:42:7 | 1? 2: 3 |
|
||||
| tst.js:42:4:42:4 | 2 |
|
||||
| tst.js:42:7:42:7 | 3 |
|
||||
| tst.js:43:4:43:4 | 2 |
|
||||
| tst.js:43:7:43:7 | 3 |
|
||||
| tst.js:44:1:44:1 | 1 |
|
||||
| tst.js:44:7:44:7 | 3 |
|
||||
| tst.js:45:1:45:1 | 1 |
|
||||
| tst.js:45:4:45:4 | 2 |
|
||||
| tst.js:47:1:47:5 | x = 1 |
|
||||
| tst.js:47:5:47:5 | 1 |
|
||||
| tst.js:48:1:48:7 | x.p = 1 |
|
||||
| tst.js:48:7:48:7 | 1 |
|
||||
| tst.js:49:6:49:6 | 1 |
|
||||
| tst.js:52:1:52:9 | x = 1_000 |
|
||||
| tst.js:52:5:52:9 | 1_000 |
|
||||
| tst.js:53:1:53:13 | x = 1_000_123 |
|
||||
| tst.js:53:5:53:13 | 1_000_123 |
|
||||
| tst.js:54:1:54:17 | x = 1_000_000_000 |
|
||||
| tst.js:54:5:54:17 | 1_000_000_000 |
|
||||
| tst.js:55:1:55:18 | x = 101_475_938.38 |
|
||||
| tst.js:55:5:55:18 | 101_475_938.38 |
|
||||
| tst.js:56:1:56:10 | x = 123_00 |
|
||||
| tst.js:56:5:56:10 | 123_00 |
|
||||
| tst.js:57:1:57:10 | x = 12_300 |
|
||||
| tst.js:57:5:57:10 | 12_300 |
|
||||
| tst.js:58:1:58:12 | x = 12345_00 |
|
||||
| tst.js:58:5:58:12 | 12345_00 |
|
||||
| tst.js:59:1:59:12 | x = 123_4500 |
|
||||
| tst.js:59:5:59:12 | 123_4500 |
|
||||
| tst.js:60:1:60:13 | x = 1_234_500 |
|
||||
| tst.js:60:5:60:13 | 1_234_500 |
|
||||
| tst.js:61:1:61:10 | x = 1e1_00 |
|
||||
| tst.js:61:5:61:10 | 1e1_00 |
|
||||
| tst.js:62:1:62:14 | x = 0xaa_bb_cc |
|
||||
| tst.js:62:5:62:14 | 0xaa_bb_cc |
|
||||
getIntValue
|
||||
| tst2.ts:1:21:1:21 | 1 | 1 |
|
||||
| tst.js:6:1:6:1 | 1 | 1 |
|
||||
@@ -117,89 +203,3 @@ getLiteralValue
|
||||
| tst.js:60:5:60:13 | 1_234_500 | 1234500 |
|
||||
| tst.js:61:5:61:10 | 1e1_00 | 1.0E100 |
|
||||
| tst.js:62:5:62:14 | 0xaa_bb_cc | 11189196 |
|
||||
#select
|
||||
| tst2.ts:1:13:1:21 | <number>1 |
|
||||
| tst2.ts:1:21:1:21 | 1 |
|
||||
| tst.js:1:1:1:3 | "a" |
|
||||
| tst.js:2:1:2:3 | "b" |
|
||||
| tst.js:2:1:2:9 | "b" + "c" |
|
||||
| tst.js:2:7:2:9 | "c" |
|
||||
| tst.js:3:1:3:3 | "d" |
|
||||
| tst.js:3:1:3:9 | "d" + "e" |
|
||||
| tst.js:3:1:3:15 | "d" + "e" + "f" |
|
||||
| tst.js:3:7:3:9 | "e" |
|
||||
| tst.js:3:13:3:15 | "f" |
|
||||
| tst.js:4:1:4:3 | `g` |
|
||||
| tst.js:4:1:4:9 | `g` + `h` |
|
||||
| tst.js:4:2:4:2 | g |
|
||||
| tst.js:4:7:4:9 | `h` |
|
||||
| tst.js:4:8:4:8 | h |
|
||||
| tst.js:6:1:6:1 | 1 |
|
||||
| tst.js:8:1:8:5 | false |
|
||||
| tst.js:9:1:9:4 | true |
|
||||
| tst.js:11:1:11:2 | -1 |
|
||||
| tst.js:11:2:11:2 | 1 |
|
||||
| tst.js:12:1:12:2 | !0 |
|
||||
| tst.js:12:2:12:2 | 0 |
|
||||
| tst.js:14:1:14:4 | null |
|
||||
| tst.js:16:1:16:9 | undefined |
|
||||
| tst.js:24:2:24:9 | `${"x"}` |
|
||||
| tst.js:24:5:24:7 | "x" |
|
||||
| tst.js:26:1:26:3 | !!0 |
|
||||
| tst.js:26:2:26:3 | !0 |
|
||||
| tst.js:26:3:26:3 | 0 |
|
||||
| tst.js:27:1:27:4 | !!`` |
|
||||
| tst.js:27:2:27:4 | !`` |
|
||||
| tst.js:27:3:27:4 | `` |
|
||||
| tst.js:29:1:29:6 | void 0 |
|
||||
| tst.js:29:6:29:6 | 0 |
|
||||
| tst.js:30:1:30:8 | void f() |
|
||||
| tst.js:32:1:32:3 | NaN |
|
||||
| tst.js:33:1:33:8 | Infinity |
|
||||
| tst.js:35:1:35:1 | 1 |
|
||||
| tst.js:35:1:35:5 | 1 + 2 |
|
||||
| tst.js:35:1:35:9 | 1 + 2 + 3 |
|
||||
| tst.js:35:5:35:5 | 2 |
|
||||
| tst.js:35:9:35:9 | 3 |
|
||||
| tst.js:37:1:37:3 | (1) |
|
||||
| tst.js:37:2:37:2 | 1 |
|
||||
| tst.js:39:1:39:4 | f, 1 |
|
||||
| tst.js:39:4:39:4 | 1 |
|
||||
| tst.js:40:1:40:1 | 1 |
|
||||
| tst.js:42:1:42:1 | 1 |
|
||||
| tst.js:42:1:42:7 | 1? 2: 3 |
|
||||
| tst.js:42:4:42:4 | 2 |
|
||||
| tst.js:42:7:42:7 | 3 |
|
||||
| tst.js:43:4:43:4 | 2 |
|
||||
| tst.js:43:7:43:7 | 3 |
|
||||
| tst.js:44:1:44:1 | 1 |
|
||||
| tst.js:44:7:44:7 | 3 |
|
||||
| tst.js:45:1:45:1 | 1 |
|
||||
| tst.js:45:4:45:4 | 2 |
|
||||
| tst.js:47:1:47:5 | x = 1 |
|
||||
| tst.js:47:5:47:5 | 1 |
|
||||
| tst.js:48:1:48:7 | x.p = 1 |
|
||||
| tst.js:48:7:48:7 | 1 |
|
||||
| tst.js:49:6:49:6 | 1 |
|
||||
| tst.js:52:1:52:9 | x = 1_000 |
|
||||
| tst.js:52:5:52:9 | 1_000 |
|
||||
| tst.js:53:1:53:13 | x = 1_000_123 |
|
||||
| tst.js:53:5:53:13 | 1_000_123 |
|
||||
| tst.js:54:1:54:17 | x = 1_000_000_000 |
|
||||
| tst.js:54:5:54:17 | 1_000_000_000 |
|
||||
| tst.js:55:1:55:18 | x = 101_475_938.38 |
|
||||
| tst.js:55:5:55:18 | 101_475_938.38 |
|
||||
| tst.js:56:1:56:10 | x = 123_00 |
|
||||
| tst.js:56:5:56:10 | 123_00 |
|
||||
| tst.js:57:1:57:10 | x = 12_300 |
|
||||
| tst.js:57:5:57:10 | 12_300 |
|
||||
| tst.js:58:1:58:12 | x = 12345_00 |
|
||||
| tst.js:58:5:58:12 | 12345_00 |
|
||||
| tst.js:59:1:59:12 | x = 123_4500 |
|
||||
| tst.js:59:5:59:12 | 123_4500 |
|
||||
| tst.js:60:1:60:13 | x = 1_234_500 |
|
||||
| tst.js:60:5:60:13 | 1_234_500 |
|
||||
| tst.js:61:1:61:10 | x = 1e1_00 |
|
||||
| tst.js:61:5:61:10 | 1e1_00 |
|
||||
| tst.js:62:1:62:14 | x = 0xaa_bb_cc |
|
||||
| tst.js:62:5:62:14 | 0xaa_bb_cc |
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user