Merge remote-tracking branch 'origin/master' into HEAD

This commit is contained in:
Esben Sparre Andreasen
2020-06-23 12:21:26 +02:00
2000 changed files with 101257 additions and 59235 deletions

View File

@@ -19,6 +19,7 @@
+ semmlecode-javascript-queries/Security/CWE-094/CodeInjection.ql: /Security/CWE/CWE-094
+ semmlecode-javascript-queries/Security/CWE-094/UnsafeDynamicMethodAccess.ql: /Security/CWE/CWE-094
+ semmlecode-javascript-queries/Security/CWE-116/IncompleteSanitization.ql: /Security/CWE/CWE-116
+ semmlecode-javascript-queries/Security/CWE-116/IncompleteHtmlAttributeSanitization.ql: /Security/CWE/CWE-116
+ semmlecode-javascript-queries/Security/CWE-116/DoubleEscaping.ql: /Security/CWE/CWE-116
+ semmlecode-javascript-queries/Security/CWE-134/TaintedFormatString.ql: /Security/CWE/CWE-134
+ semmlecode-javascript-queries/Security/CWE-201/PostMessageStar.ql: /Security/CWE/CWE-201

View File

@@ -5,7 +5,7 @@ Overview
--------
This document presents an approach for running information flow analyses (such as the standard
Semmle security queries) on an application that depends on one or more npm packages. Instead of
security queries) on an application that depends on one or more npm packages. Instead of
installing the npm packages during the snapshot build and analyzing them together with application
code, we analyze each package in isolation and compute *flow summaries* that record information
about any sources, sinks and flow steps contributed by the package's API. These flow summaries
@@ -41,7 +41,7 @@ If the value of ``p`` can be controlled by an untrusted user, this would allow t
folders, which may not be desirable.
By analyzing the application code base together with the source code for the ``mkdirp`` package,
Semmle's default path injection analysis would be able to track taint through the call to ``mkdirp`` into its
the default path injection analysis would be able to track taint through the call to ``mkdirp`` into its
implementation, which ultimately uses built-in Node.js file system APIs to create the folder. Since
the path injection analysis has built-in models of these APIs it would then be able to spot and flag this
vulnerability.

View File

@@ -2,7 +2,7 @@
"name": "typescript-parser-wrapper",
"private": true,
"dependencies": {
"typescript": "3.8.2"
"typescript": "3.9.2"
},
"scripts": {
"build": "tsc --project tsconfig.json",

View File

@@ -4,31 +4,31 @@
"@types/node@12.7.11":
version "12.7.11"
resolved "node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446"
resolved node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446
ansi-regex@^2.0.0:
version "2.1.1"
resolved "ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
resolved ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df
ansi-styles@^2.2.1:
version "2.2.1"
resolved "ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
resolved ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe
ansi-styles@^3.1.0:
version "3.2.0"
resolved "ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
resolved ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88
dependencies:
color-convert "^1.9.0"
argparse@^1.0.7:
version "1.0.9"
resolved "argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
resolved argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86
dependencies:
sprintf-js "~1.0.2"
babel-code-frame@^6.22.0:
version "6.26.0"
resolved "babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
resolved babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b
dependencies:
chalk "^1.1.3"
esutils "^2.0.2"
@@ -36,22 +36,22 @@ babel-code-frame@^6.22.0:
balanced-match@^1.0.0:
version "1.0.0"
resolved "balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
resolved balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767
brace-expansion@^1.1.7:
version "1.1.8"
resolved "brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
resolved brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
builtin-modules@^1.1.1:
version "1.1.1"
resolved "builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
resolved builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f
chalk@^1.1.3:
version "1.1.3"
resolved "chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
resolved chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98
dependencies:
ansi-styles "^2.2.1"
escape-string-regexp "^1.0.2"
@@ -61,7 +61,7 @@ chalk@^1.1.3:
chalk@^2.3.0:
version "2.3.0"
resolved "chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba"
resolved chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba
dependencies:
ansi-styles "^3.1.0"
escape-string-regexp "^1.0.5"
@@ -69,45 +69,45 @@ chalk@^2.3.0:
color-convert@^1.9.0:
version "1.9.1"
resolved "color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed"
resolved color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed
dependencies:
color-name "^1.1.1"
color-name@^1.1.1:
version "1.1.3"
resolved "color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
resolved color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25
commander@^2.12.1:
version "2.13.0"
resolved "commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
resolved commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c
concat-map@0.0.1:
version "0.0.1"
resolved "concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
resolved concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b
diff@^3.2.0:
version "3.4.0"
resolved "diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c"
resolved diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
resolved escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4
esprima@^4.0.0:
version "4.0.0"
resolved "esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
resolved esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804
esutils@^2.0.2:
version "2.0.2"
resolved "esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
resolved esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b
fs.realpath@^1.0.0:
version "1.0.0"
resolved "fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
resolved fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f
glob@^7.1.1:
version "7.1.2"
resolved "glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
resolved glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
@@ -118,93 +118,93 @@ glob@^7.1.1:
has-ansi@^2.0.0:
version "2.0.0"
resolved "has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
resolved has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91
dependencies:
ansi-regex "^2.0.0"
has-flag@^2.0.0:
version "2.0.0"
resolved "has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
resolved has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51
inflight@^1.0.4:
version "1.0.6"
resolved "inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
resolved inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2:
version "2.0.3"
resolved "inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
resolved inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de
js-tokens@^3.0.2:
version "3.0.2"
resolved "js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
resolved js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b
js-yaml@^3.7.0:
version "3.10.0"
resolved "js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
resolved js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
minimatch@^3.0.4:
version "3.0.4"
resolved "minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
resolved minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083
dependencies:
brace-expansion "^1.1.7"
once@^1.3.0:
version "1.4.0"
resolved "once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
resolved once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1
dependencies:
wrappy "1"
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
resolved path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f
path-parse@^1.0.5:
version "1.0.5"
resolved "path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
resolved path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1
resolve@^1.3.2:
version "1.5.0"
resolved "resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
resolved resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36
dependencies:
path-parse "^1.0.5"
semver@^5.3.0:
version "5.5.0"
resolved "semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
resolved semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab
sprintf-js@~1.0.2:
version "1.0.3"
resolved "sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
resolved sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c
strip-ansi@^3.0.0:
version "3.0.1"
resolved "strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
resolved strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf
dependencies:
ansi-regex "^2.0.0"
supports-color@^2.0.0:
version "2.0.0"
resolved "supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
resolved supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7
supports-color@^4.0.0:
version "4.5.0"
resolved "supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b"
resolved supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b
dependencies:
has-flag "^2.0.0"
tslib@^1.8.0, tslib@^1.8.1:
version "1.9.0"
resolved "tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8"
resolved tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8
tslint@^5.9.1:
version "5.9.1"
resolved "tslint-5.9.1.tgz#1255f87a3ff57eb0b0e1f0e610a8b4748046c9ae"
resolved tslint-5.9.1.tgz#1255f87a3ff57eb0b0e1f0e610a8b4748046c9ae
dependencies:
babel-code-frame "^6.22.0"
builtin-modules "^1.1.1"
@@ -221,14 +221,14 @@ tslint@^5.9.1:
tsutils@^2.12.1:
version "2.19.1"
resolved "tsutils-2.19.1.tgz#76d7ebdea9d7a7bf4a05f50ead3701b0168708d7"
resolved tsutils-2.19.1.tgz#76d7ebdea9d7a7bf4a05f50ead3701b0168708d7
dependencies:
tslib "^1.8.1"
typescript@3.8.2:
version "3.8.2"
resolved typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a
typescript@3.9.2:
version "3.9.2"
resolved typescript-3.9.2.tgz#64e9c8e9be6ea583c54607677dd4680a1cf35db9
wrappy@1:
version "1.0.2"
resolved "wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
resolved wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f

View File

@@ -77,8 +77,8 @@ import com.semmle.util.trap.TrapWriter;
* <li><code>LGTM_INDEX_EXCLUDE</code>: a newline-separated list of paths to exclude
* <li><code>LGTM_REPOSITORY_FOLDERS_CSV</code>: the path of a CSV file containing file
* classifications
* <li><code>LGTM_INDEX_FILTERS</code>: a newline-separated list of {@link ProjectLayout}-style
* patterns that can be used to refine the list of files to include and exclude
* <li><code>LGTM_INDEX_FILTERS</code>: a newline-separated list of strings of form "include:PATTERN"
* or "exclude:PATTERN" that can be used to refine the list of files to include and exclude.
* <li><code>LGTM_INDEX_TYPESCRIPT</code>: whether to extract TypeScript
* <li><code>LGTM_INDEX_FILETYPES</code>: a newline-separated list of ".extension:filetype" pairs
* specifying which {@link FileType} to use for the given extension; the additional file type

View File

@@ -146,7 +146,7 @@ public class ExtractionMetrics {
public void stopPhase(
ExtractionPhase
event /* technically not needed, but useful for documentation and sanity checking */) {
event /* technically not needed, but useful for documentation and consistency checking */) {
if (stack.isEmpty()) {
failTimings(
String.format(

View File

@@ -1588,7 +1588,16 @@ public class TypeScriptASTConverter {
}
private Node convertLiteralType(JsonObject node, SourceLocation loc) throws ParseError {
return convertChild(node, "literal");
Node literal = convertChild(node, "literal");
// Convert a negated literal to a negative number
if (literal instanceof UnaryExpression) {
UnaryExpression unary = (UnaryExpression) literal;
if (unary.getOperator().equals("-") && unary.getArgument() instanceof Literal) {
Literal arg = (Literal) unary.getArgument();
literal = new Literal(loc, arg.getTokenType(), "-" + arg.getValue());
}
}
return literal;
}
private Node convertMappedType(JsonObject node, SourceLocation loc) throws ParseError {

View File

@@ -86,6 +86,12 @@ public class TypeScriptParser {
*/
public static final String TYPESCRIPT_TIMEOUT_VAR = "SEMMLE_TYPESCRIPT_TIMEOUT";
/**
* An environment variable that can be set to specify a number of retries when verifying
* the TypeScript installation. Default is 3.
*/
public static final String TYPESCRIPT_RETRIES_VAR = "SEMMLE_TYPESCRIPT_RETRIES";
/**
* An environment variable (without the <tt>SEMMLE_</tt> or <tt>LGTM_</tt> prefix), that can be
* set to indicate the maximum heap space usable by the Node.js process, in addition to its
@@ -179,9 +185,6 @@ public class TypeScriptParser {
public String verifyNodeInstallation() {
if (nodeJsVersionString != null) return nodeJsVersionString;
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream();
// Determine where to find the Node.js runtime.
String explicitNodeJsRuntime = Env.systemEnv().get(TYPESCRIPT_NODE_RUNTIME_VAR);
if (explicitNodeJsRuntime != null) {
@@ -198,12 +201,41 @@ public class TypeScriptParser {
nodeJsRuntimeExtraArgs = Arrays.asList(extraArgs.split("\\s+"));
}
// Run 'node --version' with a timeout, and retry a few times if it times out.
// If the Java process is suspended we may get a spurious timeout, and we want to
// support long suspensions in cloud environments. Instead of setting a huge timeout,
// retrying guarantees we can survive arbitrary suspensions as long as they don't happen
// too many times in rapid succession.
int timeout = Env.systemEnv().getInt(TYPESCRIPT_TIMEOUT_VAR, 10000);
int numRetries = Env.systemEnv().getInt(TYPESCRIPT_RETRIES_VAR, 3);
for (int i = 0; i < numRetries - 1; ++i) {
try {
return startNodeAndGetVersion(timeout);
} catch (InterruptedError e) {
Exceptions.ignore(e, "We will retry the call that caused this exception.");
System.err.println("Starting Node.js seems to take a long time. Retrying.");
}
}
try {
return startNodeAndGetVersion(timeout);
} catch (InterruptedError e) {
Exceptions.ignore(e, "Exception details are not important.");
throw new CatastrophicError(
"Could not start Node.js (timed out after " + (timeout / 1000) + "s and " + numRetries + " attempts");
}
}
/**
* Checks that Node.js is installed and can be run and returns its version string.
*/
private String startNodeAndGetVersion(int timeout) throws InterruptedError {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream();
Builder b =
new Builder(
getNodeJsRuntimeInvocation("--version"), out, err, getParserWrapper().getParentFile());
b.expectFailure(); // We want to do our own logging in case of an error.
int timeout = Env.systemEnv().getInt(TYPESCRIPT_TIMEOUT_VAR, 10000);
try {
int r = b.execute(timeout);
String stdout = new String(out.toByteArray());
@@ -213,10 +245,6 @@ public class TypeScriptParser {
"Could not start Node.js. It is required for TypeScript extraction.\n" + stderr);
}
return nodeJsVersionString = stdout;
} catch (InterruptedError e) {
Exceptions.ignore(e, "Exception details are not important.");
throw new CatastrophicError(
"Could not start Node.js (timed out after " + (timeout / 1000) + "s).");
} catch (ResourceError e) {
// In case 'node' is not found, the process builder converts the IOException
// into a ResourceError.

View File

@@ -3,7 +3,7 @@
* @description An AngularJS event listener that listens for a non-existent event has no effect.
* @kind problem
* @problem.severity warning
* @precision medium
* @precision low
* @id js/angular/dead-event-listener
* @tags correctness
* frameworks/angularjs

View File

@@ -3,7 +3,7 @@
* @description Unused dependencies are confusing, and should be removed.
* @kind problem
* @problem.severity recommendation
* @precision high
* @precision low
* @id js/angular/unused-dependency
* @tags maintainability
* frameworks/angularjs

View File

@@ -8,7 +8,7 @@
* @tags maintainability
* correctness
* external/cwe/cwe-758
* @precision medium
* @precision low
*/
import javascript

View File

@@ -7,7 +7,7 @@
* @tags maintainability
* correctness
* external/cwe/cwe-563
* @precision medium
* @precision low
*/
import javascript

View File

@@ -6,7 +6,7 @@
* @id js/too-many-parameters
* @tags testability
* readability
* @precision high
* @precision low
*/
import javascript

View File

@@ -108,7 +108,7 @@ predicate signaturesMatch(MethodSignature method, MethodSignature other) {
method.getBody().getThisTypeAnnotation().getType() =
other.getBody().getThisTypeAnnotation().getType()
) and
// The types are compared in matchingCallSignature. This is sanity-check that the textual representation of the type-annotations are somewhat similar.
// The types are compared in matchingCallSignature. This is a consistency check that the textual representation of the type-annotations are somewhat similar.
forall(int i | i in [0 .. -1 + method.getBody().getNumParameter()] |
getParameterTypeAnnotation(method, i) = getParameterTypeAnnotation(other, i)
) and

View File

@@ -5,7 +5,7 @@
* @problem.severity recommendation
* @id js/unused-property
* @tags maintainability
* @precision high
* @precision low
*/
import javascript

View File

@@ -6,7 +6,7 @@
* @id js/bitwise-sign-check
* @tags reliability
* correctness
* @precision medium
* @precision low
*/
import javascript

View File

@@ -11,7 +11,7 @@
* convention
* external/cwe/cwe-570
* external/cwe/cwe-571
* @precision medium
* @precision low
*/
import Clones

View File

@@ -158,5 +158,11 @@ predicate hasNoEffect(Expr e) {
// exclude block-level flow type annotations. For example: `(name: empty)`.
not e.(ParExpr).getExpression().getLastToken().getNextToken().getValue() = ":" and
// exclude the first statement of a try block
not e = any(TryStmt stmt).getBody().getStmt(0).(ExprStmt).getExpr()
not e = any(TryStmt stmt).getBody().getStmt(0).(ExprStmt).getExpr() and
// exclude expressions that are alone in a file, and file doesn't contain a function.
not exists(TopLevel top |
top = e.getParent().(ExprStmt).getParent() and
top.getNumChild() = 1 and
not exists(Function fun | fun.getEnclosingContainer() = top)
)
}

View File

@@ -162,7 +162,26 @@ abstract class NullOrUndefinedConversion extends ImplicitConversion {
class PlusConversion extends NullOrUndefinedConversion {
PlusConversion() { parent instanceof AddExpr or parent instanceof AssignAddExpr }
override string getConversionTarget() { result = "number or string" }
override string getConversionTarget() {
result = getDefiniteSiblingType()
or
not exists(getDefiniteSiblingType()) and
result = "number or string"
}
/**
* Gets the sibling of this implicit conversion.
* E.g. if this is `a` in the expression `a + b`, then the sibling is `b`.
*/
private Expr getSibling() { result = parent.getAChild() and not result = this.getEnclosingExpr() }
/**
* Gets the unique type of the sibling expression, if that type is `string` or `number`.
*/
private string getDefiniteSiblingType() {
result = unique(InferredType t | t = getSibling().flow().analyze().getAType()).getTypeofTag() and
result = ["string", "number"]
}
}
/**

View File

@@ -6,7 +6,7 @@
* @id js/misspelled-identifier
* @tags maintainability
* readability
* @precision high
* @precision low
*/
import Misspelling

View File

@@ -14,6 +14,37 @@
import Misspelling
from GlobalVarAccess gva, VarDecl lvd
where misspelledVariableName(gva, lvd)
select gva, "'" + gva + "' may be a typo for variable $@.", lvd, lvd.getName()
/**
* Gets the number of times a local variable with name `name` occurs in the program.
*/
bindingset[name]
int localAcceses(string name) {
result = count(VarAccess acc | acc.getName() = name and not acc instanceof GlobalVarAccess)
}
/**
* Gets the number of times a global variable with name `name` occurs in the program.
*/
bindingset[name]
int globalAccesses(string name) { result = count(GlobalVarAccess acc | acc.getName() = name) }
/**
* Holds if our heuristic says that the local variable `lvd` seems to be a misspelling of the global variable `gva`.
* Otherwise the global variable is likely the misspelling.
*/
predicate globalIsLikelyCorrect(GlobalVarAccess gva, VarDecl lvd) {
// If there are more occurrences of the global (by a margin of at least 2), and the local is missing one letter compared to the global.
globalAccesses(gva.getName()) >= localAcceses(lvd.getName()) + 2 and
lvd.getName().length() = gva.getName().length() - 1
or
// Or if there are many more of the global.
globalAccesses(gva.getName()) > 2 * localAcceses(lvd.getName()) + 2
}
from GlobalVarAccess gva, VarDecl lvd, string msg
where
misspelledVariableName(gva, lvd) and
if globalIsLikelyCorrect(gva, lvd)
then msg = "$@ may be a typo for '" + gva + "'."
else msg = "'" + gva + "' may be a typo for variable $@."
select gva, msg, lvd, lvd.getName()

View File

@@ -16,5 +16,7 @@ where
// ignore ":" pseudo-directive sometimes seen in dual-use shell/node.js scripts
not d.getExpr().getStringValue() = ":" and
// but exclude attribute top-levels: `<a href="javascript:'some-attribute-string'">`
not d.getParent() instanceof CodeInAttribute
not d.getParent() instanceof CodeInAttribute and
// exclude babel generated directives like "@babel/helpers - typeof".
not d.getDirectiveText().prefix(14) = "@babel/helpers"
select d, "Unknown directive: '" + truncate(d.getDirectiveText(), 20, " ... (truncated)") + "'."

View File

@@ -9,7 +9,7 @@
* @tags maintainability
* readability
* documentation
* @precision high
* @precision low
*/
import javascript

View File

@@ -8,7 +8,7 @@
* @tags maintainability
* readability
* documentation
* @precision high
* @precision low
*/
import javascript

View File

@@ -8,7 +8,7 @@
* @tags maintainability
* readability
* documentation
* @precision high
* @precision low
*/
import javascript

View File

@@ -7,7 +7,7 @@
* @tags maintainability
* readability
* language-features
* @precision high
* @precision low
*/
import javascript

View File

@@ -8,6 +8,11 @@ If the same pattern variable is bound multiple times in the same object or array
binding overwrites all of the earlier ones. This is most likely unintended and should be avoided.
</p>
<p>
In TypeScript, a common mistake is to try to write type annotations inside a pattern. This is not
possible, and the type annotation should come after the pattern.
</p>
</overview>
<recommendation>
@@ -34,6 +39,21 @@ From context, it appears that the second binding should have been for variable <
<sample src="examples/NonLinearPatternGood.js" />
<p>
This can sometimes happen in TypeScript, due to the apparant similarity between property patterns
and type annotations. In the following example, the function uses a pattern parameter with properties <code>x</code>
and <code>y</code>. These appear to have type <code>number</code>, but are in fact untyped properties both stored in a variable named <code>number</code>.
</p>
<sample src="examples/NonLinearPatternTS.ts" />
<p>
It is not possible to specify type annotations inside a pattern. The correct way is to specify the type
after the parameter:
</p>
<sample src="examples/NonLinearPatternTSGood.ts" />
</example>
<references>
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment">Destructuring assignment</a>.</li>

View File

@@ -14,12 +14,55 @@
import javascript
from BindingPattern p, string n, VarDecl v, VarDecl w
class RootDestructuringPattern extends BindingPattern {
RootDestructuringPattern() {
this instanceof DestructuringPattern and
not this = any(PropertyPattern p).getValuePattern() and
not this = any(ArrayPattern p).getAnElement()
}
/** Holds if this pattern has multiple bindings for `name`. */
predicate hasConflictingBindings(string name) {
exists(VarRef v, VarRef w |
v = getABindingVarRef() and
w = getABindingVarRef() and
name = v.getName() and
name = w.getName() and
v != w
)
}
/** Gets the first occurrence of the conflicting binding `name`. */
VarDecl getFirstClobberedVarDecl(string name) {
hasConflictingBindings(name) and
result =
min(VarDecl decl |
decl = getABindingVarRef() and decl.getName() = name
|
decl order by decl.getLocation().getStartLine(), decl.getLocation().getStartColumn()
)
}
/** Holds if variables in this pattern may resemble type annotations. */
predicate resemblesTypeAnnotation() {
hasConflictingBindings(_) and // Restrict size of predicate.
this instanceof Parameter and
this instanceof ObjectPattern and
not exists(getTypeAnnotation()) and
getFile().getFileType().isTypeScript()
}
}
from RootDestructuringPattern p, string n, VarDecl v, VarDecl w, string message
where
v = p.getABindingVarRef() and
v = p.getFirstClobberedVarDecl(n) and
w = p.getABindingVarRef() and
v.getName() = n and
w.getName() = n and
v != w and
v.getLocation().startsBefore(w.getLocation())
select w, "Repeated binding of pattern variable '" + n + "' previously bound $@.", v, "here"
if p.resemblesTypeAnnotation()
then
message =
"The pattern variable '" + n +
"' appears to be a type, but is a variable previously bound $@."
else message = "Repeated binding of pattern variable '" + n + "' previously bound $@."
select w, message, v, "here"

View File

@@ -8,7 +8,7 @@
* @tags reliability
* maintainability
* language-features
* @precision high
* @precision low
*/
import javascript

View File

@@ -6,7 +6,7 @@
* @id js/json-in-javascript-file
* @tags maintainability
* language-features
* @precision high
* @precision low
*/
import javascript

View File

@@ -0,0 +1,3 @@
function distance({x: number, y: number}) {
return Math.sqrt(x*x + y*y);
}

View File

@@ -0,0 +1,3 @@
function distance({x, y}: {x: number, y: number}) {
return Math.sqrt(x*x + y*y);
}

View File

@@ -8,7 +8,7 @@
* @tags reliability
* maintainability
* frameworks/node.js
* @precision medium
* @precision low
*/
import javascript

View File

@@ -3,7 +3,7 @@
* @description If unnecessary package dependencies are included in package.json, the
* package will become harder to install.
* @kind problem
* @problem.severity warning
* @problem.severity recommendation
* @id js/node/unused-npm-dependency
* @tags maintainability
* frameworks/node.js

View File

@@ -27,11 +27,10 @@ class DangerousScheme extends string {
/** Returns a node that refers to the scheme of `url`. */
DataFlow::SourceNode schemeOf(DataFlow::Node url) {
// url.split(":")[0]
exists(DataFlow::MethodCallNode split |
split.getMethodName() = "split" and
split.getArgument(0).getStringValue() = ":" and
result = split.getAPropertyRead("0") and
url = split.getReceiver()
exists(StringSplitCall split |
split.getSeparator() = ":" and
result = split.getASubstringRead(0) and
url = split.getBaseString()
)
or
// url.getScheme(), url.getProtocol(), getScheme(url), getProtocol(url)
@@ -62,18 +61,14 @@ DataFlow::Node schemeCheck(DataFlow::Node nd, DangerousScheme scheme) {
sw.getSubstring().mayHaveStringValue(scheme)
)
or
// check of the form `array.includes(getScheme(nd))`
exists(InclusionTest test, DataFlow::ArrayCreationNode array | test = result |
schemeOf(nd).flowsTo(test.getContainedNode()) and
array.flowsTo(test.getContainerNode()) and
array.getAnElement().mayHaveStringValue(scheme.getWithOrWithoutColon())
)
or
// check of the form `getScheme(nd) === scheme`
exists(EqualityTest test, Expr op1, Expr op2 | test.flow() = result |
test.hasOperands(op1, op2) and
schemeOf(nd).flowsToExpr(op1) and
op2.mayHaveStringValue(scheme.getWithOrWithoutColon())
exists(MembershipCandidate candidate |
result = candidate.getTest()
or
// fall back to the candidate if the test itself is implicit
not exists(candidate.getTest()) and result = candidate
|
candidate.getAMemberString() = scheme.getWithOrWithoutColon() and
schemeOf(nd).flowsTo(candidate)
)
or
// propagate through trimming, case conversion, and regexp replace

View File

@@ -0,0 +1,75 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Dynamically constructing a shell command with inputs from exported
functions may inadvertently change the meaning of the shell command.
Clients using the exported function may use inputs containing
characters that the shell interprets in a special way, for instance
quotes and spaces.
This can result in the shell command misbehaving, or even
allowing a malicious user to execute arbitrary commands on the system.
</p>
</overview>
<recommendation>
<p>
If possible, provide the dynamic arguments to the shell as an array
using a safe API such as <code>child_process.execFile</code> to avoid
interpretation by the shell.
</p>
<p>
Alternatively, if the shell command must be constructed
dynamically, then add code to ensure that special characters
do not alter the shell command unexpectedly.
</p>
</recommendation>
<example>
<p>
The following example shows a dynamically constructed shell
command that downloads a file from a remote URL.
</p>
<sample src="examples/unsafe-shell-command-construction.js" />
<p>
The shell command will, however, fail to work as intended if the
input contains spaces or other special characters interpreted in a
special way by the shell.
</p>
<p>
Even worse, a client might pass in user-controlled
data, not knowing that the input is interpreted as a shell command.
This could allow a malicious user to provide the input <code>http://example.org; cat /etc/passwd</code>
in order to execute the command <code>cat /etc/passwd</code>.
</p>
<p>
To avoid such potentially catastrophic behaviors, provide the
inputs from exported functions as an argument that does not
get interpreted by a shell:
</p>
<sample src="examples/unsafe-shell-command-construction_fixed.js" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,23 @@
/**
* @name Unsafe shell command constructed from library input
* @description Using externally controlled strings in a command line may allow a malicious
* user to change the meaning of the command.
* @kind path-problem
* @problem.severity error
* @precision high
* @id js/shell-command-constructed-from-input
* @tags correctness
* security
* external/cwe/cwe-078
* external/cwe/cwe-088
*/
import javascript
import semmle.javascript.security.dataflow.UnsafeShellCommandConstruction::UnsafeShellCommandConstruction
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
where cfg.hasFlowPath(source, sink) and sinkNode = sink.getNode()
select sinkNode.getAlertLocation(), source, sink, "$@ based on libary input is later used in $@.",
sinkNode.getAlertLocation(), sinkNode.getSinkType(), sinkNode.getCommandExecution(),
"shell command"

View File

@@ -0,0 +1,5 @@
var cp = require("child_process");
module.exports = function download(path, callback) {
cp.exec("wget " + path, callback);
}

View File

@@ -0,0 +1,5 @@
var cp = require("child_process");
module.exports = function download(path, callback) {
cp.execFile("wget", [path], callback);
}

View File

@@ -16,7 +16,8 @@ import semmle.javascript.security.dataflow.UnsafeJQueryPlugin::UnsafeJQueryPlugi
import DataFlow::PathGraph
from
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, JQueryPluginMethod plugin
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink,
JQuery::JQueryPluginMethod plugin
where
cfg.hasFlowPath(source, sink) and
source.getNode().(Source).getPlugin() = plugin and

View File

@@ -0,0 +1,70 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Extracting text from a DOM node and interpreting it as HTML can lead to a cross-site scripting vulnerability.
</p>
<p>
A webpage with this vulnerability reads text from the DOM, and afterwards adds the text as HTML to the DOM.
Using text from the DOM as HTML effectively unescapes the text, and thereby invalidates any escaping done on the text.
If an attacker is able to control the safe sanitized text, then this vulnerability can be exploited to perform a cross-site scripting attack.
</p>
</overview>
<recommendation>
<p>
To guard against cross-site scripting, consider using contextual output encoding/escaping before
writing text to the page, or one of the other solutions that are mentioned in the References section below.
</p>
</recommendation>
<example>
<p>
The following example shows a webpage using a <code>data-target</code> attribute
to select and manipulate a DOM element using the JQuery library. In the example, the
<code>data-target</code> attribute is read into the <code>target</code> variable, and the
<code>$</code> function is then supposed to use the <code>target</code> variable as a CSS
selector to determine which element should be manipulated.
</p>
<sample src="examples/XssThroughDom.js" />
<p>
However, if an attacker can control the <code>data-target</code> attribute,
then the value of <code>target</code> can be used to cause the <code>$</code> function
to execute arbitary JavaScript.
</p>
<p>
The above vulnerability can be fixed by using <code>$.find</code> instead of <code>$</code>.
The <code>$.find</code> function will only interpret <code>target</code> as a CSS selector
and never as HTML, thereby preventing an XSS attack.
</p>
<sample src="examples/XssThroughDomFixed.js" />
</example>
<references>
<li>
OWASP:
<a href="https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html">DOM based
XSS Prevention Cheat Sheet</a>.
</li>
<li>
OWASP:
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html">XSS
(Cross Site Scripting) Prevention Cheat Sheet</a>.
</li>
<li>
OWASP
<a href="https://owasp.org/www-community/attacks/DOM_Based_XSS">DOM Based XSS</a>.
</li>
<li>
OWASP
<a href="https://owasp.org/www-community/Types_of_Cross-Site_Scripting">Types of Cross-Site
Scripting</a>.
</li>
<li>
Wikipedia: <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">Cross-site scripting</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,21 @@
/**
* @name Cross-site scripting through DOM
* @description Writing user-controlled DOM to HTML can allow for
* a cross-site scripting vulnerability.
* @kind path-problem
* @problem.severity error
* @precision medium
* @id js/xss-through-dom
* @tags security
* external/cwe/cwe-079
* external/cwe/cwe-116
*/
import javascript
import semmle.javascript.security.dataflow.XssThroughDom::XssThroughDom
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.",
source.getNode(), "DOM text"

View File

@@ -0,0 +1,4 @@
$("button").click(function () {
var target = $(this).attr("data-target");
$(target).hide();
});

View File

@@ -0,0 +1,4 @@
$("button").click(function () {
var target = $(this).attr("data-target");
$.find(target).hide();
});

View File

@@ -1,18 +1,21 @@
const pg = require('pg');
const pool = new pg.Pool(config);
const app = require("express")(),
pg = require("pg"),
pool = new pg.Pool(config);
function handler(req, res) {
app.get("search", function handler(req, res) {
// BAD: the category might have SQL special characters in it
var query1 = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='"
+ req.params.category + "' ORDER BY PRICE";
var query1 =
"SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='" +
req.params.category +
"' ORDER BY PRICE";
pool.query(query1, [], function(err, results) {
// process results
});
// GOOD: use parameters
var query2 = "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY=$1"
+ " ORDER BY PRICE";
var query2 =
"SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY=$1" + " ORDER BY PRICE";
pool.query(query2, [req.params.category], function(err, results) {
// process results
// process results
});
}
});

View File

@@ -0,0 +1,90 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Sanitizing untrusted input for HTML meta-characters is an important
technique for preventing cross-site scripting attacks. Usually, this
is done by escaping <code>&lt;</code>, <code>&gt;</code>,
<code>&amp;</code> and <code>&quot;</code>. However, the context in which
the sanitized value is used decides the characters that
need to be sanitized.
</p>
<p>
As a consequence, some programs only sanitize
<code>&lt;</code> and <code>&gt;</code> since those are the most
common dangerous characters. The lack of sanitization for
<code>&quot;</code> is problematic when an incompletely sanitized
value is used as an HTML attribute in a string that
later is parsed as HTML.
</p>
</overview>
<recommendation>
<p>
Sanitize all relevant HTML meta-characters when
constructing HTML dynamically, and pay special attention to where the
sanitized value is used.
</p>
</recommendation>
<example>
<p>
The following example code writes part of an HTTP request (which is
controlled by the user) to an HTML attribute of the server response.
The user-controlled value is, however, not sanitized for
<code>&quot;</code>. This leaves the website vulnerable to cross-site
scripting since an attacker can use a string like <code>"
onclick="alert(42)</code> to inject JavaScript code into the response.
</p>
<sample src="examples/IncompleteHtmlAttributeSanitization.js" />
<p>
Sanitizing the user-controlled data for
<code>&quot;</code> helps prevent the vulnerability:
</p>
<sample src="examples/IncompleteHtmlAttributeSanitizationGood.js" />
</example>
<references>
<li>
OWASP:
<a href="https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html">DOM based
XSS Prevention Cheat Sheet</a>.
</li>
<li>
OWASP:
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html">XSS
(Cross Site Scripting) Prevention Cheat Sheet</a>.
</li>
<li>
OWASP
<a href="https://owasp.org/www-community/Types_of_Cross-Site_Scripting">Types of Cross-Site</a>.
</li>
<li>
Wikipedia: <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">Cross-site scripting</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,41 @@
/**
* @name Incomplete HTML attribute sanitization
* @description Writing incompletely sanitized values to HTML
* attribute strings can lead to a cross-site
* scripting vulnerability.
* @kind path-problem
* @problem.severity warning
* @precision high
* @id js/incomplete-html-attribute-sanitization
* @tags security
* external/cwe/cwe-079
* external/cwe/cwe-116
* external/cwe/cwe-20
*/
import javascript
import DataFlow::PathGraph
import semmle.javascript.security.dataflow.IncompleteHtmlAttributeSanitization::IncompleteHtmlAttributeSanitization
import semmle.javascript.security.IncompleteBlacklistSanitizer
/**
* Gets a pretty string of the dangerous characters for `sink`.
*/
string prettyPrintDangerousCharaters(Sink sink) {
result =
strictconcat(string s |
s = describeCharacters(sink.getADangerousCharacter())
|
s, ", " order by s
).regexpReplaceAll(",(?=[^,]+$)", " or")
}
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
// this message is slightly sub-optimal as we do not have an easy way
// to get the flow labels that reach the sink, so the message includes
// all of them in a disjunction
"Cross-site scripting vulnerability as the output of $@ may contain " +
prettyPrintDangerousCharaters(sink.getNode()) + " when it reaches this attribute definition.",
source.getNode(), "this final HTML sanitizer step"

View File

@@ -0,0 +1,99 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Sanitizing untrusted input for HTML meta-characters is an
important technique for preventing cross-site scripting attacks. But
even a sanitized input can be dangerous to use if it is modified
further before a browser treats it as HTML.
A seemingly innocent transformation that expands a
self-closing HTML tag from <code>&lt;div attr="{sanitized}"/&gt;</code>
to <code>&lt;div attr="{sanitized}"&gt;&lt;/div&gt;</code> may
in fact cause cross-site scripting vulnerabilities.
</p>
</overview>
<recommendation>
<p>
Use a well-tested sanitization library if at all
possible, and avoid modifying sanitized values further before treating
them as HTML.
</p>
</recommendation>
<example>
<p>
The following function transforms a self-closing HTML tag
to a pair of open/close tags. It does so for all non-<code>img</code>
and non-<code>area</code> tags, by using a regular expression with two
capture groups. The first capture group corresponds to the name of the
tag, and the second capture group to the content of the tag.
</p>
<sample src="examples/UnsafeHtmlExpansion.js" />
<p>
While it is generally known regular expressions are
ill-suited for parsing HTML, variants of this particular transformation
pattern have long been considered safe.
</p>
<p>
However, the function is not safe. As an example, consider
the following string:
</p>
<sample src="examples/UnsafeHtmlExpansion-original.html" />
<p>
When the above function transforms the string, it becomes
a string that results in an alert when a browser treats it as HTML.
</p>
<sample src="examples/UnsafeHtmlExpansion-transformed.html" />
</example>
<references>
<li>jQuery:
<a href="https://blog.jquery.com/2020/04/10/jquery-3-5-0-released/">Security fixes in jQuery 3.5.0</a>
</li>
<li>
OWASP:
<a href="https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html">DOM based
XSS Prevention Cheat Sheet</a>.
</li>
<li>
OWASP:
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html">XSS
(Cross Site Scripting) Prevention Cheat Sheet</a>.
</li>
<li>
OWASP
<a href="https://owasp.org/www-community/Types_of_Cross-Site_Scripting">Types of Cross-Site</a>.
</li>
<li>
Wikipedia: <a href="http://en.wikipedia.org/wiki/Cross-site_scripting">Cross-site scripting</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,58 @@
/**
* @name Unsafe expansion of self-closing HTML tag
* @description Using regular expressions to expand self-closing HTML
* tags may lead to cross-site scripting vulnerabilities.
* @kind problem
* @problem.severity warning
* @precision very-high
* @id js/unsafe-html-expansion
* @tags correctness
* security
* external/cwe/cwe-079
* external/cwe/cwe-116
*/
import javascript
/**
* A regular expression that captures the name and content of a
* self-closing HTML tag such as `<div id='foo'/>`.
*/
class SelfClosingTagRecognizer extends DataFlow::RegExpCreationNode {
SelfClosingTagRecognizer() {
exists(RegExpSequence seq, RegExpGroup name, RegExpGroup content |
// `/.../g`
RegExp::isGlobal(this.getFlags()) and
this.getRoot() = seq.getRootTerm() and
// `/<.../`
seq.getChild(0).getConstantValue() = "<" and
// `/...\/>/`
seq.getLastChild().getPredecessor().getConstantValue() = "/" and
seq.getLastChild().getConstantValue() = ">" and
// `/...((...)...).../`
seq.getAChild() = content and
content.getNumber() = 1 and
name.getNumber() = 2 and
name = content.getChild(0).(RegExpSequence).getChild(0) and
// `/...(([a-z]+)...).../` or `/...(([a-z][...]*)...).../`
exists(RegExpQuantifier quant | name.getAChild*() = quant |
quant instanceof RegExpStar or
quant instanceof RegExpPlus
) and
// `/...((...)[^>]*).../`
exists(RegExpCharacterClass lazy |
name.getSuccessor().(RegExpStar).getChild(0) = lazy and
lazy.isInverted() and
lazy.getAChild().getConstantValue() = ">"
)
)
}
}
from SelfClosingTagRecognizer regexp, StringReplaceCall replace
where
regexp.getAReference().flowsTo(replace.getArgument(0)) and
replace.getRawReplacement().mayHaveStringValue("<$1></$2>")
select replace,
"This self-closing HTML tag expansion invalidates prior sanitization as $@ may match part of an attribute value.",
regexp, "this regular expression"

View File

@@ -0,0 +1,9 @@
var app = require('express')();
app.get('/user/:id', function(req, res) {
let id = req.params.id;
id = id.replace(/<|>/g, ""); // BAD
let userHtml = `<div data-id="${id}">${getUserName(id) || "Unknown name"}</div>`;
// ...
res.send(prefix + userHtml + suffix);
});

View File

@@ -0,0 +1,9 @@
var app = require('express')();
app.get('/user/:id', function(req, res) {
let id = req.params.id;
id = id.replace(/<|>|&|"/g, ""); // GOOD
let userHtml = `<div data-id="${id}">${getUserName(id) || "Unknown name"}</div>`;
// ...
res.send(prefix + userHtml + suffix);
});

View File

@@ -0,0 +1,3 @@
<div alt="
<x" title="/>
<img src=url404 onerror=alert(1)>"/>

View File

@@ -0,0 +1,3 @@
<img alt="
<x" title="></x" >
<img src=url404 onerror=alert(1)>"/>

View File

@@ -0,0 +1,4 @@
function expandSelfClosingTags(html) {
var rxhtmlTag = /<(?!img|area)(([a-z][^\w\/>]*)[^>]*)\/>/gi;
return html.replace(rxhtmlTag, "<$1></$2>"); // BAD
}

View File

@@ -1 +1,7 @@
console.log("Unauthorized access attempt by " + user, ip);
const app = require("express")();
app.get("unauthorized", function handler(req, res) {
let user = req.query.user;
let ip = req.connection.remoteAddress;
console.log("Unauthorized access attempt by " + user, ip);
});

View File

@@ -1 +1,7 @@
console.log("Unauthorized access attempt by %s", user, ip);
const app = require("express")();
app.get("unauthorized", function handler(req, res) {
let user = req.query.user;
let ip = req.connection.remoteAddress;
console.log("Unauthorized access attempt by %s", user, ip);
});

View File

@@ -1,7 +1,9 @@
const crypto = require('crypto');
var secretText = obj.getSecretText();
const desCipher = crypto.createCipher('des', key);
let desEncrypted = cipher.write(secretText, 'utf8', 'hex'); // BAD: weak encryption
let desEncrypted = desCipher.write(secretText, 'utf8', 'hex'); // BAD: weak encryption
const aesCipher = crypto.createCipher('aes-128', key);
let aesEncrypted = cipher.update(secretText, 'utf8', 'hex'); // GOOD: strong encryption
let aesEncrypted = aesCipher.update(secretText, 'utf8', 'hex'); // GOOD: strong encryption

View File

@@ -1,11 +1,11 @@
var express = require('express')
var cookieParser = require('cookie-parser')
var passport = require('passport')
var app = require("express")(),
cookieParser = require("cookie-parser"),
passport = require("passport");
var app = express()
app.use(cookieParser());
app.use(passport.authorize({ session: true }));
app.use(cookieParser())
app.use(passport.authorize({ session: true }))
app.post('/changeEmail', ..., function (req, res) {
})
app.post("/changeEmail", function(req, res) {
let newEmail = req.cookies["newEmail"];
// ...
});

View File

@@ -1,13 +1,12 @@
var express = require('express')
var cookieParser = require('cookie-parser')
var passport = require('passport')
var csrf = require('csurf')
var app = require("express")(),
cookieParser = require("cookie-parser"),
passport = require("passport"),
csrf = require("csurf");
var app = express()
app.use(cookieParser())
app.use(passport.authorize({ session: true }))
app.use(csrf({ cookie:true }))
app.post('/changeEmail', ..., function (req, res) {
})
app.use(cookieParser());
app.use(passport.authorize({ session: true }));
app.use(csrf({ cookie: true }));
app.post("/changeEmail", function(req, res) {
let newEmail = req.cookies["newEmail"];
// ...
});

View File

@@ -21,11 +21,10 @@ import semmle.javascript.DynamicPropertyAccess
*
* We restrict this to parameter nodes to focus on "deep assignment" functions.
*/
class SplitCall extends MethodCallNode {
class SplitCall extends StringSplitCall {
SplitCall() {
getMethodName() = "split" and
getArgument(0).mayHaveStringValue(".") and
getReceiver().getALocalSource() instanceof ParameterNode
getSeparator() = "." and
getBaseString().getALocalSource() instanceof ParameterNode
}
}
@@ -450,8 +449,10 @@ class BlacklistInclusionGuard extends DataFlow::LabeledBarrierGuardNode, Inclusi
*/
class WhitelistInclusionGuard extends DataFlow::LabeledBarrierGuardNode {
WhitelistInclusionGuard() {
this instanceof TaintTracking::PositiveIndexOfSanitizer or
this instanceof TaintTracking::InclusionSanitizer
this instanceof TaintTracking::PositiveIndexOfSanitizer
or
this instanceof TaintTracking::MembershipTestSanitizer and
not this = any(MembershipCandidate::ObjectPropertyNameMembershipCandidate c).getTest() // handled with more precision in `HasOwnPropertyGuard`
}
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel lbl) {

View File

@@ -1,6 +1,7 @@
const jsyaml = require("js-yaml");
const app = require("express")(),
jsyaml = require("js-yaml");
function requestHandler(req, res) {
app.get("load", function(req, res) {
let data = jsyaml.load(req.params.data);
// ...
}
});

View File

@@ -1,6 +1,7 @@
const jsyaml = require("js-yaml");
const app = require("express")(),
jsyaml = require("js-yaml");
function requestHandler(req, res) {
app.get("load", function(req, res) {
let data = jsyaml.safeLoad(req.params.data);
// ...
}
});

View File

@@ -1,3 +1,5 @@
const app = require("express")();
app.get('/some/path', function(req, res) {
// BAD: a request parameter is incorporated without validation into a URL redirect
res.redirect(req.param("target"));

View File

@@ -1,3 +1,5 @@
const app = require("express")();
const VALID_REDIRECT = "http://cwe.mitre.org/data/definitions/601.html";
app.get('/some/path', function(req, res) {

View File

@@ -1,2 +1,7 @@
const libxml = require('libxmljs');
var doc = libxml.parseXml(xmlSrc, { noent: true });
const app = require("express")(),
libxml = require("libxmljs");
app.post("upload", (req, res) => {
let xmlSrc = req.body,
doc = libxml.parseXml(xmlSrc, { noent: true });
});

View File

@@ -1,2 +1,7 @@
const libxml = require('libxmljs');
var doc = libxml.parseXml(xmlSrc);
const app = require("express")(),
libxml = require("libxmljs");
app.post("upload", (req, res) => {
let xmlSrc = req.body,
doc = libxml.parseXml(xmlSrc);
});

View File

@@ -1,5 +1,10 @@
const expat = require('node-expat');
var parser = new expat.Parser();
parser.on('startElement', handleStart);
parser.on('text', handleText);
parser.write(xmlSrc);
const app = require("express")(),
expat = require("node-expat");
app.post("upload", (req, res) => {
let xmlSrc = req.body,
parser = new expat.Parser();
parser.on("startElement", handleStart);
parser.on("text", handleText);
parser.write(xmlSrc);
});

View File

@@ -1,5 +1,10 @@
const sax = require('sax');
var parser = sax.parser(true);
parser.onopentag = handleStart;
parser.ontext = handleText;
parser.write(xmlSrc);
const app = require("express")(),
sax = require("sax");
app.post("upload", (req, res) => {
let xmlSrc = req.body,
parser = sax.parser(true);
parser.onopentag = handleStart;
parser.ontext = handleText;
parser.write(xmlSrc);
});

View File

@@ -1,10 +1,10 @@
const pg = require('pg')
const pg = require("pg");
const client = new pg.Client({
user: 'dbuser',
host: 'database.server.com',
database: 'mydb',
password: 'secretpassword',
port: 3211,
})
client.connect()
user: "bob",
host: "database.server.com",
database: "mydb",
password: "correct-horse-battery-staple",
port: 3211
});
client.connect();

View File

@@ -1,15 +1,15 @@
var express = require('express'),
path = require('path'),
var app = express();
var app = require("express")(),
path = require("path");
app.get('/user-files', function(req, res) {
var file = req.param('file');
if (file.indexOf('..') !== -1) { // BAD
// forbid paths outside the /public directory
res.status(400).send('Bad request');
} else {
var absolute = path.resolve('/public/' + file);
console.log("Sending file: %s", absolute);
res.sendFile(absolute);
}
app.get("/user-files", function(req, res) {
var file = req.param("file");
if (file.indexOf("..") !== -1) {
// BAD
// forbid paths outside the /public directory
res.status(400).send("Bad request");
} else {
var absolute = path.resolve("/public/" + file);
console.log("Sending file: %s", absolute);
res.sendFile(absolute);
}
});

View File

@@ -1,15 +1,15 @@
var express = require('express'),
path = require('path'),
var app = express();
var app = require("express")(),
path = require("path");
app.get('/user-files', function(req, res) {
var file = req.param('file');
if (typeof path !== 'string' || path.indexOf('..') !== -1) { // GOOD
// forbid paths outside the /public directory
res.status(400).send('Bad request');
} else {
var full = path.resolve('/public/' + file);
console.log("Sending file: %s", full);
res.sendFile(full);
}
app.get("/user-files", function(req, res) {
var file = req.param("file");
if (typeof path !== 'string' || file.indexOf("..") !== -1) {
// BAD
// forbid paths outside the /public directory
res.status(400).send("Bad request");
} else {
var absolute = path.resolve("/public/" + file);
console.log("Sending file: %s", absolute);
res.sendFile(absolute);
}
});

View File

@@ -1,5 +1,5 @@
const crypto = require("crypto");
function hashPassword(password) {
var crypto = require("crypto");
var hasher = crypto.createHash('md5');
var hashed = hasher.update(password).digest("hex"); // BAD
return hashed;

View File

@@ -1,5 +1,5 @@
const bcrypt = require("bcrypt");
function hashPassword(password, salt) {
var bcrypt = require('bcrypt');
var hashed = bcrypt.hashSync(password, salt); // GOOD
return hashed;
var hashed = bcrypt.hashSync(password, salt); // GOOD
return hashed;
}

View File

@@ -2,7 +2,7 @@ import http from 'http';
import url from 'url';
var server = http.createServer(function(req, res) {
var target = url.parse(request.url, true).query.target;
var target = url.parse(req.url, true).query.target;
// BAD: `target` is controlled by the attacker
http.get('https://' + target + ".example.com/data/", res => {

View File

@@ -2,7 +2,7 @@ import http from 'http';
import url from 'url';
var server = http.createServer(function(req, res) {
var target = url.parse(request.url, true).query.target;
var target = url.parse(req.url, true).query.target;
var subdomain;
if (target === 'EU') {

View File

@@ -6,7 +6,7 @@
* @problem.severity recommendation
* @id js/single-run-loop
* @tags readability
* @precision high
* @precision low
*/
import javascript

View File

@@ -7,7 +7,7 @@
* @id js/nested-loops-with-same-variable
* @tags maintainability
* correctness
* @precision medium
* @precision low
*/
import javascript

View File

@@ -7,7 +7,7 @@
* @id js/return-outside-function
* @tags reliability
* correctness
* @precision medium
* @precision low
*/
import javascript

View File

@@ -62,6 +62,14 @@ predicate isInitialParameterUse(Expr e) {
not p.isRestParameter()
)
or
// same as above, but for captured variables
exists(SimpleParameter p, LocalVariable var |
var = p.getVariable() and
var.isCaptured() and
e = var.getAnAccess() and
not p.isRestParameter()
)
or
isInitialParameterUse(e.(LogNotExpr).getOperand())
}

View File

@@ -2,3 +2,8 @@
- qlpack: codeql-javascript
- apply: lgtm-selectors.yml
from: codeql-suite-helpers
# These are only for IDE use.
- exclude:
tags contain:
- ide-contextual-queries/local-definitions
- ide-contextual-queries/local-references

View File

@@ -0,0 +1,4 @@
- description: Security-and-quality queries for JavaScript
- qlpack: codeql-javascript
- apply: security-and-quality-selectors.yml
from: codeql-suite-helpers

View File

@@ -0,0 +1,4 @@
- description: Security-extended queries for JavaScript
- qlpack: codeql-javascript
- apply: security-extended-selectors.yml
from: codeql-suite-helpers

View File

@@ -6,169 +6,8 @@
* @id js/jump-to-definition
*/
import javascript
private import Declarations.Declarations
import definitions
/**
* Gets the kind of reference that `r` represents.
*
* References in callee position have kind `"M"` (for "method"), all
* others have kind `"V"` (for "variable").
*
* For example, in the expression `f(x)`, `f` has kind `"M"` while
* `x` has kind `"V"`.
*/
string refKind(RefExpr r) {
if exists(InvokeExpr invk | r = invk.getCallee().getUnderlyingReference())
then result = "M"
else result = "V"
}
/**
* Gets a class, function or object literal `va` may refer to.
*/
ASTNode lookupDef(VarAccess va) {
exists(AbstractValue av | av = va.analyze().getAValue() |
result = av.(AbstractClass).getClass() or
result = av.(AbstractFunction).getFunction() or
result = av.(AbstractObjectLiteral).getObjectExpr()
)
}
/**
* Holds if `va` is of kind `kind` and `def` is the unique class,
* function or object literal it refers to.
*/
predicate variableDefLookup(VarAccess va, ASTNode def, string kind) {
count(lookupDef(va)) = 1 and
def = lookupDef(va) and
kind = refKind(va)
}
/**
* Holds if variable access `va` is of kind `kind` and refers to the
* variable declaration.
*
* For example, in the statement `var x = 42, y = x;`, the initializing
* expression of `y` is a variable access `x` of kind `"V"` that refers to
* the declaration `x = 42`.
*/
predicate variableDeclLookup(VarAccess va, VarDecl decl, string kind) {
// restrict to declarations in same file to avoid accidentally picking up
// unrelated global definitions
decl = firstRefInTopLevel(va.getVariable(), Decl(), va.getTopLevel()) and
kind = refKind(va)
}
/**
* Holds if path expression `path`, which appears in a CommonJS `require`
* call or an ES 2015 import statement, imports module `target`; `kind`
* is always "I" (for "import").
*
* For example, in the statement `var a = require("./a")`, the path expression
* `"./a"` imports a module `a` in the same folder.
*/
predicate importLookup(ASTNode path, Module target, string kind) {
kind = "I" and
(
exists(Import i |
path = i.getImportedPath() and
target = i.getImportedModule()
)
or
exists(ReExportDeclaration red |
path = red.getImportedPath() and
target = red.getReExportedModule()
)
)
}
/**
* Gets a node that may write the property read by `prn`.
*/
ASTNode getAWrite(DataFlow::PropRead prn) {
exists(DataFlow::AnalyzedNode base, DefiniteAbstractValue baseVal, string propName |
base = prn.getBase() and
propName = prn.getPropertyName() and
baseVal = base.getAValue().getAPrototype*()
|
// write to a property on baseVal
exists(AnalyzedPropertyWrite apw |
result = apw.getAstNode() and
apw.writes(baseVal, propName, _)
)
or
// non-static class members aren't covered by `AnalyzedPropWrite`, so have to be handled
// separately
exists(ClassDefinition c, MemberDefinition m |
m = c.getMember(propName) and
baseVal.(AbstractInstance).getConstructor().(AbstractClass).getClass() = c and
result = m.getNameExpr()
)
)
}
/**
* Holds if `prop` is the property name expression of a property read that
* may read the property written by `write`. Furthermore, `write` must be the
* only such property write. Parameter `kind` is always bound to `"M"`
* at the moment.
*/
predicate propertyLookup(Expr prop, ASTNode write, string kind) {
exists(DataFlow::PropRead prn | prop = prn.getPropertyNameExpr() |
count(getAWrite(prn)) = 1 and
write = getAWrite(prn) and
kind = "M"
)
}
/**
* Holds if `ref` is an identifier that refers to a type declared at `decl`.
*/
predicate typeLookup(ASTNode ref, ASTNode decl, string kind) {
exists(TypeAccess typeAccess |
ref = typeAccess.getIdentifier() and
decl = typeAccess.getTypeName().getADefinition() and
kind = "T"
)
}
/**
* Holds if `ref` is the callee name of an invocation of `decl`.
*/
predicate typedInvokeLookup(ASTNode ref, ASTNode decl, string kind) {
not variableDefLookup(ref, decl, _) and
not propertyLookup(ref, decl, _) and
exists(InvokeExpr invoke, Expr callee |
callee = invoke.getCallee().getUnderlyingReference() and
(ref = callee.(Identifier) or ref = callee.(DotExpr).getPropertyNameExpr()) and
decl = invoke.getResolvedCallee() and
kind = "M"
)
}
/**
* Holds if `ref` is a JSDoc type annotation referring to a class defined at `decl`.
*/
predicate jsdocTypeLookup(JSDocNamedTypeExpr ref, ASTNode decl, string kind) {
decl = ref.getClass().getAstNode() and
kind = "T"
}
from Locatable ref, ASTNode decl, string kind
where
variableDefLookup(ref, decl, kind)
or
// prefer definitions over declarations
not variableDefLookup(ref, _, _) and variableDeclLookup(ref, decl, kind)
or
importLookup(ref, decl, kind)
or
propertyLookup(ref, decl, kind)
or
typeLookup(ref, decl, kind)
or
typedInvokeLookup(ref, decl, kind)
or
jsdocTypeLookup(ref, decl, kind)
select ref, decl, kind
from Locatable e, ASTNode def, string kind
where def = definitionOf(e, kind)
select e, def, kind

View File

@@ -0,0 +1,188 @@
/**
* Provides classes and predicates related to jump-to-definition links
* in the code viewer.
*/
import javascript
private import Declarations.Declarations
/**
* Gets the kind of reference that `r` represents.
*
* References in callee position have kind `"M"` (for "method"), all
* others have kind `"V"` (for "variable").
*
* For example, in the expression `f(x)`, `f` has kind `"M"` while
* `x` has kind `"V"`.
*/
private string refKind(RefExpr r) {
if exists(InvokeExpr invk | r = invk.getCallee().getUnderlyingReference())
then result = "M"
else result = "V"
}
/**
* Gets a class, function or object literal `va` may refer to.
*/
private ASTNode lookupDef(VarAccess va) {
exists(AbstractValue av | av = va.analyze().getAValue() |
result = av.(AbstractClass).getClass() or
result = av.(AbstractFunction).getFunction() or
result = av.(AbstractObjectLiteral).getObjectExpr()
)
}
/**
* Holds if `va` is of kind `kind` and `def` is the unique class,
* function or object literal it refers to.
*/
private predicate variableDefLookup(VarAccess va, ASTNode def, string kind) {
count(lookupDef(va)) = 1 and
def = lookupDef(va) and
kind = refKind(va)
}
/**
* Holds if variable access `va` is of kind `kind` and refers to the
* variable declaration.
*
* For example, in the statement `var x = 42, y = x;`, the initializing
* expression of `y` is a variable access `x` of kind `"V"` that refers to
* the declaration `x = 42`.
*/
private predicate variableDeclLookup(VarAccess va, VarDecl decl, string kind) {
// restrict to declarations in same file to avoid accidentally picking up
// unrelated global definitions
decl = firstRefInTopLevel(va.getVariable(), Decl(), va.getTopLevel()) and
kind = refKind(va)
}
/**
* Holds if path expression `path`, which appears in a CommonJS `require`
* call or an ES 2015 import statement, imports module `target`; `kind`
* is always "I" (for "import").
*
* For example, in the statement `var a = require("./a")`, the path expression
* `"./a"` imports a module `a` in the same folder.
*/
private predicate importLookup(ASTNode path, Module target, string kind) {
kind = "I" and
(
exists(Import i |
path = i.getImportedPath() and
target = i.getImportedModule()
)
or
exists(ReExportDeclaration red |
path = red.getImportedPath() and
target = red.getReExportedModule()
)
)
}
/**
* Gets a node that may write the property read by `prn`.
*/
private ASTNode getAWrite(DataFlow::PropRead prn) {
exists(DataFlow::AnalyzedNode base, DefiniteAbstractValue baseVal, string propName |
base = prn.getBase() and
propName = prn.getPropertyName() and
baseVal = base.getAValue().getAPrototype*()
|
// write to a property on baseVal
exists(AnalyzedPropertyWrite apw |
result = apw.getAstNode() and
apw.writes(baseVal, propName, _)
)
or
// non-static class members aren't covered by `AnalyzedPropWrite`, so have to be handled
// separately
exists(ClassDefinition c, MemberDefinition m |
m = c.getMember(propName) and
baseVal.(AbstractInstance).getConstructor().(AbstractClass).getClass() = c and
result = m.getNameExpr()
)
)
}
/**
* Holds if `prop` is the property name expression of a property read that
* may read the property written by `write`. Furthermore, `write` must be the
* only such property write. Parameter `kind` is always bound to `"M"`
* at the moment.
*/
private predicate propertyLookup(Expr prop, ASTNode write, string kind) {
exists(DataFlow::PropRead prn | prop = prn.getPropertyNameExpr() |
count(getAWrite(prn)) = 1 and
write = getAWrite(prn) and
kind = "M"
)
}
/**
* Holds if `ref` is an identifier that refers to a type declared at `decl`.
*/
private predicate typeLookup(ASTNode ref, ASTNode decl, string kind) {
exists(TypeAccess typeAccess |
ref = typeAccess.getIdentifier() and
decl = typeAccess.getTypeName().getADefinition() and
kind = "T"
)
}
/**
* Holds if `ref` is the callee name of an invocation of `decl`.
*/
private predicate typedInvokeLookup(ASTNode ref, ASTNode decl, string kind) {
not variableDefLookup(ref, decl, _) and
not propertyLookup(ref, decl, _) and
exists(InvokeExpr invoke, Expr callee |
callee = invoke.getCallee().getUnderlyingReference() and
(ref = callee.(Identifier) or ref = callee.(DotExpr).getPropertyNameExpr()) and
decl = invoke.getResolvedCallee() and
kind = "M"
)
}
/**
* Holds if `ref` is a JSDoc type annotation referring to a class defined at `decl`.
*/
private predicate jsdocTypeLookup(JSDocNamedTypeExpr ref, ASTNode decl, string kind) {
decl = ref.getClass().getAstNode() and
kind = "T"
}
/**
* Gets an element, of kind `kind`, that element `e` uses, if any.
*
* The `kind` is a string representing what kind of use it is:
* - `"M"` for function and method calls
* - `"T"` for uses of types
* - `"V"` for variable accesses
* - `"I"` for imports
*/
cached
ASTNode definitionOf(Locatable e, string kind) {
variableDefLookup(e, result, kind)
or
// prefer definitions over declarations
not variableDefLookup(e, _, _) and variableDeclLookup(e, result, kind)
or
importLookup(e, result, kind)
or
propertyLookup(e, result, kind)
or
typeLookup(e, result, kind)
or
typedInvokeLookup(e, result, kind)
or
jsdocTypeLookup(e, result, kind)
}
/**
* Returns an appropriately encoded version of a filename `name`
* passed by the VS Code extension in order to coincide with the
* output of `.getFile()` on locatable entities.
*/
cached
File getEncodedFile(string name) { result.getAbsolutePath().replaceAll(":", "_") = name }

View File

@@ -0,0 +1,40 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>If you use cross-origin communication between Window objects and do expect to receive messages from other sites, always verify the sender's identity using the origin and possibly source properties of the recevied `MessageEvent`. </p>
<p>Unexpected behaviours, like `DOM-based XSS` could occur, if the event handler for incoming data does not check the origin of the data received and handles the data in an unsafe way.</p>
</overview>
<recommendation>
<p>
Always verify the sender's identity of incoming messages.
</p>
</recommendation>
<example>
<p>In the first example, the `MessageEvent.data` is passed to the `eval` function withouth checking the origin. This means that any window can send arbitrary messages that will be executed in the window receiving the message</p>
<sample src="examples/postMessageNoOriginCheck.js" />
<p> In the second example, the `MessageEvent.origin` is verified with an unsecure check. For example, using `event.origin.indexOf('www.example.com') > -1` can be bypassed because the string `www.example.com` could appear anywhere in `event.origin` (i.e. `www.example.com.mydomain.com`)</p>
<sample src="examples/postMessageInsufficientCheck.js" />
<p> In the third example, the `MessageEvent.origin` is properly checked against a trusted origin. </p>
<sample src="examples/postMessageWithOriginCheck.js" />
</example>
<references>
<li><a href="https://cwe.mitre.org/data/definitions/20.html">CWE-20: Improper Input Validation</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage">Window.postMessage()</a></li>
<li><a href="https://portswigger.net/web-security/dom-based/web-message-manipulation">Web-message manipulation</a></li>
<li><a href="https://labs.detectify.com/2016/12/08/the-pitfalls-of-postmessage/">The pitfalls of postMessage</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,64 @@
/**
* @name Missing `MessageEvent.origin` verification in `postMessage` handlers
* @description Missing the `MessageEvent.origin` verification in `postMessage` handlers, allows any windows to send arbitrary data to the `MessageEvent` listener.
* This could lead to unexpected behaviour, especially when `MessageEvent.data` is used in an unsafe way.
* @kind problem
* @problem.severity warning
* @precision high
* @id js/missing-postmessageorigin-verification
* @tags correctness
* security
* external/cwe/cwe-20
*/
import javascript
import semmle.javascript.security.dataflow.DOM
/**
* A method call for the insecure functions used to verify the `MessageEvent.origin`.
*/
class InsufficientOriginChecks extends DataFlow::Node {
InsufficientOriginChecks() {
exists(DataFlow::Node node |
this.(StringOps::StartsWith).getSubstring() = node or
this.(StringOps::Includes).getSubstring() = node or
this.(StringOps::EndsWith).getSubstring() = node
)
}
}
/**
* A function handler for the `MessageEvent`.
*/
class PostMessageHandler extends DataFlow::FunctionNode {
PostMessageHandler() { this.getFunction() instanceof PostMessageEventHandler }
}
/**
* The `MessageEvent` parameter received by the handler
*/
class PostMessageEvent extends DataFlow::SourceNode {
PostMessageEvent() { exists(PostMessageHandler handler | this = handler.getParameter(0)) }
/**
* Holds if an access on `MessageEvent.origin` is in an `EqualityTest` and there is no call of an insufficient verification method on `MessageEvent.origin`
*/
predicate hasOriginChecked() {
exists(EqualityTest test |
this.getAPropertyRead(["origin", "source"]).flowsToExpr(test.getAnOperand())
)
}
/**
* Holds if there is an insufficient method call (i.e indexOf) used to verify `MessageEvent.origin`
*/
predicate hasOriginInsufficientlyChecked() {
exists(InsufficientOriginChecks insufficientChecks |
this.getAPropertyRead("origin").getAMethodCall*() = insufficientChecks
)
}
}
from PostMessageEvent event
where not event.hasOriginChecked() or event.hasOriginInsufficientlyChecked()
select event, "Missing or unsafe origin verification."

View File

@@ -0,0 +1,14 @@
function postMessageHandler(event) {
let origin = event.origin.toLowerCase();
let host = window.location.host;
// BAD
if (origin.indexOf(host) === -1)
return;
eval(event.data);
}
window.addEventListener('message', postMessageHandler, false);

View File

@@ -0,0 +1,9 @@
function postMessageHandler(event) {
let origin = event.origin.toLowerCase();
console.log(origin)
// BAD: the origin property is not checked
eval(event.data);
}
window.addEventListener('message', postMessageHandler, false);

View File

@@ -0,0 +1,9 @@
function postMessageHandler(event) {
console.log(event.origin)
// GOOD: the origin property is checked
if (event.origin === 'www.example.com') {
// do something
}
}
window.addEventListener('message', postMessageHandler, false);

View File

@@ -0,0 +1,56 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Server-Side Template Injection vulnerabilities occur when user input is embedded
in a template in an unsafe manner allowing attackers to access the template context and
run arbitrary code on the application server.
</p>
</overview>
<recommendation>
<p>
Avoid including user input in any expression or template which may be dynamically rendered.
If user input must be included, use context-specific escaping before including it or run
the rendering engine with sandbox options.
</p>
</recommendation>
<example>
<p>
The following example shows a page being rendered with user input allowing attackers to access the
template context and run arbitrary code on the application server.
The Pug template engine (and other template engines) provides an interpolation feature - insertion of variable values into a string of some kind.
For example, <code>Hello #{user.username}!</code>, could be used for printing a username from a scoped variable user,
but the <code>user.username</code> expression will be executed as JavaScript.
Unsafe injection of user input in a template therefore allows an attacker to inject arbitrary JavaScript code.
For example, a payload of <code>#{global.process.exit(1)}</code> will cause the below server to crash.
</p>
<sample src="examples/ServerSideTemplateInjection.js" />
</example>
<example>
<p>
The example below provides an example of how to use a template engine without any risk of Server-Side Template Injection.
Instead of concatenating user input onto the template, the template uses a placeholder and safely inserts
the user input.
</p>
<sample src="examples/ServerSideTemplateInjectionSafe.js" />
</example>
<references>
<li>
OWASP:
<a href="https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/18-Testing_for_Server_Side_Template_Injection">Server Side Template Injection</a>.
</li>
<li>
PortSwigger Research Blog:
<a href="https://portswigger.net/research/server-side-template-injection">Server-Side Template Injection</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,64 @@
/**
* @name Server Side Template Injection
* @description Rendering templates with unsanitized user input allows a malicious user arbitrary
* code execution.
* @kind path-problem
* @problem.severity error
* @precision high
* @id js/server-side-template-injection
* @tags security
* external/cwe/cwe-094
*/
import javascript
import DataFlow
import DataFlow::PathGraph
class ServerSideTemplateInjectionConfiguration extends TaintTracking::Configuration {
ServerSideTemplateInjectionConfiguration() { this = "ServerSideTemplateInjectionConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof ServerSideTemplateInjectionSink }
}
abstract class ServerSideTemplateInjectionSink extends DataFlow::Node { }
class SSTIPugSink extends ServerSideTemplateInjectionSink {
SSTIPugSink() {
exists(CallNode compile, ModuleImportNode renderImport |
renderImport = moduleImport(["pug", "jade"]) and
(
compile = renderImport.getAMemberCall("compile")
or
compile = renderImport.getAMemberCall("render")
) and
this = compile.getArgument(0)
)
}
}
class SSTIDotSink extends ServerSideTemplateInjectionSink {
SSTIDotSink() {
exists(CallNode compile |
compile = moduleImport("dot").getAMemberCall("template") and
this = compile.getArgument(0)
)
}
}
class SSTIEjsSink extends ServerSideTemplateInjectionSink {
SSTIEjsSink() { this = moduleImport("ejs").getAMemberCall("render").getArgument(0) }
}
class SSTINunjucksSink extends ServerSideTemplateInjectionSink {
SSTINunjucksSink() {
this = moduleImport("nunjucks").getAMemberCall("renderString").getArgument(0)
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, ServerSideTemplateInjectionConfiguration c
where c.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"$@ flows to here and unsafely used as part of rendered template", source.getNode(),
"User-provided value"

View File

@@ -0,0 +1,35 @@
const express = require('express')
var bodyParser = require('body-parser');
const app = express()
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
//Dependent of Templating engine
var jade = require('pug');
const port = 5061
function getHTML(input) {
var template = `
doctype
html
head
title= 'Hello world'
body
form(action='/' method='post')
label(for='name') Name:
input#name.form-control(type='text', placeholder='' name='name')
button.btn.btn-primary(type='submit') Submit
p Hello `+ input
var fn = jade.compile(template);
var html = fn();
console.log(html);
return html;
}
app.post('/', (request, response) => {
var input = request.param('name', "")
var html = getHTML(input)
response.send(html);
})
app.listen(port, () => { console.log(`server is listening on ${port}`) })

View File

@@ -0,0 +1,34 @@
const express = require('express')
var bodyParser = require('body-parser');
const app = express()
app.use(bodyParser.urlencoded({ extended: true }));
//Dependent of Templating engine
var jade = require('pug');
const port = 5061
function getHTML(input) {
var template = `
doctype
html
head
title= 'Hello world'
body
form(action='/' method='post')
label(for='name') Name:
input#name.form-control(type='text', placeholder='' name='name')
button.btn.btn-primary(type='submit') Submit
p Hello #{username}`
var fn = jade.compile(template);
var html = fn({username: input});
console.log(html);
return html;
}
app.post('/', (request, response) => {
var input = request.param('name', "")
var html = getHTML(input)
response.send(html);
})
app.listen(port, () => { console.log(`server is listening on ${port}`) })

View File

@@ -0,0 +1,325 @@
/**
* Provides classes and predicates for discovering points of interest
* in an unknown code base.
*
* To use this module, subclass the
* `ActivePoI` class, override *one* of its `is` predicates, and use
* `alertQuery` as a `@kind problem` query . This will present
* the desired points of interest as alerts that are easily browsable
* in a codeql IDE. By itself, this is no different from an ordinary
* query, but the strength of this module lies in its extensibility
* and standard library:
*
* - points of interest can be added, removed and mixed seamlessly
* - this module comes with a collection of standard points of interest (see `StandardPoIs`)
*
* A global configuration for the points of interest (see
* `PoIConfiguration`) can be used to easily manage multiple points of
* interests, and to restrict the points of interest to specific
* corners of the code base.
*
* Below is an example use of this module that will produce an alert
* for each route handler and route handler setup in a file named
* "server-core.js". The route setup alerts will contain a link to its
* associated route handler.
*
* ```
* /**
* * @kind problem
* *\/
*
* import PoI
*
* class Configuration extends PoIConfiguration {
* Configuration() { this = "Configuration" }
*
* override predicate shown(DataFlow::Node n) { n.getFile().getBaseName() = "server-core.js" }
* }
*
* class RouteHandlerPoI extends ActivePoI {
* RouteHandlerPoI() { this = "RouteHandlerPoI" }
* override predicate is(DataFlow::Node l0) { l0 instanceof Express::RouteHandler }
* }
*
* class RouteSetupAndRouteHandlerPoI extends ActivePoI {
* RouteSetupAndRouteHandlerPoI() { this = "RouteSetupAndRouteHandlerPoI" }
*
* override predicate is(DataFlow::Node l0, DataFlow::Node l1, string t1) {
* l0.asExpr().(Express::RouteSetup).getARouteHandler() = l1 and t1 = "routehandler"
* }
* }
*
* query predicate problems = alertQuery/6;
* ```
*/
import javascript
private import DataFlow
private import filters.ClassifyFiles
private import semmle.javascript.RestrictedLocations
/**
* Provides often used points of interest.
*
* Note that these points of interest should not extend
* `ActivePoI`, and that they can be enabled on
* demand like this:
*
* ```
* class MyPoI extends ServerRelatedPoI, ActivePoI {}
* ```
*/
private module StandardPoIs {
/**
* An unpromoted route setup candidate.
*/
class UnpromotedRouteSetupPoI extends PoI {
UnpromotedRouteSetupPoI() { this = "UnpromotedRouteSetupPoI" }
override predicate is(Node l0) {
l0 instanceof HTTP::RouteSetupCandidate and not l0.asExpr() instanceof HTTP::RouteSetup
}
}
/**
* An unpromoted route handler candidate.
*/
class UnpromotedRouteHandlerPoI extends PoI {
UnpromotedRouteHandlerPoI() { this = "UnpromotedRouteHandlerPoI" }
override predicate is(Node l0) {
l0 instanceof HTTP::RouteHandlerCandidate and not l0 instanceof HTTP::RouteHandler
}
}
/**
* An unpromoted route handler candidate, with explanatory data flow information.
*/
class UnpromotedRouteHandlerWithFlowPoI extends PoI {
UnpromotedRouteHandlerWithFlowPoI() { this = "UnpromotedRouteHandlerWithFlowPoI" }
private DataFlow::SourceNode track(HTTP::RouteHandlerCandidate cand, DataFlow::TypeTracker t) {
t.start() and
result = cand
or
exists(DataFlow::TypeTracker t2 | result = track(cand, t2).track(t2, t))
}
override predicate is(Node l0, Node l1, string t1) {
l0 instanceof HTTP::RouteHandlerCandidate and
not l0 instanceof HTTP::RouteHandler and
l1 = track(l0, TypeTracker::end()) and
(if l1 = l0 then t1 = "ends here" else t1 = "starts/ends here")
}
}
/**
* A callee that is unknown.
*/
class UnknownCalleePoI extends PoI {
UnknownCalleePoI() { this = "UnknownCalleePoI" }
override predicate is(Node l0) {
exists(InvokeNode invk | l0 = invk.getCalleeNode() and not exists(invk.getACallee()))
}
}
/**
* A source of remote flow.
*/
class RemoteFlowSourcePoI extends PoI {
RemoteFlowSourcePoI() { this = "RemoteFlowSourcePoI" }
override predicate is(Node l0) { l0 instanceof RemoteFlowSource }
}
/**
* A "source" for any active configuration.
*/
class SourcePoI extends PoI {
SourcePoI() { this = "SourcePoI" }
override predicate is(Node l0) {
exists(Configuration cfg | cfg.isSource(l0) or cfg.isSource(l0, _))
}
}
/**
* A "sink" for any active configuration.
*/
class SinkPoI extends PoI {
SinkPoI() { this = "SinkPoI" }
override predicate is(Node l0) {
exists(Configuration cfg | cfg.isSink(l0) or cfg.isSink(l0, _))
}
}
/**
* A "barrier" for any active configuration.
*/
class BarrierPoI extends PoI {
BarrierPoI() { this = "BarrierPoI" }
override predicate is(Node l0) {
exists(Configuration cfg |
cfg.isBarrier(l0) or
cfg.isBarrierEdge(l0, _) or
cfg.isBarrierEdge(l0, _, _) or
cfg.isLabeledBarrier(l0, _)
)
}
}
/**
* Provides groups of often used points of interest.
*/
module StandardPoIGroups {
/**
* A server-related point of interest.
*/
class ServerRelatedPoI extends PoI {
ServerRelatedPoI() {
this instanceof UnpromotedRouteSetupPoI or
this instanceof UnpromotedRouteHandlerPoI or
this instanceof UnpromotedRouteHandlerWithFlowPoI
}
}
/**
* A configuration-related point of interest.
*/
class DataFlowConfigurationPoI extends PoI {
DataFlowConfigurationPoI() {
this instanceof SourcePoI or
this instanceof SinkPoI
}
}
}
import StandardPoIGroups
}
import StandardPoIs
/**
* A tagging interface for a custom point of interest that should be
* enabled in the absence of an explicit
* `PoIConfiguration::enabled/1`.
*/
abstract class ActivePoI extends PoI {
bindingset[this]
ActivePoI() { any() }
}
private module PoIConfigDefaults {
predicate enabled(PoI poi) { poi instanceof ActivePoI }
predicate shown(Node n) { not classify(n.getFile(), _) }
}
/**
* A configuration for the points of interest to display.
*/
abstract class PoIConfiguration extends string {
bindingset[this]
PoIConfiguration() { any() }
/**
* Holds if the points of interest from `poi` should be shown.
*/
predicate enabled(PoI poi) { PoIConfigDefaults::enabled(poi) }
/**
* Holds if the points of interest `n` should be shown.
*/
predicate shown(Node n) { PoIConfigDefaults::shown(n) }
}
/**
* A class of points of interest.
*
* Note that only one of the `is/1`, `is/3`, `is/5` methods should
* be overridden, as two overrides will degrade the alert UI
* slightly.
*/
abstract class PoI extends string {
bindingset[this]
PoI() { any() }
/**
* Holds if `l0` is a point of interest.
*/
predicate is(Node l0) { none() }
/**
* Holds if `l0` is a point of interest, with `l1` as an auxiliary location described by `t1`.
*/
predicate is(Node l0, Node l1, string t1) { none() }
/**
* Holds if `l0` is a point of interest, with `l1` and `l2` as auxiliary locations described by `t1` and `t2`.
*/
predicate is(Node l0, Node l1, string t1, Node l2, string t2) { none() }
/**
* Gets the message format for the point of interest.
*/
string getFormat() {
is(_) and result = ""
or
is(_, _, _) and result = "$@"
or
is(_, _, _, _, _) and result = "$@ $@"
}
}
/**
* An alert query for a point of interest.
*
* Should be used as:
*
* ```
* query predicate problems = alertQuery/6;
* ```
*
* Or alternatively:
*
* ```
* from Locatable l1line, string msg, Node l2, string s2, Node l3, string s3
* where alertQuery(l1line, msg, l2, s2, l3, s3)
* select l1line, msg, l2, s2, l3, s3
* ```
*
* Note that some points of interest do not have auxiliary
* locations, so `l2`,`l3`, `s2`, `s3` may have placeholder values.
*/
predicate alertQuery(Locatable l1line, string msg, Node l2, string s2, Node l3, string s3) {
exists(PoI poi, Node l1, string m |
l1.getAstNode().(FirstLineOf) = l1line and
(
not exists(PoIConfiguration cfg) and
PoIConfigDefaults::enabled(poi) and
PoIConfigDefaults::shown(l1)
or
exists(PoIConfiguration cfg |
cfg.enabled(poi) and
cfg.shown(l1)
)
) and
m = poi.getFormat() and
if m = "" then msg = poi else msg = poi + ": " + m
|
poi.is(l1) and
l1 = l2 and
s2 = "irrelevant" and
l1 = l3 and
s3 = "irrelevant"
or
poi.is(l1, l2, s2) and
l1 = l3 and
s3 = "irrelevant"
or
poi.is(l1, l2, s2, l3, s3)
)
}

View File

@@ -261,6 +261,11 @@ predicate similarContainers(StmtContainer sc, StmtContainer other, float percent
)
}
/**
* INTERNAL: do not use.
*
* Holds if `line` in `f` is similar to a line somewhere else.
*/
predicate similarLines(File f, int line) {
exists(SimilarBlock b | b.sourceFile() = f and line in [b.sourceStartLine() .. b.sourceEndLine()])
}
@@ -275,6 +280,7 @@ private predicate similarLinesPerEquivalenceClass(int equivClass, int lines, Fil
)
}
/** Holds if `coveredLines` lines of `f` are similar to lines in `otherFile`. */
pragma[noopt]
private predicate similarLinesCovered(File f, int coveredLines, File otherFile) {
exists(int numLines | numLines = f.getNumberOfLines() |
@@ -296,6 +302,11 @@ private predicate similarLinesCovered(File f, int coveredLines, File otherFile)
)
}
/**
* INTERNAL: do not use.
*
* Holds if `line` in `f` is duplicated by a line somewhere else.
*/
predicate duplicateLines(File f, int line) {
exists(DuplicateBlock b |
b.sourceFile() = f and line in [b.sourceStartLine() .. b.sourceEndLine()]
@@ -312,6 +323,7 @@ private predicate duplicateLinesPerEquivalenceClass(int equivClass, int lines, F
)
}
/** Holds if `coveredLines` lines of `f` are duplicates of lines in `otherFile`. */
pragma[noopt]
private predicate duplicateLinesCovered(File f, int coveredLines, File otherFile) {
exists(int numLines | numLines = f.getNumberOfLines() |
@@ -333,6 +345,7 @@ private predicate duplicateLinesCovered(File f, int coveredLines, File otherFile
)
}
/** Holds if most of `f` (`percent`%) is similar to `other`. */
predicate similarFiles(File f, File other, int percent) {
exists(int covered, int total |
similarLinesCovered(f, covered, other) and
@@ -343,6 +356,7 @@ predicate similarFiles(File f, File other, int percent) {
not duplicateFiles(f, other, _)
}
/** Holds if most of `f` (`percent`%) is duplicated by `other`. */
predicate duplicateFiles(File f, File other, int percent) {
exists(int covered, int total |
duplicateLinesCovered(f, covered, other) and

View File

@@ -7,85 +7,8 @@
* @id js/file-classifier
*/
import semmle.javascript.GeneratedCode
import semmle.javascript.frameworks.Testing
import semmle.javascript.frameworks.Templating
import semmle.javascript.dependencies.FrameworkLibraries
/**
* Holds if `e` may be caused by parsing a template file as plain HTML or JavaScript.
*
* We use two heuristics: check for the presence of a known template delimiter preceding
* the error on the same line, and check whether the file name contains `template` or
* `templates`.
*/
predicate maybeCausedByTemplate(JSParseError e) {
exists(File f | f = e.getFile() |
e.getLine().indexOf(Templating::getADelimiter()) <= e.getLocation().getStartColumn()
or
f.getAbsolutePath().regexpMatch("(?i).*\\btemplates?\\b.*")
)
}
/**
* Holds if `e` is an expression in the form `o.p1.p2.p3....pn`.
*/
private predicate isNestedDotExpr(DotExpr e) {
e.getBase() instanceof VarAccess or
isNestedDotExpr(e.getBase())
}
/**
* Holds if `tl` only contains variable declarations and field reads.
*/
private predicate looksLikeExterns(TopLevel tl) {
forex(Stmt s | s.getParent() = tl |
exists(VarDeclStmt vds | vds = s |
forall(VariableDeclarator vd | vd = vds.getADecl() | not exists(vd.getInit()))
)
or
isNestedDotExpr(s.(ExprStmt).getExpr())
)
}
/**
* Holds if `f` is classified as belonging to `category`.
*
* There are currently four categories:
* - `"generated"`: `f` contains generated or minified code;
* - `"test"`: `f` contains test code;
* - `"externs"`: `f` contains externs declarations;
* - `"library"`: `f` contains library code;
* - `"template"`: `f` contains template code.
*/
predicate classify(File f, string category) {
isGenerated(f.getATopLevel()) and category = "generated"
or
(
exists(Test t | t.getFile() = f)
or
exists(string stemExt | stemExt = "test" or stemExt = "spec" |
f = getTestFile(any(File orig), stemExt)
)
or
f.getAbsolutePath().regexpMatch(".*/__(mocks|tests)__/.*")
) and
category = "test"
or
(f.getATopLevel().isExterns() or looksLikeExterns(f.getATopLevel())) and
category = "externs"
or
f.getATopLevel() instanceof FrameworkLibraryInstance and category = "library"
or
exists(JSParseError err | maybeCausedByTemplate(err) |
f = err.getFile() and category = "template"
)
or
// Polymer templates
exists(HTML::Element elt | elt.getName() = "template" |
f = elt.getFile() and category = "template"
)
}
import javascript
import ClassifyFiles
from File f, string category
where classify(f, category)

View File

@@ -0,0 +1,85 @@
/**
* Provides classes and predicates for classifying files as containing
* generated code, test code, externs declarations, library code or
* template code.
*/
import semmle.javascript.GeneratedCode
import semmle.javascript.frameworks.Testing
import semmle.javascript.frameworks.Templating
import semmle.javascript.dependencies.FrameworkLibraries
/**
* Holds if `e` may be caused by parsing a template file as plain HTML or JavaScript.
*
* We use two heuristics: check for the presence of a known template delimiter preceding
* the error on the same line, and check whether the file name contains `template` or
* `templates`.
*/
predicate maybeCausedByTemplate(JSParseError e) {
exists(File f | f = e.getFile() |
e.getLine().indexOf(Templating::getADelimiter()) <= e.getLocation().getStartColumn()
or
f.getAbsolutePath().regexpMatch("(?i).*\\btemplates?\\b.*")
)
}
/**
* Holds if `e` is an expression in the form `o.p1.p2.p3....pn`.
*/
private predicate isNestedDotExpr(DotExpr e) {
e.getBase() instanceof VarAccess or
isNestedDotExpr(e.getBase())
}
/**
* Holds if `tl` only contains variable declarations and field reads.
*/
private predicate looksLikeExterns(TopLevel tl) {
forex(Stmt s | s.getParent() = tl |
exists(VarDeclStmt vds | vds = s |
forall(VariableDeclarator vd | vd = vds.getADecl() | not exists(vd.getInit()))
)
or
isNestedDotExpr(s.(ExprStmt).getExpr())
)
}
/**
* Holds if `f` is classified as belonging to `category`.
*
* There are currently four categories:
* - `"generated"`: `f` contains generated or minified code;
* - `"test"`: `f` contains test code;
* - `"externs"`: `f` contains externs declarations;
* - `"library"`: `f` contains library code;
* - `"template"`: `f` contains template code.
*/
predicate classify(File f, string category) {
isGenerated(f.getATopLevel()) and category = "generated"
or
(
exists(Test t | t.getFile() = f)
or
exists(string stemExt | stemExt = "test" or stemExt = "spec" |
f = getTestFile(any(File orig), stemExt)
)
or
f.getAbsolutePath().regexpMatch(".*/__(mocks|tests)__/.*")
) and
category = "test"
or
(f.getATopLevel().isExterns() or looksLikeExterns(f.getATopLevel())) and
category = "externs"
or
f.getATopLevel() instanceof FrameworkLibraryInstance and category = "library"
or
exists(JSParseError err | maybeCausedByTemplate(err) |
f = err.getFile() and category = "template"
)
or
// Polymer templates
exists(HTML::Element elt | elt.getName() = "template" |
f = elt.getFile() and category = "template"
)
}

View File

@@ -12,6 +12,7 @@ import semmle.javascript.Base64
import semmle.javascript.CFG
import semmle.javascript.Classes
import semmle.javascript.Closure
import semmle.javascript.Collections
import semmle.javascript.Comments
import semmle.javascript.Concepts
import semmle.javascript.Constants
@@ -36,6 +37,7 @@ import semmle.javascript.JsonParsers
import semmle.javascript.JSX
import semmle.javascript.Lines
import semmle.javascript.Locations
import semmle.javascript.MembershipCandidates
import semmle.javascript.Modules
import semmle.javascript.NodeJS
import semmle.javascript.NPM

View File

@@ -0,0 +1,16 @@
/**
* @name Jump-to-definition links
* @description Generates use-definition pairs that provide the data
* for jump-to-definition in the code viewer.
* @kind definitions
* @id js/ide-jump-to-definition
* @tags ide-contextual-queries/local-definitions
*/
import definitions
external string selectedSourceFile();
from Locatable e, ASTNode def, string kind
where def = definitionOf(e, kind) and e.getFile() = getEncodedFile(selectedSourceFile())
select e, def, kind

View File

@@ -0,0 +1,16 @@
/**
* @name Find-references links
* @description Generates use-definition pairs that provide the data
* for find-references in the code viewer.
* @kind definitions
* @id js/ide-find-references
* @tags ide-contextual-queries/local-references
*/
import definitions
external string selectedSourceFile();
from Locatable e, ASTNode def, string kind
where def = definitionOf(e, kind) and def.getFile() = getEncodedFile(selectedSourceFile())
select e, def, kind

Some files were not shown because too many files have changed in this diff Show More