mirror of
https://github.com/github/codeql.git
synced 2026-05-13 10:49:26 +02:00
merge in main
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
description: Associate symbols with external module declarations
|
||||
compatibility: backwards
|
||||
1217
javascript/downgrades/initial/semmlecode.javascript.dbscheme
Normal file
1217
javascript/downgrades/initial/semmlecode.javascript.dbscheme
Normal file
File diff suppressed because it is too large
Load Diff
4
javascript/downgrades/qlpack.yml
Normal file
4
javascript/downgrades/qlpack.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
name: codeql/javascript-downgrades
|
||||
groups: javascript
|
||||
downgrades: .
|
||||
library: true
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "typescript-parser-wrapper",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"typescript": "4.6.2"
|
||||
"typescript": "4.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --project tsconfig.json",
|
||||
|
||||
@@ -670,6 +670,12 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
|
||||
if (file.endsWith(".d.ts")) {
|
||||
return pathlib.basename(file, ".d.ts");
|
||||
}
|
||||
if (file.endsWith(".d.mts") || file.endsWith(".d.cts")) {
|
||||
// We don't extract d.mts or d.cts files, but their symbol can coincide with that of a d.ts file,
|
||||
// which means any module bindings we generate for it will ultimately be visible in QL.
|
||||
let base = pathlib.basename(file);
|
||||
return base.substring(0, base.length - '.d.mts'.length);
|
||||
}
|
||||
let base = pathlib.basename(file);
|
||||
let dot = base.lastIndexOf('.');
|
||||
return dot === -1 || dot === 0 ? base : base.substring(0, dot);
|
||||
|
||||
@@ -1068,6 +1068,7 @@ export class TypeTable {
|
||||
let superType = this.typeChecker.getTypeFromTypeNode(typeExpr);
|
||||
if (superType == null) continue;
|
||||
let baseTypeSymbol = superType.symbol;
|
||||
baseTypeSymbol = (baseTypeSymbol as any)?.type?.symbol ?? baseTypeSymbol;
|
||||
if (baseTypeSymbol == null) continue;
|
||||
let baseId = this.getSymbolId(baseTypeSymbol);
|
||||
// Note: take care not to perform a recursive call between the two `push` calls.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
version "12.7.11"
|
||||
resolved node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446
|
||||
|
||||
typescript@4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4"
|
||||
integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==
|
||||
typescript@4.7.2:
|
||||
version "4.7.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4"
|
||||
integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==
|
||||
|
||||
@@ -141,8 +141,9 @@ public class Fetcher {
|
||||
entryPath = entryPath.subpath(1, entryPath.getNameCount());
|
||||
|
||||
String filename = entryPath.getFileName().toString();
|
||||
if (!filename.endsWith(".d.ts") && !filename.equals("package.json")) {
|
||||
continue; // Only extract .d.ts files and package.json
|
||||
if (!filename.endsWith(".d.ts") && !filename.endsWith(".d.mts") && !filename.endsWith(".d.cts")
|
||||
&& !filename.equals("package.json")) {
|
||||
continue; // Only extract .d.ts, .d.mts, .d.cts files, and package.json
|
||||
}
|
||||
relativePaths.add(entryPath);
|
||||
Path outputFile = destDir.resolve(entryPath);
|
||||
|
||||
@@ -590,7 +590,7 @@ public class ASTExtractor {
|
||||
trapwriter.addTuple("literals", valueString, source, key);
|
||||
Position start = nd.getLoc().getStart();
|
||||
com.semmle.util.locations.Position startPos = new com.semmle.util.locations.Position(start.getLine(), start.getColumn() + 1 /* Convert from 0-based to 1-based. */, start.getOffset());
|
||||
|
||||
|
||||
if (nd.isRegExp()) {
|
||||
OffsetTranslation offsets = new OffsetTranslation();
|
||||
offsets.set(0, 1); // skip the initial '/'
|
||||
@@ -622,7 +622,7 @@ public class ASTExtractor {
|
||||
/**
|
||||
* Constant-folds simple string concatenations in `exp` while keeping an offset translation
|
||||
* that tracks back to the original source.
|
||||
*/
|
||||
*/
|
||||
private Pair<String, OffsetTranslation> getStringConcatResult(Expression exp) {
|
||||
if (exp instanceof BinaryExpression) {
|
||||
BinaryExpression be = (BinaryExpression) exp;
|
||||
@@ -636,7 +636,7 @@ public class ASTExtractor {
|
||||
if (str.length() > 1000) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
int delta = be.getRight().getLoc().getStart().getOffset() - be.getLeft().getLoc().getStart().getOffset();
|
||||
int offset = left.fst().length();
|
||||
return Pair.make(str, left.snd().append(right.snd(), offset, delta));
|
||||
@@ -747,7 +747,7 @@ public class ASTExtractor {
|
||||
visit(nd.getProperty(), key, 1, IdContext.TYPE_LABEL);
|
||||
} else {
|
||||
IdContext baseIdContext =
|
||||
c.idcontext == IdContext.EXPORT ? IdContext.EXPORT_BASE : IdContext.VAR_BIND;
|
||||
(c.idcontext == IdContext.EXPORT || c.idcontext == IdContext.EXPORT_BASE) ? IdContext.EXPORT_BASE : IdContext.VAR_BIND;
|
||||
visit(nd.getObject(), key, 0, baseIdContext);
|
||||
visit(nd.getProperty(), key, 1, nd.isComputed() ? IdContext.VAR_BIND : IdContext.LABEL);
|
||||
}
|
||||
@@ -848,14 +848,14 @@ public class ASTExtractor {
|
||||
public Label visit(BinaryExpression nd, Context c) {
|
||||
Label key = super.visit(nd, c);
|
||||
if (nd.getOperator().equals("in") && nd.getLeft() instanceof Identifier && ((Identifier)nd.getLeft()).getName().startsWith("#")) {
|
||||
// this happens with Ergonomic brand checks for Private Fields (see https://github.com/tc39/proposal-private-fields-in-in).
|
||||
// this happens with Ergonomic brand checks for Private Fields (see https://github.com/tc39/proposal-private-fields-in-in).
|
||||
// it's the only case where private field identifiers are used not as a field.
|
||||
visit(nd.getLeft(), key, 0, IdContext.LABEL, true);
|
||||
} else {
|
||||
visit(nd.getLeft(), key, 0, true);
|
||||
}
|
||||
visit(nd.getRight(), key, 1, true);
|
||||
|
||||
|
||||
extractRegxpFromBinop(nd, c);
|
||||
return key;
|
||||
}
|
||||
@@ -1815,7 +1815,7 @@ public class ASTExtractor {
|
||||
visit(nd.getLocal(), lbl, 1, nd.hasTypeKeyword() ? IdContext.TYPE_ONLY_IMPORT : c.idcontext);
|
||||
if (nd.hasTypeKeyword()) {
|
||||
trapwriter.addTuple("has_type_keyword", lbl);
|
||||
}
|
||||
}
|
||||
return lbl;
|
||||
}
|
||||
|
||||
@@ -2191,6 +2191,7 @@ public class ASTExtractor {
|
||||
visitAll(nd.getBody(), key);
|
||||
contextManager.leaveContainer();
|
||||
scopeManager.leaveScope();
|
||||
emitNodeSymbol(nd, key);
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ public class FileExtractor {
|
||||
}
|
||||
},
|
||||
|
||||
TYPESCRIPT(".ts", ".tsx") {
|
||||
TYPESCRIPT(".ts", ".tsx", ".mts", ".cts") {
|
||||
@Override
|
||||
protected boolean contains(File f, String lcExt, ExtractorConfig config) {
|
||||
if (config.getTypeScriptMode() == TypeScriptMode.NONE) return false;
|
||||
|
||||
@@ -40,7 +40,8 @@ public class HTMLExtractor implements IExtractor {
|
||||
this.textualExtractor = textualExtractor;
|
||||
|
||||
this.scopeManager =
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(), true);
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(),
|
||||
ScopeManager.FileKind.TEMPLATE);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -425,7 +426,7 @@ public class HTMLExtractor implements IExtractor {
|
||||
extractSnippet(
|
||||
TopLevelKind.ANGULAR_STYLE_TEMPLATE,
|
||||
config.withSourceType(SourceType.ANGULAR_STYLE_TEMPLATE),
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2020, true),
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2020, ScopeManager.FileKind.TEMPLATE),
|
||||
textualExtractor,
|
||||
m.group(bodyGroup),
|
||||
m.start(bodyGroup),
|
||||
|
||||
@@ -43,7 +43,7 @@ public class Main {
|
||||
* A version identifier that should be updated every time the extractor changes in such a way that
|
||||
* it may produce different tuples for the same file under the same {@link ExtractorConfig}.
|
||||
*/
|
||||
public static final String EXTRACTOR_VERSION = "2022-02-22";
|
||||
public static final String EXTRACTOR_VERSION = "2022-06-08";
|
||||
|
||||
public static final Pattern NEWLINE = Pattern.compile("\n");
|
||||
|
||||
@@ -153,7 +153,7 @@ public class Main {
|
||||
ensureFileIsExtracted(file, ap);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TypeScriptParser tsParser = extractorState.getTypeScriptParser();
|
||||
tsParser.setTypescriptRam(extractorConfig.getTypeScriptRam());
|
||||
if (containsTypeScriptFiles()) {
|
||||
@@ -460,7 +460,7 @@ public class Main {
|
||||
if (ap.has(P_TYPESCRIPT)) return TypeScriptMode.BASIC;
|
||||
return TypeScriptMode.NONE;
|
||||
}
|
||||
|
||||
|
||||
private Path inferSourceRoot(ArgsParser ap) {
|
||||
List<File> files = getFilesArg(ap);
|
||||
Path sourceRoot = files.iterator().next().toPath().toAbsolutePath().getParent();
|
||||
|
||||
@@ -97,20 +97,31 @@ public class ScopeManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static enum FileKind {
|
||||
/** Any file not specific to one of the other file kinds. */
|
||||
PLAIN,
|
||||
|
||||
/** A file potentially containing template tags. */
|
||||
TEMPLATE,
|
||||
|
||||
/** A d.ts file, containing TypeScript ambient declarations. */
|
||||
TYPESCRIPT_DECLARATION,
|
||||
}
|
||||
|
||||
private final TrapWriter trapWriter;
|
||||
private Scope curScope;
|
||||
private final Scope toplevelScope;
|
||||
private final ECMAVersion ecmaVersion;
|
||||
private final Set<String> implicitGlobals = new LinkedHashSet<String>();
|
||||
private Scope implicitVariableScope;
|
||||
private final boolean isInTemplateScope;
|
||||
private final FileKind fileKind;
|
||||
|
||||
public ScopeManager(TrapWriter trapWriter, ECMAVersion ecmaVersion, boolean isInTemplateScope) {
|
||||
public ScopeManager(TrapWriter trapWriter, ECMAVersion ecmaVersion, FileKind fileKind) {
|
||||
this.trapWriter = trapWriter;
|
||||
this.toplevelScope = enterScope(ScopeKind.GLOBAL, trapWriter.globalID("global_scope"), null);
|
||||
this.ecmaVersion = ecmaVersion;
|
||||
this.implicitVariableScope = toplevelScope;
|
||||
this.isInTemplateScope = isInTemplateScope;
|
||||
this.implicitVariableScope = toplevelScope;
|
||||
this.fileKind = fileKind;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,7 +129,11 @@ public class ScopeManager {
|
||||
* relevant template tags.
|
||||
*/
|
||||
public boolean isInTemplateFile() {
|
||||
return isInTemplateScope;
|
||||
return this.fileKind == FileKind.TEMPLATE;
|
||||
}
|
||||
|
||||
public boolean isInTypeScriptDeclarationFile() {
|
||||
return this.fileKind == FileKind.TYPESCRIPT_DECLARATION;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,7 +236,7 @@ public class ScopeManager {
|
||||
|
||||
/**
|
||||
* Get the label for a given variable in the current scope; if it cannot be found, add it to the
|
||||
* implicit variable scope (usually the global scope).
|
||||
* implicit variable scope (usually the global scope).
|
||||
*/
|
||||
public Label getVarKey(String name) {
|
||||
Label lbl = curScope.lookupVariable(name);
|
||||
@@ -411,7 +426,7 @@ public class ScopeManager {
|
||||
// cases where we turn on the 'declKind' flags
|
||||
@Override
|
||||
public Void visit(FunctionDeclaration nd, Void v) {
|
||||
if (nd.hasDeclareKeyword()) return null;
|
||||
if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null;
|
||||
// strict mode functions are block-scoped, non-strict mode ones aren't
|
||||
if (blockscope == isStrict) visit(nd.getId(), DeclKind.var);
|
||||
return null;
|
||||
@@ -419,7 +434,7 @@ public class ScopeManager {
|
||||
|
||||
@Override
|
||||
public Void visit(ClassDeclaration nd, Void c) {
|
||||
if (nd.hasDeclareKeyword()) return null;
|
||||
if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null;
|
||||
if (blockscope) visit(nd.getClassDef().getId(), DeclKind.varAndType);
|
||||
return null;
|
||||
}
|
||||
@@ -468,7 +483,7 @@ public class ScopeManager {
|
||||
|
||||
@Override
|
||||
public Void visit(VariableDeclaration nd, Void v) {
|
||||
if (nd.hasDeclareKeyword()) return null;
|
||||
if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null;
|
||||
// in block scoping mode, only process 'let'; in non-block scoping
|
||||
// mode, only process non-'let'
|
||||
if (blockscope == nd.isBlockScoped(ecmaVersion)) visit(nd.getDeclarations());
|
||||
@@ -503,7 +518,8 @@ public class ScopeManager {
|
||||
@Override
|
||||
public Void visit(NamespaceDeclaration nd, Void c) {
|
||||
if (blockscope) return null;
|
||||
boolean hasVariable = nd.isInstantiated() && !nd.hasDeclareKeyword();
|
||||
boolean isAmbientOutsideDtsFile = nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile();
|
||||
boolean hasVariable = nd.isInstantiated() && !isAmbientOutsideDtsFile;
|
||||
visit(nd.getName(), hasVariable ? DeclKind.varAndNamespace : DeclKind.namespace);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public class ScriptExtractor implements IExtractor {
|
||||
}
|
||||
|
||||
ScopeManager scopeManager =
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(), false);
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(), ScopeManager.FileKind.PLAIN);
|
||||
Label toplevelLabel = null;
|
||||
LoCInfo loc;
|
||||
try {
|
||||
|
||||
@@ -22,8 +22,10 @@ public class TypeScriptExtractor implements IExtractor {
|
||||
String source = textualExtractor.getSource();
|
||||
File sourceFile = textualExtractor.getExtractedFile();
|
||||
Result res = state.getTypeScriptParser().parse(sourceFile, source, textualExtractor.getMetrics());
|
||||
ScopeManager scopeManager =
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2017, false);
|
||||
ScopeManager.FileKind fileKind = sourceFile.getName().endsWith(".d.ts")
|
||||
? ScopeManager.FileKind.TYPESCRIPT_DECLARATION
|
||||
: ScopeManager.FileKind.PLAIN;
|
||||
ScopeManager scopeManager = new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2017, fileKind);
|
||||
try {
|
||||
FileSnippet snippet = state.getSnippets().get(sourceFile.toPath());
|
||||
SourceType sourceType = snippet != null ? snippet.getSourceType() : jsExtractor.establishSourceType(source, false);
|
||||
|
||||
@@ -7,9 +7,10 @@ import com.semmle.js.ast.Visitor;
|
||||
import java.util.List;
|
||||
|
||||
/** A statement of form <code>declare module "X" {...}</code>. */
|
||||
public class ExternalModuleDeclaration extends Statement {
|
||||
public class ExternalModuleDeclaration extends Statement implements INodeWithSymbol {
|
||||
private final Literal name;
|
||||
private final List<Statement> body;
|
||||
private int symbol = -1;
|
||||
|
||||
public ExternalModuleDeclaration(SourceLocation loc, Literal name, List<Statement> body) {
|
||||
super("ExternalModuleDeclaration", loc);
|
||||
@@ -29,4 +30,14 @@ public class ExternalModuleDeclaration extends Statement {
|
||||
public List<Statement> getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSymbol() {
|
||||
return this.symbol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSymbol(int symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,7 +591,7 @@ public class TypeScriptASTConverter {
|
||||
return convertTryStatement(node, loc);
|
||||
case "TupleType":
|
||||
return convertTupleType(node, loc);
|
||||
case "NamedTupleMember":
|
||||
case "NamedTupleMember":
|
||||
return convertNamedTupleMember(node, loc);
|
||||
case "TypeAliasDeclaration":
|
||||
return convertTypeAliasDeclaration(node, loc);
|
||||
@@ -1710,7 +1710,9 @@ public class TypeScriptASTConverter {
|
||||
}
|
||||
if (nameNode instanceof Literal) {
|
||||
// Declaration of form: declare module "X" {...}
|
||||
return new ExternalModuleDeclaration(loc, (Literal) nameNode, body);
|
||||
ExternalModuleDeclaration decl = new ExternalModuleDeclaration(loc, (Literal) nameNode, body);
|
||||
attachSymbolInformation(decl, node);
|
||||
return decl;
|
||||
}
|
||||
if (hasFlag(node, "GlobalAugmentation")) {
|
||||
// Declaration of form: declare global {...}
|
||||
|
||||
@@ -97,28 +97,28 @@ scopenodes(#20001,#20033)
|
||||
scopenesting(#20033,#20000)
|
||||
is_module(#20001)
|
||||
is_es2015_module(#20001)
|
||||
#20034=*
|
||||
stmts(#20034,30,#20001,0,"export ... foo();")
|
||||
hasLocation(#20034,#20003)
|
||||
stmt_containers(#20034,#20001)
|
||||
#20034=@"var;{foo};{#20033}"
|
||||
variables(#20034,"foo",#20033)
|
||||
#20035=*
|
||||
stmts(#20035,17,#20034,-1,"declare ... foo();")
|
||||
#20036=@"loc,{#10000},1,8,1,30"
|
||||
locations_default(#20036,#10000,1,8,1,30)
|
||||
hasLocation(#20035,#20036)
|
||||
stmts(#20035,30,#20001,0,"export ... foo();")
|
||||
hasLocation(#20035,#20003)
|
||||
stmt_containers(#20035,#20001)
|
||||
has_declare_keyword(#20035)
|
||||
#20037=*
|
||||
exprs(#20037,78,#20035,-1,"foo")
|
||||
hasLocation(#20037,#20013)
|
||||
expr_containers(#20037,#20035)
|
||||
literals("foo","foo",#20037)
|
||||
#20038=@"var;{foo};{#20000}"
|
||||
variables(#20038,"foo",#20000)
|
||||
decl(#20037,#20038)
|
||||
#20036=*
|
||||
stmts(#20036,17,#20035,-1,"declare ... foo();")
|
||||
#20037=@"loc,{#10000},1,8,1,30"
|
||||
locations_default(#20037,#10000,1,8,1,30)
|
||||
hasLocation(#20036,#20037)
|
||||
stmt_containers(#20036,#20001)
|
||||
has_declare_keyword(#20036)
|
||||
#20038=*
|
||||
exprs(#20038,78,#20036,-1,"foo")
|
||||
hasLocation(#20038,#20013)
|
||||
expr_containers(#20038,#20036)
|
||||
literals("foo","foo",#20038)
|
||||
decl(#20038,#20034)
|
||||
#20039=*
|
||||
scopes(#20039,1)
|
||||
scopenodes(#20035,#20039)
|
||||
scopenodes(#20036,#20039)
|
||||
scopenesting(#20039,#20033)
|
||||
#20040=@"var;{arguments};{#20039}"
|
||||
variables(#20040,"arguments",#20039)
|
||||
@@ -142,8 +142,8 @@ hasLocation(#20043,#20044)
|
||||
exit_cfg_node(#20045,#20001)
|
||||
hasLocation(#20045,#20031)
|
||||
successor(#20041,#20045)
|
||||
successor(#20034,#20035)
|
||||
successor(#20035,#20041)
|
||||
successor(#20043,#20034)
|
||||
successor(#20035,#20036)
|
||||
successor(#20036,#20041)
|
||||
successor(#20043,#20035)
|
||||
numlines(#10000,2,2,0)
|
||||
filetype(#10000,"typescript")
|
||||
|
||||
@@ -305,11 +305,12 @@ hasLocation(#20096,#20097)
|
||||
enclosing_stmt(#20096,#20092)
|
||||
expr_containers(#20096,#20001)
|
||||
#20098=*
|
||||
exprs(#20098,79,#20096,0,"M")
|
||||
exprs(#20098,103,#20096,0,"M")
|
||||
hasLocation(#20098,#20052)
|
||||
enclosing_stmt(#20098,#20092)
|
||||
expr_containers(#20098,#20001)
|
||||
literals("M","M",#20098)
|
||||
namespacebind(#20098,#20069)
|
||||
bind(#20098,#20066)
|
||||
#20099=*
|
||||
exprs(#20099,0,#20096,1,"N")
|
||||
|
||||
@@ -34,7 +34,7 @@ class IdorTaint extends TaintTracking::Configuration {
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer for values that have succesfully been compared to another value.
|
||||
* A sanitizer for values that have successfully been compared to another value.
|
||||
*/
|
||||
class EqualityGuard extends TaintTracking::SanitizerGuardNode, ValueNode {
|
||||
override EqualityTest astNode;
|
||||
|
||||
@@ -144,9 +144,9 @@ private module AccessPaths {
|
||||
not param = base.getReceiver()
|
||||
|
|
||||
result = param and
|
||||
name = param.getAnImmediateUse().asExpr().(Parameter).getName()
|
||||
name = param.asSource().asExpr().(Parameter).getName()
|
||||
or
|
||||
param.getAnImmediateUse().asExpr() instanceof DestructuringPattern and
|
||||
param.asSource().asExpr() instanceof DestructuringPattern and
|
||||
result = param.getMember(name)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-experimental-atm-lib
|
||||
version: 0.2.1
|
||||
version: 0.3.1
|
||||
extractor: javascript
|
||||
library: true
|
||||
groups:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-experimental-atm-model
|
||||
version: 0.1.1
|
||||
version: 0.2.1
|
||||
groups:
|
||||
- javascript
|
||||
- experimental
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
dependencies:
|
||||
codeql/javascript-experimental-atm-model:
|
||||
version: 0.1.0
|
||||
version: 0.2.0
|
||||
compiled: false
|
||||
lockVersion: 1.0.0
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for a particular dataflow config.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import evaluation.EndToEndEvaluation
|
||||
|
||||
query predicate countAlertsAndSinks(int numAlerts, int numSinks) {
|
||||
numAlerts =
|
||||
count(DataFlow::Configuration cfg, DataFlow::Node source, DataFlow::Node sink |
|
||||
cfg.hasFlow(source, sink) and not isFlowExcluded(source, sink)
|
||||
) and
|
||||
numSinks =
|
||||
count(DataFlow::Node sink |
|
||||
exists(DataFlow::Configuration cfg | cfg.isSink(sink) or cfg.isSink(sink, _))
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `CodeInjection` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.CodeInjectionQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `NosqlInection` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.NosqlInjectionQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `SqlInection` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.SqlInjectionQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `TaintedPath` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.TaintedPathQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `DomBasedXss` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.DomBasedXssQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `XssThroughDom` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.XssThroughDomQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -6,4 +6,4 @@ groups:
|
||||
- experimental
|
||||
dependencies:
|
||||
codeql/javascript-experimental-atm-lib: "*"
|
||||
codeql/javascript-experimental-atm-model: "0.1.0"
|
||||
codeql/javascript-experimental-atm-model: "0.2.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
dependencies:
|
||||
codeql/javascript-experimental-atm-model:
|
||||
version: 0.1.0
|
||||
version: 0.2.0
|
||||
compiled: false
|
||||
lockVersion: 1.0.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: codeql/javascript-experimental-atm-queries
|
||||
language: javascript
|
||||
version: 0.2.1
|
||||
version: 0.3.1
|
||||
suites: codeql-suites
|
||||
defaultSuiteFile: codeql-suites/javascript-atm-code-scanning.qls
|
||||
groups:
|
||||
@@ -8,4 +8,4 @@ groups:
|
||||
- experimental
|
||||
dependencies:
|
||||
codeql/javascript-experimental-atm-lib: "*"
|
||||
codeql/javascript-experimental-atm-model: "0.1.0"
|
||||
codeql/javascript-experimental-atm-model: "0.2.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
dependencies:
|
||||
codeql/javascript-experimental-atm-model:
|
||||
version: 0.1.0
|
||||
version: 0.2.0
|
||||
compiled: false
|
||||
lockVersion: 1.0.0
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
## 0.1.4
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `isLibaryFile` predicate from `ClassifyFiles.qll` has been renamed to `isLibraryFile` to fix a typo.
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The `ReflectedXss`, `StoredXss`, `XssThroughDom`, and `ExceptionXss` modules from `Xss.qll` have been deprecated.
|
||||
Use the `Customizations.qll` file belonging to the query instead.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The [cash](https://github.com/fabiospampinato/cash) library is now modelled as an alias for JQuery.
|
||||
Sinks and sources from cash should now be handled by all XSS queries.
|
||||
* Added the `Selection` api as a DOM text source in the `js/xss-through-dom` query.
|
||||
* The security queries now recognize drag and drop data as a source, enabling the queries to flag additional alerts.
|
||||
* The security queries now recognize ClipboardEvent function parameters as a source, enabling the queries to flag additional alerts.
|
||||
|
||||
## 0.1.1
|
||||
|
||||
## 0.1.0
|
||||
|
||||
@@ -37,6 +37,8 @@ predicate inVoidContext(Expr e) {
|
||||
)
|
||||
or
|
||||
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
|
||||
or
|
||||
exists(ConditionalExpr cond | e = cond.getABranch() | inVoidContext(cond))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The security queries now recognize drag and drop data as a source, enabling the queries to flag additional alerts.
|
||||
* The security queries now recognize ClipboardEvent function parameters as a source, enabling the queries to flag additional alerts.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* The `ReflectedXss`, `StoredXss`, `XssThroughDom`, and `ExceptionXss` modules from `Xss.qll` have been deprecated.
|
||||
Use the `Customizations.qll` file belonging to the query instead.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added the `Selection` api as a DOM text source in the `js/xss-through-dom` query.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The [cash](https://github.com/fabiospampinato/cash) library is now modelled as an alias for JQuery.
|
||||
Sinks and sources from cash should now be handled by all XSS queries.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* All new ECMAScript 2022 features are now supported.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* Added support for TypeScript 4.7.
|
||||
14
javascript/ql/lib/change-notes/released/0.1.2.md
Normal file
14
javascript/ql/lib/change-notes/released/0.1.2.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## 0.1.2
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The `ReflectedXss`, `StoredXss`, `XssThroughDom`, and `ExceptionXss` modules from `Xss.qll` have been deprecated.
|
||||
Use the `Customizations.qll` file belonging to the query instead.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The [cash](https://github.com/fabiospampinato/cash) library is now modelled as an alias for JQuery.
|
||||
Sinks and sources from cash should now be handled by all XSS queries.
|
||||
* Added the `Selection` api as a DOM text source in the `js/xss-through-dom` query.
|
||||
* The security queries now recognize drag and drop data as a source, enabling the queries to flag additional alerts.
|
||||
* The security queries now recognize ClipboardEvent function parameters as a source, enabling the queries to flag additional alerts.
|
||||
5
javascript/ql/lib/change-notes/released/0.1.3.md
Normal file
5
javascript/ql/lib/change-notes/released/0.1.3.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.1.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `isLibaryFile` predicate from `ClassifyFiles.qll` has been renamed to `isLibraryFile` to fix a typo.
|
||||
1
javascript/ql/lib/change-notes/released/0.1.4.md
Normal file
1
javascript/ql/lib/change-notes/released/0.1.4.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.1.4
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.1.1
|
||||
lastReleaseVersion: 0.1.4
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-all
|
||||
version: 0.1.2-dev
|
||||
version: 0.2.0-dev
|
||||
groups: javascript
|
||||
dbscheme: semmlecode.javascript.dbscheme
|
||||
extractor: javascript
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
private import Expressions.ExprHasNoEffect
|
||||
|
||||
/**
|
||||
* An AMD `define` call.
|
||||
@@ -26,7 +27,7 @@ private import semmle.javascript.internal.CachedStages
|
||||
*/
|
||||
class AmdModuleDefinition extends CallExpr {
|
||||
AmdModuleDefinition() {
|
||||
getParent() instanceof ExprStmt and
|
||||
inVoidContext(this) and
|
||||
getCallee().(GlobalVarAccess).getName() = "define" and
|
||||
exists(int n | n = getNumArgument() |
|
||||
n = 1
|
||||
@@ -202,13 +203,22 @@ private class ConstantAmdDependencyPathElement extends PathExpr, ConstantString
|
||||
override string getValue() { result = getStringValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nd` is nested inside an AMD module definition.
|
||||
*/
|
||||
private predicate inAmdModuleDefinition(AstNode nd) {
|
||||
nd.getParent() instanceof AmdModuleDefinition
|
||||
or
|
||||
inAmdModuleDefinition(nd.getParent())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `def` is an AMD module definition in `tl` which is not
|
||||
* nested inside another module definition.
|
||||
*/
|
||||
private predicate amdModuleTopLevel(AmdModuleDefinition def, TopLevel tl) {
|
||||
def.getTopLevel() = tl and
|
||||
not def.getParent+() instanceof AmdModuleDefinition
|
||||
not inAmdModuleDefinition(def)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
* Provides an implementation of _API graphs_, which are an abstract representation of the API
|
||||
* surface used and/or defined by a code base.
|
||||
*
|
||||
* The nodes of the API graph represent definitions and uses of API components. The edges are
|
||||
* directed and labeled; they specify how the components represented by nodes relate to each other.
|
||||
* For example, if one of the nodes represents a definition of an API function, then there
|
||||
* will be nodes corresponding to the function's parameters, which are connected to the function
|
||||
* node by edges labeled `parameter <i>`.
|
||||
* See `API::Node` for more in-depth documentation.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
@@ -14,50 +10,159 @@ private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
|
||||
private import internal.CachedStages
|
||||
|
||||
/**
|
||||
* Provides classes and predicates for working with APIs defined or used in a database.
|
||||
* Provides classes and predicates for working with the API boundary between the current
|
||||
* codebase and external libraries.
|
||||
*
|
||||
* See `API::Node` for more in-depth documentation.
|
||||
*/
|
||||
module API {
|
||||
/**
|
||||
* An abstract representation of a definition or use of an API component such as a function
|
||||
* exported by an npm package, a parameter of such a function, or its result.
|
||||
* A node in the API graph, representing a value that has crossed the boundary between this
|
||||
* codebase and an external library (or in general, any external codebase).
|
||||
*
|
||||
* ### Basic usage
|
||||
*
|
||||
* API graphs are typically used to identify "API calls", that is, calls to an external function
|
||||
* whose implementation is not necessarily part of the current codebase.
|
||||
*
|
||||
* The most basic use of API graphs is typically as follows:
|
||||
* 1. Start with `API::moduleImport` for the relevant library.
|
||||
* 2. Follow up with a chain of accessors such as `getMember` describing how to get to the relevant API function.
|
||||
* 3. Map the resulting API graph nodes to data-flow nodes, using `asSource` or `asSink`.
|
||||
*
|
||||
* For example, a simplified way to get arguments to `underscore.extend` would be
|
||||
* ```ql
|
||||
* API::moduleImport("underscore").getMember("extend").getParameter(0).asSink()
|
||||
* ```
|
||||
*
|
||||
* The most commonly used accessors are `getMember`, `getParameter`, and `getReturn`.
|
||||
*
|
||||
* ### API graph nodes
|
||||
*
|
||||
* There are two kinds of nodes in the API graphs, distinguished by who is "holding" the value:
|
||||
* - **Use-nodes** represent values held by the current codebase, which came from an external library.
|
||||
* (The current codebase is "using" a value that came from the library).
|
||||
* - **Def-nodes** represent values held by the external library, which came from this codebase.
|
||||
* (The current codebase "defines" the value seen by the library).
|
||||
*
|
||||
* API graph nodes are associated with data-flow nodes in the current codebase.
|
||||
* (Since external libraries are not part of the database, there is no way to associate with concrete
|
||||
* data-flow nodes from the external library).
|
||||
* - **Use-nodes** are associated with data-flow nodes where a value enters the current codebase,
|
||||
* such as the return value of a call to an external function.
|
||||
* - **Def-nodes** are associated with data-flow nodes where a value leaves the current codebase,
|
||||
* such as an argument passed in a call to an external function.
|
||||
*
|
||||
*
|
||||
* ### Access paths and edge labels
|
||||
*
|
||||
* Nodes in the API graph are associated with a set of access paths, describing a series of operations
|
||||
* that may be performed to obtain that value.
|
||||
*
|
||||
* For example, the access path `API::moduleImport("lodash").getMember("extend")` represents the action of
|
||||
* importing `lodash` and then accessing the member `extend` on the resulting object.
|
||||
* It would be associated with an expression such as `require("lodash").extend`.
|
||||
*
|
||||
* Each edge in the graph is labelled by such an "operation". For an edge `A->B`, the type of the `A` node
|
||||
* determines who is performing the operation, and the type of the `B` node determines who ends up holding
|
||||
* the result:
|
||||
* - An edge starting from a use-node describes what the current codebase is doing to a value that
|
||||
* came from a library.
|
||||
* - An edge starting from a def-node describes what the external library might do to a value that
|
||||
* came from the current codebase.
|
||||
* - An edge ending in a use-node means the result ends up in the current codebase (at its associated data-flow node).
|
||||
* - An edge ending in a def-node means the result ends up in external code (its associated data-flow node is
|
||||
* the place where it was "last seen" in the current codebase before flowing out)
|
||||
*
|
||||
* Because the implementation of the external library is not visible, it is not known exactly what operations
|
||||
* it will perform on values that flow there. Instead, the edges starting from a def-node are operations that would
|
||||
* lead to an observable effect within the current codebase; without knowing for certain if the library will actually perform
|
||||
* those operations. (When constructing these edges, we assume the library is somewhat well-behaved).
|
||||
*
|
||||
* For example, given this snippet:
|
||||
* ```js
|
||||
* require('foo')(x => { doSomething(x) })
|
||||
* ```
|
||||
* A callback is passed to the external function `foo`. We can't know if `foo` will actually invoke this callback.
|
||||
* But _if_ the library should decide to invoke the callback, then a value will flow into the current codebase via the `x` parameter.
|
||||
* For that reason, an edge is generated representing the argument-passing operation that might be performed by `foo`.
|
||||
* This edge is going from the def-node associated with the callback to the use-node associated with the parameter `x`.
|
||||
*
|
||||
* ### Thinking in operations versus code patterns
|
||||
*
|
||||
* Treating edges as "operations" helps avoid a pitfall in which library models become overly specific to certain code patterns.
|
||||
* Consider the following two equivalent calls to `foo`:
|
||||
* ```js
|
||||
* const foo = require('foo');
|
||||
*
|
||||
* foo({
|
||||
* myMethod(x) {...}
|
||||
* });
|
||||
*
|
||||
* foo({
|
||||
* get myMethod() {
|
||||
* return function(x) {...}
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
* If `foo` calls `myMethod` on its first parameter, either of the `myMethod` implementations will be invoked.
|
||||
* And indeed, the access path `API::moduleImport("foo").getParameter(0).getMember("myMethod").getParameter(0)` correctly
|
||||
* identifies both `x` parameters.
|
||||
*
|
||||
* Observe how `getMember("myMethod")` behaves when the member is defined via a getter. When thinking in code patterns,
|
||||
* it might seem obvious that `getMember` should have obtained a reference to the getter method itself.
|
||||
* But when seeing it as an access to `myMethod` performed by the library, we can deduce that the relevant expression
|
||||
* on the client side is actually the return-value of the getter.
|
||||
*
|
||||
* Although one may think of API graphs as a tool to find certain program elements in the codebase,
|
||||
* it can lead to some situations where intuition does not match what works best in practice.
|
||||
*/
|
||||
class Node extends Impl::TApiNode {
|
||||
/**
|
||||
* Gets a data-flow node corresponding to a use of the API component represented by this node.
|
||||
* Get a data-flow node where this value may flow after entering the current codebase.
|
||||
*
|
||||
* For example, `require('fs').readFileSync` is a use of the function `readFileSync` from the
|
||||
* `fs` module, and `require('fs').readFileSync(file)` is a use of the return of that function.
|
||||
*
|
||||
* This includes indirect uses found via data flow, meaning that in
|
||||
* `f(obj.foo); function f(x) {};` both `obj.foo` and `x` are uses of the `foo` member from `obj`.
|
||||
*
|
||||
* As another example, in the assignment `exports.plusOne = (x) => x+1` the two references to
|
||||
* `x` are uses of the first parameter of `plusOne`.
|
||||
* This is similar to `asSource()` but additionally includes nodes that are transitively reachable by data flow.
|
||||
* See `asSource()` for examples.
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::Node getAUse() {
|
||||
exists(DataFlow::SourceNode src | Impl::use(this, src) |
|
||||
Impl::trackUseNode(src).flowsTo(result)
|
||||
)
|
||||
DataFlow::Node getAValueReachableFromSource() {
|
||||
Impl::trackUseNode(this.asSource()).flowsTo(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an immediate use of the API component represented by this node.
|
||||
* Get a data-flow node where this value enters the current codebase.
|
||||
*
|
||||
* For example, `require('fs').readFileSync` is a an immediate use of the `readFileSync` member
|
||||
* from the `fs` module.
|
||||
* For example:
|
||||
* ```js
|
||||
* // API::moduleImport("fs").asSource()
|
||||
* require('fs');
|
||||
*
|
||||
* Unlike `getAUse()`, this predicate only gets the immediate references, not the indirect uses
|
||||
* found via data flow. This means that in `const x = fs.readFile` only `fs.readFile` is a reference
|
||||
* to the `readFile` member of `fs`, neither `x` nor any node that `x` flows to is a reference to
|
||||
* this API component.
|
||||
* // API::moduleImport("fs").getMember("readFile").asSource()
|
||||
* require('fs').readFile;
|
||||
*
|
||||
* // API::moduleImport("fs").getMember("readFile").getReturn().asSource()
|
||||
* require('fs').readFile();
|
||||
*
|
||||
* require('fs').readFile(
|
||||
* filename,
|
||||
* // 'y' matched by API::moduleImport("fs").getMember("readFile").getParameter(1).getParameter(0).asSource()
|
||||
* y => {
|
||||
* ...
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
DataFlow::SourceNode getAnImmediateUse() { Impl::use(this, result) }
|
||||
DataFlow::SourceNode asSource() { Impl::use(this, result) }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `asSource`. */
|
||||
deprecated DataFlow::SourceNode getAnImmediateUse() { result = this.asSource() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `getAValueReachableFromSource`. */
|
||||
deprecated DataFlow::Node getAUse() { result = this.getAValueReachableFromSource() }
|
||||
|
||||
/**
|
||||
* Gets a call to the function represented by this API component.
|
||||
*/
|
||||
CallNode getACall() { result = this.getReturn().getAnImmediateUse() }
|
||||
CallNode getACall() { result = this.getReturn().asSource() }
|
||||
|
||||
/**
|
||||
* Gets a call to the function represented by this API component,
|
||||
@@ -72,7 +177,7 @@ module API {
|
||||
/**
|
||||
* Gets a `new` call to the function represented by this API component.
|
||||
*/
|
||||
NewNode getAnInstantiation() { result = this.getInstance().getAnImmediateUse() }
|
||||
NewNode getAnInstantiation() { result = this.getInstance().asSource() }
|
||||
|
||||
/**
|
||||
* Gets an invocation (with our without `new`) to the function represented by this API component.
|
||||
@@ -80,26 +185,38 @@ module API {
|
||||
InvokeNode getAnInvocation() { result = this.getACall() or result = this.getAnInstantiation() }
|
||||
|
||||
/**
|
||||
* Gets a data-flow node corresponding to the right-hand side of a definition of the API
|
||||
* component represented by this node.
|
||||
* Get a data-flow node where this value leaves the current codebase and flows into an
|
||||
* external library (or in general, any external codebase).
|
||||
*
|
||||
* For example, in the assignment `exports.plusOne = (x) => x+1`, the function expression
|
||||
* `(x) => x+1` is the right-hand side of the definition of the member `plusOne` of
|
||||
* the enclosing module, and the expression `x+1` is the right-had side of the definition of
|
||||
* its result.
|
||||
* Concretely, this is either an argument passed to a call to external code,
|
||||
* or the right-hand side of a property write on an object flowing into such a call.
|
||||
*
|
||||
* Note that for parameters, it is the arguments flowing into that parameter that count as
|
||||
* right-hand sides of the definition, not the declaration of the parameter itself.
|
||||
* Consequently, in `require('fs').readFileSync(file)`, `file` is the right-hand
|
||||
* side of a definition of the first parameter of `readFileSync` from the `fs` module.
|
||||
* For example:
|
||||
* ```js
|
||||
* // 'x' is matched by API::moduleImport("foo").getParameter(0).asSink()
|
||||
* require('foo')(x);
|
||||
*
|
||||
* // 'x' is matched by API::moduleImport("foo").getParameter(0).getMember("prop").asSink()
|
||||
* require('foo')({
|
||||
* prop: x
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
DataFlow::Node getARhs() { Impl::rhs(this, result) }
|
||||
DataFlow::Node asSink() { Impl::rhs(this, result) }
|
||||
|
||||
/**
|
||||
* Gets a data-flow node that may interprocedurally flow to the right-hand side of a definition
|
||||
* of the API component represented by this node.
|
||||
* Get a data-flow node that transitively flows to an external library (or in general, any external codebase).
|
||||
*
|
||||
* This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow.
|
||||
* See `asSink()` for examples.
|
||||
*/
|
||||
DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.getARhs()) }
|
||||
DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `asSink`. */
|
||||
deprecated DataFlow::Node getARhs() { result = this.asSink() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `getAValueReachingSink`. */
|
||||
deprecated DataFlow::Node getAValueReachingRhs() { result = this.getAValueReachingSink() }
|
||||
|
||||
/**
|
||||
* Gets a node representing member `m` of this API component.
|
||||
@@ -334,7 +451,7 @@ module API {
|
||||
* In other words, the value of a use of `that` may flow into the right-hand side of a
|
||||
* definition of this node.
|
||||
*/
|
||||
predicate refersTo(Node that) { this.getARhs() = that.getAUse() }
|
||||
predicate refersTo(Node that) { this.asSink() = that.getAValueReachableFromSource() }
|
||||
|
||||
/**
|
||||
* Gets the data-flow node that gives rise to this node, if any.
|
||||
@@ -445,11 +562,17 @@ module API {
|
||||
bindingset[this]
|
||||
EntryPoint() { any() }
|
||||
|
||||
/** Gets a data-flow node that uses this entry point. */
|
||||
abstract DataFlow::SourceNode getAUse();
|
||||
/** DEPRECATED. This predicate has been renamed to `getASource`. */
|
||||
deprecated DataFlow::SourceNode getAUse() { none() }
|
||||
|
||||
/** Gets a data-flow node that defines this entry point. */
|
||||
abstract DataFlow::Node getARhs();
|
||||
/** DEPRECATED. This predicate has been renamed to `getASink`. */
|
||||
deprecated DataFlow::SourceNode getARhs() { none() }
|
||||
|
||||
/** Gets a data-flow node where a value enters the current codebase through this entry-point. */
|
||||
DataFlow::SourceNode getASource() { none() }
|
||||
|
||||
/** Gets a data-flow node where a value leaves the current codebase through this entry-point. */
|
||||
DataFlow::Node getASink() { none() }
|
||||
|
||||
/** Gets an API-node for this entry point. */
|
||||
API::Node getANode() { result = root().getASuccessor(Label::entryPoint(this)) }
|
||||
@@ -567,7 +690,7 @@ module API {
|
||||
base = MkRoot() and
|
||||
exists(EntryPoint e |
|
||||
lbl = Label::entryPoint(e) and
|
||||
rhs = e.getARhs()
|
||||
rhs = e.getASink()
|
||||
)
|
||||
or
|
||||
exists(string m, string prop |
|
||||
@@ -604,12 +727,36 @@ module API {
|
||||
or
|
||||
lbl = Label::promisedError() and
|
||||
PromiseFlow::storeStep(rhs, pred, Promises::errorProp())
|
||||
or
|
||||
// The return-value of a getter G counts as a definition of property G
|
||||
// (Ordinary methods and properties are handled as PropWrite nodes)
|
||||
exists(string name | lbl = Label::member(name) |
|
||||
rhs = pred.(DataFlow::ObjectLiteralNode).getPropertyGetter(name).getAReturn()
|
||||
or
|
||||
rhs =
|
||||
pred.(DataFlow::ClassNode)
|
||||
.getStaticMember(name, DataFlow::MemberKind::getter())
|
||||
.getAReturn()
|
||||
)
|
||||
or
|
||||
// If `new C()` escapes, generate edges to its instance members
|
||||
exists(DataFlow::ClassNode cls, string name |
|
||||
pred = cls.getAClassReference().getAnInstantiation() and
|
||||
lbl = Label::member(name)
|
||||
|
|
||||
rhs = cls.getInstanceMethod(name)
|
||||
or
|
||||
rhs = cls.getInstanceMember(name, DataFlow::MemberKind::getter()).getAReturn()
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ClassNode cls, string name |
|
||||
base = MkClassInstance(cls) and
|
||||
lbl = Label::member(name) and
|
||||
lbl = Label::member(name)
|
||||
|
|
||||
rhs = cls.getInstanceMethod(name)
|
||||
or
|
||||
rhs = cls.getInstanceMember(name, DataFlow::MemberKind::getter()).getAReturn()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::FunctionNode f |
|
||||
@@ -720,7 +867,7 @@ module API {
|
||||
base = MkRoot() and
|
||||
exists(EntryPoint e |
|
||||
lbl = Label::entryPoint(e) and
|
||||
ref = e.getAUse()
|
||||
ref = e.getASource()
|
||||
)
|
||||
or
|
||||
// property reads
|
||||
@@ -1154,8 +1301,8 @@ module API {
|
||||
API::Node callee;
|
||||
|
||||
InvokeNode() {
|
||||
this = callee.getReturn().getAnImmediateUse() or
|
||||
this = callee.getInstance().getAnImmediateUse() or
|
||||
this = callee.getReturn().asSource() or
|
||||
this = callee.getInstance().asSource() or
|
||||
this = Impl::getAPromisifiedInvocation(callee, _, _)
|
||||
}
|
||||
|
||||
@@ -1170,7 +1317,7 @@ module API {
|
||||
* Gets an API node where a RHS of the node is the `i`th argument to this call.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private Node getAParameterCandidate(int i) { result.getARhs() = this.getArgument(i) }
|
||||
private Node getAParameterCandidate(int i) { result.asSink() = this.getArgument(i) }
|
||||
|
||||
/** Gets the API node for a parameter of this invocation. */
|
||||
Node getAParameter() { result = this.getParameter(_) }
|
||||
@@ -1181,13 +1328,13 @@ module API {
|
||||
/** Gets the API node for the return value of this call. */
|
||||
Node getReturn() {
|
||||
result = callee.getReturn() and
|
||||
result.getAnImmediateUse() = this
|
||||
result.asSource() = this
|
||||
}
|
||||
|
||||
/** Gets the API node for the object constructed by this invocation. */
|
||||
Node getInstance() {
|
||||
result = callee.getInstance() and
|
||||
result.getAnImmediateUse() = this
|
||||
result.asSource() = this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ module ArrayTaintTracking {
|
||||
succ.(DataFlow::SourceNode).getAMethodCall("splice") = call
|
||||
or
|
||||
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
|
||||
call.(DataFlow::MethodCallNode).calls(pred, ["pop", "shift", "slice", "splice"]) and
|
||||
call.(DataFlow::MethodCallNode).calls(pred, ["pop", "shift", "slice", "splice", "at"]) and
|
||||
succ = call
|
||||
or
|
||||
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
|
||||
@@ -199,13 +199,13 @@ private module ArrayDataFlow {
|
||||
}
|
||||
|
||||
/**
|
||||
* A step for retrieving an element from an array using `.pop()` or `.shift()`.
|
||||
* A step for retrieving an element from an array using `.pop()`, `.shift()`, or `.at()`.
|
||||
* E.g. `array.pop()`.
|
||||
*/
|
||||
private class ArrayPopStep extends DataFlow::SharedFlowStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = ["pop", "shift"] and
|
||||
call.getMethodName() = ["pop", "shift", "at"] and
|
||||
prop = arrayElement() and
|
||||
obj = call.getReceiver() and
|
||||
element = call
|
||||
|
||||
@@ -68,7 +68,7 @@ private predicate hasDefaultExport(ES2015Module mod) {
|
||||
* Holds if `mod` contains both named and `default` exports.
|
||||
*
|
||||
* This is used to determine whether a default-import of the module should be reinterpreted
|
||||
* as a namespace-import, to accomodate the non-standard behavior implemented by some compilers.
|
||||
* as a namespace-import, to accommodate the non-standard behavior implemented by some compilers.
|
||||
*/
|
||||
private predicate hasBothNamedAndDefaultExports(ES2015Module mod) {
|
||||
hasNamedExports(mod) and
|
||||
@@ -615,7 +615,7 @@ class ReExportDefaultSpecifier extends ExportDefaultSpecifier {
|
||||
}
|
||||
|
||||
/**
|
||||
* A namespace export specifier, that is `*` or `* as x` occuring in an export declaration.
|
||||
* A namespace export specifier, that is `*` or `* as x` occurring in an export declaration.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
|
||||
@@ -2286,9 +2286,7 @@ class ComprehensionExpr extends @comprehension_expr, Expr {
|
||||
|
||||
/** Holds if this is a legacy postfix comprehension expression. */
|
||||
predicate isPostfix() {
|
||||
exists(Token tk | tk = this.getFirstToken().getNextToken() |
|
||||
not tk.getValue().regexpMatch("if|for")
|
||||
)
|
||||
exists(Token tk | tk = this.getFirstToken().getNextToken() | not tk.getValue() = ["if", "for"])
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ComprehensionExpr" }
|
||||
@@ -2904,7 +2902,7 @@ class ImportMetaExpr extends @import_meta_expr, Expr {
|
||||
* let data2 = {{{ user_data2 }}};
|
||||
* ```
|
||||
*
|
||||
* Note that templating placeholders occuring inside strings literals are not parsed,
|
||||
* Note that templating placeholders occurring inside strings literals are not parsed,
|
||||
* and are simply seen as being part of the string literal.
|
||||
* For example, following snippet does not contain any `GeneratedCodeExpr` nodes:
|
||||
* ```js
|
||||
|
||||
@@ -175,6 +175,15 @@ class Folder extends Container, @folder {
|
||||
result.getExtension() = extension
|
||||
}
|
||||
|
||||
/** Like `getFile` except `d.ts` is treated as a single extension. */
|
||||
private File getFileLongExtension(string stem, string extension) {
|
||||
not (stem.matches("%.d") and extension = "ts") and
|
||||
result = this.getFile(stem, extension)
|
||||
or
|
||||
extension = "d.ts" and
|
||||
result = this.getFile(stem + ".d", "ts")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file in this folder that has the given `stem` and any of the supported JavaScript extensions.
|
||||
*
|
||||
@@ -188,7 +197,11 @@ class Folder extends Container, @folder {
|
||||
*/
|
||||
File getJavaScriptFile(string stem) {
|
||||
result =
|
||||
min(int p, string ext | p = getFileExtensionPriority(ext) | this.getFile(stem, ext) order by p)
|
||||
min(int p, string ext |
|
||||
p = getFileExtensionPriority(ext)
|
||||
|
|
||||
this.getFileLongExtension(stem, ext) order by p
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a subfolder contained in this folder. */
|
||||
|
||||
@@ -420,7 +420,7 @@ module AccessPath {
|
||||
*/
|
||||
module DominatingPaths {
|
||||
/**
|
||||
* A classification of acccess paths into reads and writes.
|
||||
* A classification of access paths into reads and writes.
|
||||
*/
|
||||
private newtype AccessPathKind =
|
||||
AccessPathRead() or
|
||||
|
||||
@@ -29,7 +29,7 @@ private class PlainJsonParserCall extends JsonParserCall {
|
||||
callee =
|
||||
DataFlow::moduleMember(["json3", "json5", "flatted", "teleport-javascript", "json-cycle"],
|
||||
"parse") or
|
||||
callee = API::moduleImport("replicator").getInstance().getMember("decode").getAnImmediateUse() or
|
||||
callee = API::moduleImport("replicator").getInstance().getMember("decode").asSource() or
|
||||
callee = DataFlow::moduleImport("parse-json") or
|
||||
callee = DataFlow::moduleImport("json-parse-better-errors") or
|
||||
callee = DataFlow::moduleImport("json-safe-parse") or
|
||||
|
||||
@@ -134,7 +134,7 @@ module JsonSchema {
|
||||
.ref()
|
||||
.getMember(["addSchema", "validate", "compile", "compileAsync"])
|
||||
.getParameter(0)
|
||||
.getARhs()
|
||||
.asSink()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,7 @@ module JsonSchema {
|
||||
override boolean getPolarity() { none() }
|
||||
|
||||
override DataFlow::Node getAValidationResultAccess(boolean polarity) {
|
||||
result = this.getReturn().getMember("error").getAnImmediateUse() and
|
||||
result = this.getReturn().getMember("error").asSource() and
|
||||
polarity = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class JsonStringifyCall extends DataFlow::CallNode {
|
||||
callee =
|
||||
DataFlow::moduleMember(["json3", "json5", "flatted", "teleport-javascript", "json-cycle"],
|
||||
"stringify") or
|
||||
callee = API::moduleImport("replicator").getInstance().getMember("encode").getAnImmediateUse() or
|
||||
callee = API::moduleImport("replicator").getInstance().getMember("encode").asSource() or
|
||||
callee =
|
||||
DataFlow::moduleImport([
|
||||
"json-stringify-safe", "json-stable-stringify", "stringify-object",
|
||||
|
||||
@@ -229,10 +229,10 @@ module MembershipCandidate {
|
||||
membersNode = inExpr.getRightOperand()
|
||||
)
|
||||
or
|
||||
exists(MethodCallExpr hasOwn |
|
||||
this = hasOwn.getArgument(0).flow() and
|
||||
test = hasOwn and
|
||||
hasOwn.calls(membersNode, "hasOwnProperty")
|
||||
exists(HasOwnPropertyCall hasOwn |
|
||||
this = hasOwn.getProperty() and
|
||||
test = hasOwn.asExpr() and
|
||||
membersNode = hasOwn.getObject().asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -180,6 +180,35 @@ class PackageJson extends JsonObject {
|
||||
Module getMainModule() {
|
||||
result = min(Module m, int prio | m.getFile() = resolveMainModule(this, prio) | m order by prio)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `types` or `typings` field of this package.
|
||||
*/
|
||||
string getTypings() { result = this.getPropStringValue(["types", "typings"]) }
|
||||
|
||||
/**
|
||||
* Gets the file containing the typings of this package, which can either be from the `types` or
|
||||
* `typings` field, or derived from the `main` or `module` fields.
|
||||
*/
|
||||
File getTypingsFile() {
|
||||
result =
|
||||
TypingsModulePathString::of(this).resolve(this.getFile().getParentContainer()).getContainer()
|
||||
or
|
||||
not exists(TypingsModulePathString::of(this)) and
|
||||
exists(File mainFile |
|
||||
mainFile = this.getMainModule().getFile() and
|
||||
result =
|
||||
mainFile
|
||||
.getParentContainer()
|
||||
.getFile(mainFile.getStem().regexpReplaceAll("\\.d$", "") + ".d.ts")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the module containing the typings of this package, which can either be from the `types` or
|
||||
* `typings` field, or derived from the `main` or `module` fields.
|
||||
*/
|
||||
Module getTypingsModule() { result.getFile() = this.getTypingsFile() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for PackageJson */
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import javascript
|
||||
private import NodeModuleResolutionImpl
|
||||
private import semmle.javascript.DynamicPropertyAccess as DynamicPropertyAccess
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
|
||||
/**
|
||||
* A Node.js module.
|
||||
@@ -113,6 +114,7 @@ class NodeModule extends Module {
|
||||
}
|
||||
|
||||
override DataFlow::Node getABulkExportedNode() {
|
||||
Stages::Imports::ref() and
|
||||
exists(DataFlow::PropWrite write |
|
||||
write.getBase().asExpr() = this.getModuleVariable().getAnAccess() and
|
||||
write.getPropertyName() = "exports" and
|
||||
|
||||
@@ -33,6 +33,8 @@ int getFileExtensionPriority(string ext) {
|
||||
ext = "json" and result = 8
|
||||
or
|
||||
ext = "node" and result = 9
|
||||
or
|
||||
ext = "d.ts" and result = 10
|
||||
}
|
||||
|
||||
int prioritiesPerCandidate() { result = 3 * (numberOfExtensions() + 1) }
|
||||
@@ -196,3 +198,29 @@ private class FilesPath extends PathExpr, @json_string {
|
||||
private module FilesPath {
|
||||
FilesPath of(PackageJson pkg) { result.getPackageJson() = pkg }
|
||||
}
|
||||
|
||||
/**
|
||||
* A JSON string in a `package.json` file specifying the path of the
|
||||
* TypeScript typings entry point.
|
||||
*/
|
||||
class TypingsModulePathString extends PathString {
|
||||
PackageJson pkg;
|
||||
|
||||
TypingsModulePathString() {
|
||||
this = pkg.getTypings()
|
||||
or
|
||||
not exists(pkg.getTypings()) and
|
||||
this = pkg.getMain().regexpReplaceAll("\\.[mc]?js$", ".d.ts")
|
||||
}
|
||||
|
||||
/** Gets the `package.json` file containing this path. */
|
||||
PackageJson getPackageJson() { result = pkg }
|
||||
|
||||
override Folder getARootFolder() { result = pkg.getFile().getParentContainer() }
|
||||
}
|
||||
|
||||
/** Companion module to the `TypingsModulePathString` class. */
|
||||
module TypingsModulePathString {
|
||||
/** Get the typings path for the given `package.json` file. */
|
||||
TypingsModulePathString of(PackageJson pkg) { result.getPackageJson() = pkg }
|
||||
}
|
||||
|
||||
@@ -156,17 +156,22 @@ private DataFlow::Node getAValueExportedByPackage() {
|
||||
result = unique( | | call.getCalleeNode().getAFunctionValue()).getAReturn()
|
||||
)
|
||||
or
|
||||
// the exported value is a function that returns another import.
|
||||
// ```JavaScript
|
||||
// module.exports = function foo() {
|
||||
// return require("./other-module.js");
|
||||
// }
|
||||
// ```
|
||||
exists(DataFlow::FunctionNode func, Module mod |
|
||||
exists(DataFlow::FunctionNode func |
|
||||
func = getAValueExportedByPackage().getABoundFunctionValue(_)
|
||||
|
|
||||
mod = func.getAReturn().getALocalSource().getEnclosingExpr().(Import).getImportedModule() and
|
||||
result = getAnExportFromModule(mod)
|
||||
// the exported value is a function that returns another import.
|
||||
// ```JavaScript
|
||||
// module.exports = function foo() {
|
||||
// return require("./other-module.js");
|
||||
// }
|
||||
// ```
|
||||
exists(Module mod |
|
||||
mod = func.getAReturn().getALocalSource().getEnclosingExpr().(Import).getImportedModule() and
|
||||
result = getAnExportFromModule(mod)
|
||||
)
|
||||
or
|
||||
// a function that returns an object of methods. This acts a bit like a class.
|
||||
result = func.getAReturn().getALocalSource().getAPropertySource().(DataFlow::FunctionNode)
|
||||
)
|
||||
or
|
||||
// *****
|
||||
|
||||
@@ -193,7 +193,7 @@ private module PrintJavaScript {
|
||||
|
||||
/**
|
||||
* Gets the `i`th child of `element`.
|
||||
* Can be overriden in subclasses to get more specific behavior for `getChild()`.
|
||||
* Can be overridden in subclasses to get more specific behavior for `getChild()`.
|
||||
*/
|
||||
AstNode getChildNode(int childIndex) { result = getLocationSortedChild(element, childIndex) }
|
||||
}
|
||||
|
||||
@@ -189,6 +189,13 @@ module Promises {
|
||||
* Gets the pseudo-field used to describe rejected values in a promise.
|
||||
*/
|
||||
string errorProp() { result = "$PromiseRejectField$" }
|
||||
|
||||
/** A property set containing the pseudo-properites of a promise object. */
|
||||
class PromiseProps extends DataFlow::PropertySet {
|
||||
PromiseProps() { this = "PromiseProps" }
|
||||
|
||||
override string getAProperty() { result = [valueProp(), errorProp()] }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,6 +281,24 @@ private class PromiseStep extends PreCallGraphStep {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A step from `p -> await p` for the case where `p` is not a promise.
|
||||
*
|
||||
* In this case, `await p` just returns `p` itself. We block flow of the promise-related
|
||||
* pseudo properties through this edge.
|
||||
*/
|
||||
private class RawAwaitStep extends DataFlow::SharedTypeTrackingStep {
|
||||
override predicate withoutPropStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::PropertySet props
|
||||
) {
|
||||
exists(AwaitExpr await |
|
||||
pred = await.getOperand().flow() and
|
||||
succ = await.flow() and
|
||||
props instanceof Promises::PromiseProps
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This module defines how data-flow propagates into and out of a Promise.
|
||||
* The data-flow is based on pseudo-properties rather than tainting the Promise object (which is what `PromiseTaintStep` does).
|
||||
|
||||
@@ -1309,7 +1309,7 @@ module RegExp {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `term` can match any occurence of `char` within a string (not taking into account
|
||||
* Holds if `term` can match any occurrence of `char` within a string (not taking into account
|
||||
* the context in which `term` appears).
|
||||
*
|
||||
* This predicate is under-approximate and never considers sequences to guarantee a match.
|
||||
|
||||
@@ -192,3 +192,35 @@ class StringSplitCall extends DataFlow::MethodCallNode {
|
||||
bindingset[i]
|
||||
DataFlow::Node getASubstringRead(int i) { result = this.getAPropertyRead(i.toString()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `Object.prototype.hasOwnProperty`, `Object.hasOwn`, or a library that implements
|
||||
* the same functionality.
|
||||
*/
|
||||
class HasOwnPropertyCall extends DataFlow::Node instanceof DataFlow::CallNode {
|
||||
DataFlow::Node object;
|
||||
DataFlow::Node property;
|
||||
|
||||
HasOwnPropertyCall() {
|
||||
// Make sure we handle reflective calls since libraries love to do that.
|
||||
super.getCalleeNode().getALocalSource().(DataFlow::PropRead).getPropertyName() =
|
||||
"hasOwnProperty" and
|
||||
object = super.getReceiver() and
|
||||
property = super.getArgument(0)
|
||||
or
|
||||
this =
|
||||
[
|
||||
DataFlow::globalVarRef("Object").getAMemberCall("hasOwn"), //
|
||||
DataFlow::moduleImport("has").getACall(), //
|
||||
LodashUnderscore::member("has").getACall()
|
||||
] and
|
||||
object = super.getArgument(0) and
|
||||
property = super.getArgument(1)
|
||||
}
|
||||
|
||||
/** Gets the object whose property is being checked. */
|
||||
DataFlow::Node getObject() { result = object }
|
||||
|
||||
/** Gets the property being checked. */
|
||||
DataFlow::Node getProperty() { result = property }
|
||||
}
|
||||
|
||||
@@ -896,21 +896,28 @@ class ArrayTypeExpr extends @array_typeexpr, TypeExpr {
|
||||
override string getAPrimaryQlClass() { result = "ArrayTypeExpr" }
|
||||
}
|
||||
|
||||
private class RawUnionOrIntersectionTypeExpr = @union_typeexpr or @intersection_typeexpr;
|
||||
|
||||
/**
|
||||
* A union type, such as `string|number|boolean`.
|
||||
* A union or intersection type, such as `string|number|boolean` or `A & B`.
|
||||
*/
|
||||
class UnionTypeExpr extends @union_typeexpr, TypeExpr {
|
||||
/** Gets the `n`th type in the union, starting at 0. */
|
||||
class UnionOrIntersectionTypeExpr extends RawUnionOrIntersectionTypeExpr, TypeExpr {
|
||||
/** Gets the `n`th type in the union or intersection, starting at 0. */
|
||||
TypeExpr getElementType(int n) { result = this.getChildTypeExpr(n) }
|
||||
|
||||
/** Gets any of the types in the union. */
|
||||
/** Gets any of the types in the union or intersection. */
|
||||
TypeExpr getAnElementType() { result = this.getElementType(_) }
|
||||
|
||||
/** Gets the number of types in the union. This is always at least two. */
|
||||
/** Gets the number of types in the union or intersection. This is always at least two. */
|
||||
int getNumElementType() { result = count(this.getAnElementType()) }
|
||||
|
||||
override TypeExpr getAnUnderlyingType() { result = this.getAnElementType().getAnUnderlyingType() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A union type, such as `string|number|boolean`.
|
||||
*/
|
||||
class UnionTypeExpr extends @union_typeexpr, UnionOrIntersectionTypeExpr {
|
||||
override string getAPrimaryQlClass() { result = "UnionTypeExpr" }
|
||||
}
|
||||
|
||||
@@ -932,18 +939,7 @@ class IndexedAccessTypeExpr extends @indexed_access_typeexpr, TypeExpr {
|
||||
*
|
||||
* In general, there are can more than two operands to an intersection type.
|
||||
*/
|
||||
class IntersectionTypeExpr extends @intersection_typeexpr, TypeExpr {
|
||||
/** Gets the `n`th operand of the intersection type, starting at 0. */
|
||||
TypeExpr getElementType(int n) { result = this.getChildTypeExpr(n) }
|
||||
|
||||
/** Gets any of the operands to the intersection type. */
|
||||
TypeExpr getAnElementType() { result = this.getElementType(_) }
|
||||
|
||||
/** Gets the number of operands to the intersection type. This is always at least two. */
|
||||
int getNumElementType() { result = count(this.getAnElementType()) }
|
||||
|
||||
override TypeExpr getAnUnderlyingType() { result = this.getAnElementType().getAnUnderlyingType() }
|
||||
|
||||
class IntersectionTypeExpr extends @intersection_typeexpr, UnionOrIntersectionTypeExpr {
|
||||
override string getAPrimaryQlClass() { result = "IntersectionTypeExpr" }
|
||||
}
|
||||
|
||||
@@ -1286,6 +1282,8 @@ class ExpressionWithTypeArguments extends @expression_with_type_arguments, Expr
|
||||
override ControlFlowNode getFirstControlFlowNode() {
|
||||
result = this.getExpression().getFirstControlFlowNode()
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "ExpressionWithTypeArguments" }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -226,7 +226,7 @@ class ArgumentsVariable extends Variable {
|
||||
*/
|
||||
class VarRef extends @varref, Identifier, BindingPattern, LexicalRef {
|
||||
/** Gets the variable this identifier refers to. */
|
||||
override Variable getVariable() { none() } // Overriden in VarAccess and VarDecl
|
||||
override Variable getVariable() { none() } // Overridden in VarAccess and VarDecl
|
||||
|
||||
override string getName() { result = Identifier.super.getName() }
|
||||
|
||||
@@ -873,6 +873,18 @@ class DeclarationSpace extends string {
|
||||
DeclarationSpace() { this = "variable" or this = "type" or this = "namespace" }
|
||||
}
|
||||
|
||||
/** Module containing the `DeclarationSpace` constants. */
|
||||
module DeclarationSpace {
|
||||
/** Gets the declaration space for variables/values. */
|
||||
DeclarationSpace variable() { result = "variable" }
|
||||
|
||||
/** Gets the declaration space for types. */
|
||||
DeclarationSpace type() { result = "type" }
|
||||
|
||||
/** Gets the declaration space for namespaces. */
|
||||
DeclarationSpace namespace() { result = "namespace" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A name that is declared in a particular scope.
|
||||
*
|
||||
|
||||
@@ -87,7 +87,7 @@ module TaintTracking {
|
||||
override predicate isLabeledBarrier(DataFlow::Node node, DataFlow::FlowLabel lbl) {
|
||||
super.isLabeledBarrier(node, lbl)
|
||||
or
|
||||
isSanitizer(node) and lbl.isTaint()
|
||||
this.isSanitizer(node) and lbl.isTaint()
|
||||
}
|
||||
|
||||
override predicate isBarrier(DataFlow::Node node) {
|
||||
@@ -103,15 +103,15 @@ module TaintTracking {
|
||||
) {
|
||||
super.isBarrierEdge(source, sink, lbl)
|
||||
or
|
||||
isSanitizerEdge(source, sink, lbl)
|
||||
this.isSanitizerEdge(source, sink, lbl)
|
||||
or
|
||||
isSanitizerEdge(source, sink) and lbl.isTaint()
|
||||
this.isSanitizerEdge(source, sink) and lbl.isTaint()
|
||||
}
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuardNode guard) {
|
||||
super.isBarrierGuard(guard) or
|
||||
guard.(AdditionalSanitizerGuardNode).appliesTo(this) or
|
||||
isSanitizerGuard(guard)
|
||||
this.isSanitizerGuard(guard)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,14 +121,14 @@ module TaintTracking {
|
||||
predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
final override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
isAdditionalTaintStep(pred, succ) or
|
||||
this.isAdditionalTaintStep(pred, succ) or
|
||||
sharedTaintStep(pred, succ)
|
||||
}
|
||||
|
||||
final override predicate isAdditionalFlowStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, boolean valuePreserving
|
||||
) {
|
||||
isAdditionalFlowStep(pred, succ) and valuePreserving = false
|
||||
this.isAdditionalFlowStep(pred, succ) and valuePreserving = false
|
||||
}
|
||||
|
||||
override DataFlow::FlowLabel getDefaultSourceLabel() { result.isTaint() }
|
||||
@@ -173,9 +173,9 @@ module TaintTracking {
|
||||
abstract predicate sanitizes(boolean outcome, Expr e);
|
||||
|
||||
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
|
||||
sanitizes(outcome, e) and label.isTaint()
|
||||
this.sanitizes(outcome, e) and label.isTaint()
|
||||
or
|
||||
sanitizes(outcome, e, label)
|
||||
this.sanitizes(outcome, e, label)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1027,18 +1027,16 @@ module TaintTracking {
|
||||
class WhitelistContainmentCallSanitizer extends AdditionalSanitizerGuardNode,
|
||||
DataFlow::MethodCallNode {
|
||||
WhitelistContainmentCallSanitizer() {
|
||||
exists(string name |
|
||||
name = "contains" or
|
||||
name = "has" or
|
||||
name = "hasOwnProperty"
|
||||
|
|
||||
getMethodName() = name
|
||||
)
|
||||
this.getMethodName() = ["contains", "has", "hasOwnProperty", "hasOwn"]
|
||||
}
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) {
|
||||
outcome = true and
|
||||
e = getArgument(0).asExpr()
|
||||
exists(int propertyIndex |
|
||||
if this.getMethodName() = "hasOwn" then propertyIndex = 1 else propertyIndex = 0
|
||||
|
|
||||
outcome = true and
|
||||
e = this.getArgument(propertyIndex).asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate appliesTo(Configuration cfg) { any() }
|
||||
@@ -1053,14 +1051,14 @@ module TaintTracking {
|
||||
*/
|
||||
class AdHocWhitelistCheckSanitizer extends SanitizerGuardNode, DataFlow::CallNode {
|
||||
AdHocWhitelistCheckSanitizer() {
|
||||
getCalleeName()
|
||||
this.getCalleeName()
|
||||
.regexpMatch("(?i).*((?<!un)safe|whitelist|(?<!in)valid|allow|(?<!un)auth(?!or\\b)).*") and
|
||||
getNumArgument() = 1
|
||||
this.getNumArgument() = 1
|
||||
}
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) {
|
||||
outcome = true and
|
||||
e = getArgument(0).asExpr()
|
||||
e = this.getArgument(0).asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,12 @@ class TypeTracker extends TTypeTracker {
|
||||
step = LoadStep(prop) and result = MkTypeTracker(hasCall, "")
|
||||
or
|
||||
exists(string p | step = StoreStep(p) and prop = "" and result = MkTypeTracker(hasCall, p))
|
||||
or
|
||||
exists(PropertySet props |
|
||||
step = WithoutPropStep(props) and
|
||||
not prop = props.getAProperty() and
|
||||
result = this
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this summary. */
|
||||
@@ -373,6 +379,26 @@ class SharedTypeTrackingStep extends Unit {
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from `pred` to `succ` but block flow of `props` through here.
|
||||
*
|
||||
* This can be seen as taking a copy of the value in `pred` but without the properties in `props`.
|
||||
*/
|
||||
predicate withoutPropStep(DataFlow::Node pred, DataFlow::Node succ, PropertySet props) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A representative for a set of property names.
|
||||
*
|
||||
* Currently this is used to denote a set of properties in `withoutPropStep`.
|
||||
*/
|
||||
abstract class PropertySet extends string {
|
||||
bindingset[this]
|
||||
PropertySet() { any() }
|
||||
|
||||
/** Gets a property contained in this property set. */
|
||||
abstract string getAProperty();
|
||||
}
|
||||
|
||||
/** Provides access to the steps contributed by subclasses of `SharedTypeTrackingStep`. */
|
||||
@@ -413,6 +439,15 @@ module SharedTypeTrackingStep {
|
||||
) {
|
||||
any(SharedTypeTrackingStep s).loadStoreStep(pred, succ, loadProp, storeProp)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if type-tracking should step from `pred` to `succ` but block flow of `prop` through here.
|
||||
*
|
||||
* This can be seen as taking a copy of the value in `pred` but without the properties in `props`.
|
||||
*/
|
||||
predicate withoutPropStep(DataFlow::Node pred, DataFlow::Node succ, PropertySet props) {
|
||||
any(SharedTypeTrackingStep s).withoutPropStep(pred, succ, props)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,8 @@ private module Cached {
|
||||
CopyStep(PropertyName prop) or
|
||||
LoadStoreStep(PropertyName fromProp, PropertyName toProp) {
|
||||
SharedTypeTrackingStep::loadStoreStep(_, _, fromProp, toProp)
|
||||
}
|
||||
} or
|
||||
WithoutPropStep(PropertySet props) { SharedTypeTrackingStep::withoutPropStep(_, _, props) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,6 +111,11 @@ private module Cached {
|
||||
summary = CopyStep(prop)
|
||||
)
|
||||
or
|
||||
exists(PropertySet props |
|
||||
SharedTypeTrackingStep::withoutPropStep(pred, succ, props) and
|
||||
summary = WithoutPropStep(props)
|
||||
)
|
||||
or
|
||||
exists(string fromProp, string toProp |
|
||||
SharedTypeTrackingStep::loadStoreStep(pred, succ, fromProp, toProp) and
|
||||
summary = LoadStoreStep(fromProp, toProp)
|
||||
@@ -194,6 +200,8 @@ class StepSummary extends TStepSummary {
|
||||
or
|
||||
exists(string prop | this = CopyStep(prop) | result = "copy " + prop)
|
||||
or
|
||||
exists(string prop | this = WithoutPropStep(prop) | result = "without " + prop)
|
||||
or
|
||||
exists(string fromProp, string toProp | this = LoadStoreStep(fromProp, toProp) |
|
||||
result = "load " + fromProp + " and store to " + toProp
|
||||
)
|
||||
|
||||
@@ -73,7 +73,10 @@ predicate isExternsFile(File f) {
|
||||
/**
|
||||
* Holds if `f` contains library code.
|
||||
*/
|
||||
predicate isLibaryFile(File f) { f.getATopLevel() instanceof FrameworkLibraryInstance }
|
||||
predicate isLibraryFile(File f) { f.getATopLevel() instanceof FrameworkLibraryInstance }
|
||||
|
||||
/** DEPRECATED: Alias for isLibraryFile */
|
||||
deprecated predicate isLibaryFile = isLibraryFile/1;
|
||||
|
||||
/**
|
||||
* Holds if `f` contains template code.
|
||||
@@ -106,7 +109,7 @@ predicate classify(File f, string category) {
|
||||
or
|
||||
isExternsFile(f) and category = "externs"
|
||||
or
|
||||
isLibaryFile(f) and category = "library"
|
||||
isLibraryFile(f) and category = "library"
|
||||
or
|
||||
isTemplateFile(f) and category = "template"
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ module Babel {
|
||||
.getMember(["transform", "transformSync", "transformAsync"])
|
||||
.getACall() and
|
||||
pred = call.getArgument(0) and
|
||||
succ = [call, call.getParameter(2).getParameter(0).getAnImmediateUse()]
|
||||
succ = [call, call.getParameter(2).getParameter(0).asSource()]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ module Cheerio {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `cheerio` function, possibly with a loaded DOM. */
|
||||
DataFlow::SourceNode cheerioRef() { result = cheerioApi().getAUse() }
|
||||
DataFlow::SourceNode cheerioRef() { result = cheerioApi().getAValueReachableFromSource() }
|
||||
|
||||
/**
|
||||
* A creation of `cheerio` object, a collection of virtual DOM elements
|
||||
|
||||
@@ -39,7 +39,8 @@ module ClassValidator {
|
||||
|
||||
/** Holds if the given field has a decorator that sanitizes its value for the purpose of taint tracking. */
|
||||
predicate isFieldSanitizedByDecorator(FieldDefinition field) {
|
||||
field.getADecorator().getExpression().flow() = sanitizingDecorator().getReturn().getAUse()
|
||||
field.getADecorator().getExpression().flow() =
|
||||
sanitizingDecorator().getReturn().getAValueReachableFromSource()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
|
||||
@@ -265,7 +265,7 @@ module ClientRequest {
|
||||
or
|
||||
responseType = this.getResponseType() and
|
||||
promise = false and
|
||||
result = this.getReturn().getPromisedError().getMember("response").getAnImmediateUse()
|
||||
result = this.getReturn().getPromisedError().getMember("response").asSource()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,8 +330,6 @@ module ClientRequest {
|
||||
* A model of a URL request made using `require("needle")(...)`.
|
||||
*/
|
||||
class PromisedNeedleRequest extends ClientRequest::Range {
|
||||
DataFlow::Node url;
|
||||
|
||||
PromisedNeedleRequest() { this = DataFlow::moduleImport("needle").getACall() }
|
||||
|
||||
override DataFlow::Node getUrl() { result = this.getArgument(1) }
|
||||
@@ -465,7 +463,7 @@ module ClientRequest {
|
||||
*/
|
||||
private API::Node netSocketInstantiation(DataFlow::NewNode socket) {
|
||||
result = API::moduleImport("net").getMember("Socket").getInstance() and
|
||||
socket = result.getAnImmediateUse()
|
||||
socket = result.asSource()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -829,7 +827,7 @@ module ClientRequest {
|
||||
class ApolloClientRequest extends ClientRequest::Range, API::InvokeNode {
|
||||
ApolloClientRequest() { this = apolloUriCallee().getAnInvocation() }
|
||||
|
||||
override DataFlow::Node getUrl() { result = this.getParameter(0).getMember("uri").getARhs() }
|
||||
override DataFlow::Node getUrl() { result = this.getParameter(0).getMember("uri").asSink() }
|
||||
|
||||
override DataFlow::Node getHost() { none() }
|
||||
|
||||
@@ -850,10 +848,10 @@ module ClientRequest {
|
||||
|
||||
override DataFlow::Node getUrl() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getHost() { result = this.getParameter(0).getMember("host").getARhs() }
|
||||
override DataFlow::Node getHost() { result = this.getParameter(0).getMember("host").asSink() }
|
||||
|
||||
override DataFlow::Node getADataNode() {
|
||||
result = form.getMember("append").getACall().getParameter(1).getARhs()
|
||||
result = form.getMember("append").getACall().getParameter(1).asSink()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,38 +13,40 @@ module ClosureLibrary {
|
||||
call = Closure::moduleImport("goog.string." + name).getACall() and succ = call
|
||||
|
|
||||
pred = call.getAnArgument() and
|
||||
(
|
||||
name = "canonicalizeNewlines" or
|
||||
name = "capitalize" or
|
||||
name = "collapseBreakingSpaces" or
|
||||
name = "collapseWhitespace" or
|
||||
name = "format" or
|
||||
name = "makeSafe" or // makeSafe just guards against null and undefined
|
||||
name = "newLineOrBr" or
|
||||
name = "normalizeSpaces" or
|
||||
name = "normalizeWhitespace" or
|
||||
name = "preserveSpaces" or
|
||||
name = "remove" or // removes first occurrence of a substring
|
||||
name = "repeat" or
|
||||
name = "splitLimit" or
|
||||
name = "stripNewlines" or
|
||||
name = "subs" or
|
||||
name = "toCamelCase" or
|
||||
name = "toSelectorCase" or
|
||||
name = "toTitleCase" or
|
||||
name = "trim" or
|
||||
name = "trimLeft" or
|
||||
name = "trimRight" or
|
||||
name = "unescapeEntities" or
|
||||
name = "whitespaceEscape"
|
||||
)
|
||||
name =
|
||||
[
|
||||
"canonicalizeNewlines", //
|
||||
"capitalize", //
|
||||
"collapseBreakingSpaces", //
|
||||
"collapseWhitespace", //
|
||||
"format", //
|
||||
"makeSafe", // makeSafe just guards against null and undefined
|
||||
"newLineOrBr", //
|
||||
"normalizeSpaces", //
|
||||
"normalizeWhitespace", //
|
||||
"preserveSpaces", //
|
||||
"remove", // removes first occurrence of a substring
|
||||
"repeat", //
|
||||
"splitLimit", //
|
||||
"stripNewlines", //
|
||||
"subs", //
|
||||
"toCamelCase", //
|
||||
"toSelectorCase", //
|
||||
"toTitleCase", //
|
||||
"trim", //
|
||||
"trimLeft", //
|
||||
"trimRight", //
|
||||
"unescapeEntities", //
|
||||
"whitespaceEscape"
|
||||
]
|
||||
or
|
||||
pred = call.getArgument(0) and
|
||||
(
|
||||
name = "truncate" or
|
||||
name = "truncateMiddle" or
|
||||
name = "unescapeEntitiesWithDocument"
|
||||
)
|
||||
name =
|
||||
[
|
||||
"truncate", //
|
||||
"truncateMiddle", //
|
||||
"unescapeEntitiesWithDocument", //
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ private class CredentialsFromModel extends CredentialsExpr {
|
||||
string kind;
|
||||
|
||||
CredentialsFromModel() {
|
||||
this = ModelOutput::getASinkNode("credentials[" + kind + "]").getARhs().asExpr()
|
||||
this = ModelOutput::getASinkNode("credentials[" + kind + "]").asSink().asExpr()
|
||||
}
|
||||
|
||||
override string getCredentialsKind() { result = kind }
|
||||
|
||||
@@ -683,8 +683,6 @@ private module ExpressJwt {
|
||||
*/
|
||||
private module NodeRsa {
|
||||
private class CreateKey extends CryptographicKeyCreation, API::InvokeNode {
|
||||
CryptographicAlgorithm algorithm;
|
||||
|
||||
CreateKey() {
|
||||
this = API::moduleImport("node-rsa").getAnInstantiation()
|
||||
or
|
||||
|
||||
@@ -9,9 +9,7 @@ module D3 {
|
||||
private class D3GlobalEntry extends API::EntryPoint {
|
||||
D3GlobalEntry() { this = "D3GlobalEntry" }
|
||||
|
||||
override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("d3") }
|
||||
|
||||
override DataFlow::Node getARhs() { none() }
|
||||
override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("d3") }
|
||||
}
|
||||
|
||||
/** Gets an API node referring to the `d3` module. */
|
||||
@@ -71,18 +69,18 @@ module D3 {
|
||||
D3XssSink() {
|
||||
exists(API::Node htmlArg |
|
||||
htmlArg = d3Selection().getMember("html").getParameter(0) and
|
||||
this = [htmlArg, htmlArg.getReturn()].getARhs()
|
||||
this = [htmlArg, htmlArg.getReturn()].asSink()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class D3DomValueSource extends DOM::DomValueSource::Range {
|
||||
D3DomValueSource() {
|
||||
this = d3Selection().getMember("each").getReceiver().getAnImmediateUse()
|
||||
this = d3Selection().getMember("each").getReceiver().asSource()
|
||||
or
|
||||
this = d3Selection().getMember("node").getReturn().getAnImmediateUse()
|
||||
this = d3Selection().getMember("node").getReturn().asSource()
|
||||
or
|
||||
this = d3Selection().getMember("nodes").getReturn().getUnknownMember().getAnImmediateUse()
|
||||
this = d3Selection().getMember("nodes").getReturn().getUnknownMember().asSource()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,13 +56,13 @@ module Electron {
|
||||
}
|
||||
}
|
||||
|
||||
private API::Node browserObject() { result.getAnImmediateUse() instanceof NewBrowserObject }
|
||||
private API::Node browserObject() { result.asSource() instanceof NewBrowserObject }
|
||||
|
||||
/**
|
||||
* A data flow node whose value may originate from a browser object instantiation.
|
||||
*/
|
||||
private class BrowserObjectByFlow extends BrowserObject {
|
||||
BrowserObjectByFlow() { browserObject().getAUse() = this }
|
||||
BrowserObjectByFlow() { browserObject().getAValueReachableFromSource() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -413,7 +413,7 @@ module Fastify {
|
||||
/**
|
||||
* A call to `rep.view('file', { ... })`, seen as a template instantiation.
|
||||
*
|
||||
* Assumes the presense of a plugin that provides the `view` method, such as the `point-of-view` plugin.
|
||||
* Assumes the presence of a plugin that provides the `view` method, such as the `point-of-view` plugin.
|
||||
*/
|
||||
private class ViewCall extends Templating::TemplateInstantiation::Range, DataFlow::CallNode {
|
||||
ViewCall() { this = any(ReplySource rep).ref().getAMethodCall("view") }
|
||||
|
||||
@@ -89,7 +89,7 @@ private API::Node globbyFileNameSource() {
|
||||
* A file name or an array of file names from the `globby` library.
|
||||
*/
|
||||
private class GlobbyFileNameSource extends FileNameSource {
|
||||
GlobbyFileNameSource() { this = globbyFileNameSource().getAnImmediateUse() }
|
||||
GlobbyFileNameSource() { this = globbyFileNameSource().asSource() }
|
||||
}
|
||||
|
||||
/** Gets a file name or an array of file names from the `fast-glob` library. */
|
||||
@@ -116,7 +116,7 @@ private API::Node fastGlobFileName() {
|
||||
* A file name or an array of file names from the `fast-glob` library.
|
||||
*/
|
||||
private class FastGlobFileNameSource extends FileNameSource {
|
||||
FastGlobFileNameSource() { this = fastGlobFileName().getAnImmediateUse() }
|
||||
FastGlobFileNameSource() { this = fastGlobFileName().asSource() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,7 +200,7 @@ private class RecursiveReadDir extends FileSystemAccess, FileNameProducer, API::
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getAFileName() { result = this.trackFileSource().getAnImmediateUse() }
|
||||
override DataFlow::Node getAFileName() { result = this.trackFileSource().asSource() }
|
||||
|
||||
private API::Node trackFileSource() {
|
||||
result = this.getParameter([1 .. 2]).getParameter(1)
|
||||
@@ -223,7 +223,7 @@ private module JsonFile {
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getADataNode() { result = this.trackRead().getAnImmediateUse() }
|
||||
override DataFlow::Node getADataNode() { result = this.trackRead().asSource() }
|
||||
|
||||
private API::Node trackRead() {
|
||||
this.getCalleeName() = "readFile" and
|
||||
@@ -272,7 +272,7 @@ private class LoadJsonFile extends FileSystemReadAccess, API::CallNode {
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getADataNode() { result = this.trackRead().getAnImmediateUse() }
|
||||
override DataFlow::Node getADataNode() { result = this.trackRead().asSource() }
|
||||
|
||||
private API::Node trackRead() {
|
||||
this.getCalleeName() = "sync" and result = this.getReturn()
|
||||
@@ -310,7 +310,7 @@ private class WalkDir extends FileNameProducer, FileSystemAccess, API::CallNode
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getAFileName() { result = this.trackFileSource().getAnImmediateUse() }
|
||||
override DataFlow::Node getAFileName() { result = this.trackFileSource().asSource() }
|
||||
|
||||
private API::Node trackFileSource() {
|
||||
not this.getCalleeName() = ["sync", "async"] and
|
||||
|
||||
@@ -15,7 +15,7 @@ private class BusBoyRemoteFlow extends RemoteFlowSource {
|
||||
.getMember("on")
|
||||
.getParameter(1)
|
||||
.getAParameter()
|
||||
.getAnImmediateUse()
|
||||
.asSource()
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "parsed user value from Busbuy" }
|
||||
@@ -49,12 +49,12 @@ private class MultipartyRemoteFlow extends RemoteFlowSource {
|
||||
MultipartyRemoteFlow() {
|
||||
exists(API::Node form | form = API::moduleImport("multiparty").getMember("Form").getInstance() |
|
||||
exists(API::CallNode parse | parse = form.getMember("parse").getACall() |
|
||||
this = parse.getParameter(1).getAParameter().getAnImmediateUse()
|
||||
this = parse.getParameter(1).getAParameter().asSource()
|
||||
)
|
||||
or
|
||||
exists(API::CallNode on | on = form.getMember("on").getACall() |
|
||||
on.getArgument(0).mayHaveStringValue(["part", "file", "field"]) and
|
||||
this = on.getParameter(1).getAParameter().getAnImmediateUse()
|
||||
this = on.getParameter(1).getAParameter().asSource()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -150,8 +150,6 @@ private module HandlebarsTaintSteps {
|
||||
* helpers registered.
|
||||
*/
|
||||
class HandlebarsStep extends DataFlow::SharedFlowStep {
|
||||
DataFlow::CallNode templatingCall;
|
||||
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
isHandlebarsArgStep(pred, succ)
|
||||
}
|
||||
|
||||
@@ -8,9 +8,7 @@ module History {
|
||||
private class HistoryGlobalEntry extends API::EntryPoint {
|
||||
HistoryGlobalEntry() { this = "HistoryLibrary" }
|
||||
|
||||
override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("HistoryLibrary") }
|
||||
|
||||
override DataFlow::Node getARhs() { none() }
|
||||
override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("HistoryLibrary") }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,16 +33,16 @@ module History {
|
||||
/**
|
||||
* A user-controlled location value read from the [history](http://npmjs.org/package/history) library.
|
||||
*/
|
||||
private class HistoryLibaryRemoteFlow extends ClientSideRemoteFlowSource {
|
||||
private class HistoryLibraryRemoteFlow extends ClientSideRemoteFlowSource {
|
||||
ClientSideRemoteFlowKind kind;
|
||||
|
||||
HistoryLibaryRemoteFlow() {
|
||||
HistoryLibraryRemoteFlow() {
|
||||
exists(API::Node loc | loc = [getBrowserHistory(), getHashHistory()].getMember("location") |
|
||||
this = loc.getMember("hash").getAnImmediateUse() and kind.isFragment()
|
||||
this = loc.getMember("hash").asSource() and kind.isFragment()
|
||||
or
|
||||
this = loc.getMember("pathname").getAnImmediateUse() and kind.isPath()
|
||||
this = loc.getMember("pathname").asSource() and kind.isPath()
|
||||
or
|
||||
this = loc.getMember("search").getAnImmediateUse() and kind.isQuery()
|
||||
this = loc.getMember("search").asSource() and kind.isQuery()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ private module HttpProxy {
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrl() { result = getParameter(0).getMember("target").getARhs() }
|
||||
override DataFlow::Node getUrl() { result = getParameter(0).getMember("target").asSink() }
|
||||
|
||||
override DataFlow::Node getHost() {
|
||||
result = getParameter(0).getMember("target").getMember("host").getARhs()
|
||||
result = getParameter(0).getMember("target").getMember("host").asSink()
|
||||
}
|
||||
|
||||
override DataFlow::Node getADataNode() { none() }
|
||||
@@ -49,10 +49,10 @@ private module HttpProxy {
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrl() { result = getOptionsObject().getMember("target").getARhs() }
|
||||
override DataFlow::Node getUrl() { result = getOptionsObject().getMember("target").asSink() }
|
||||
|
||||
override DataFlow::Node getHost() {
|
||||
result = getOptionsObject().getMember("target").getMember("host").getARhs()
|
||||
result = getOptionsObject().getMember("target").getMember("host").asSink()
|
||||
}
|
||||
|
||||
override DataFlow::Node getADataNode() { none() }
|
||||
@@ -78,8 +78,8 @@ private module HttpProxy {
|
||||
ProxyListenerCallback() {
|
||||
exists(API::CallNode call |
|
||||
call = any(CreateServerCall server).getReturn().getMember(["on", "once"]).getACall() and
|
||||
call.getParameter(0).getARhs().mayHaveStringValue(event) and
|
||||
this = call.getParameter(1).getARhs().getAFunctionValue()
|
||||
call.getParameter(0).asSink().mayHaveStringValue(event) and
|
||||
this = call.getParameter(1).asSink().getAFunctionValue()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,7 @@ private module Immutable {
|
||||
private class ImmutableGlobalEntry extends API::EntryPoint {
|
||||
ImmutableGlobalEntry() { this = "ImmutableGlobalEntry" }
|
||||
|
||||
override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("Immutable") }
|
||||
|
||||
override DataFlow::Node getARhs() { none() }
|
||||
override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("Immutable") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Provides classes and predicates modeling the `jwt-decode` libary.
|
||||
* Provides classes and predicates modeling the `jwt-decode` library.
|
||||
*/
|
||||
private module JwtDecode {
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ private module JwtDecode {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides classes and predicates modeling the `jsonwebtoken` libary.
|
||||
* Provides classes and predicates modeling the `jsonwebtoken` library.
|
||||
*/
|
||||
private module JsonWebToken {
|
||||
/**
|
||||
|
||||
@@ -69,7 +69,7 @@ module Knex {
|
||||
private class KnexDatabaseAwait extends DatabaseAccess, DataFlow::ValueNode {
|
||||
KnexDatabaseAwait() {
|
||||
exists(AwaitExpr enclosingAwait | this = enclosingAwait.flow() |
|
||||
enclosingAwait.getOperand() = knexObject().getAUse().asExpr()
|
||||
enclosingAwait.getOperand() = knexObject().getAValueReachableFromSource().asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ module Koa {
|
||||
* Gets a reference to a request parameter defined by this route handler.
|
||||
*/
|
||||
DataFlow::Node getARequestParameterAccess() {
|
||||
none() // overriden in subclasses.
|
||||
none() // overridden in subclasses.
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,10 +61,10 @@ module LdapJS {
|
||||
|
||||
SearchFilter() {
|
||||
options = ldapClient().getMember("search").getACall().getParameter(1) and
|
||||
this = options.getARhs()
|
||||
this = options.asSink()
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput() { result = options.getMember("filter").getARhs() }
|
||||
override DataFlow::Node getInput() { result = options.getMember("filter").asSink() }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ private module LiveServer {
|
||||
class ServerDefinition extends HTTP::Servers::StandardServerDefinition {
|
||||
ServerDefinition() { this = DataFlow::moduleImport("live-server").asExpr() }
|
||||
|
||||
API::Node getImportNode() { result.getAnImmediateUse().asExpr() = this }
|
||||
API::Node getImportNode() { result.asSource().asExpr() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,7 +41,7 @@ private module LiveServer {
|
||||
|
||||
override DataFlow::SourceNode getARouteHandler() {
|
||||
exists(DataFlow::SourceNode middleware |
|
||||
middleware = call.getParameter(0).getMember("middleware").getAValueReachingRhs()
|
||||
middleware = call.getParameter(0).getMember("middleware").getAValueReachingSink()
|
||||
|
|
||||
result = middleware.getAMemberCall(["push", "unshift"]).getArgument(0).getAFunctionValue()
|
||||
or
|
||||
|
||||
@@ -35,9 +35,7 @@ private module Console {
|
||||
private class ConsoleGlobalEntry extends API::EntryPoint {
|
||||
ConsoleGlobalEntry() { this = "ConsoleGlobalEntry" }
|
||||
|
||||
override DataFlow::SourceNode getAUse() { result = DataFlow::globalVarRef("console") }
|
||||
|
||||
override DataFlow::Node getARhs() { none() }
|
||||
override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("console") }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -352,7 +350,7 @@ private module Pino {
|
||||
// `pino` is installed as the "log" property on the request object in `Express` and similar libraries.
|
||||
// in `Hapi` the property is "logger".
|
||||
exists(HTTP::RequestExpr req, API::Node reqNode |
|
||||
reqNode.getAnImmediateUse() = req.flow().getALocalSource() and
|
||||
reqNode.asSource() = req.flow().getALocalSource() and
|
||||
result = reqNode.getMember(["log", "logger"])
|
||||
)
|
||||
}
|
||||
|
||||
@@ -163,14 +163,14 @@ module Markdown {
|
||||
or
|
||||
call = API::moduleImport("markdown-it").getMember("Markdown").getAnInvocation()
|
||||
|
|
||||
call.getParameter(0).getMember("html").getARhs().mayHaveBooleanValue(true) and
|
||||
call.getParameter(0).getMember("html").asSink().mayHaveBooleanValue(true) and
|
||||
result = call.getReturn()
|
||||
)
|
||||
or
|
||||
exists(API::CallNode call |
|
||||
call = markdownIt().getMember(["use", "set", "configure", "enable", "disable"]).getACall() and
|
||||
result = call.getReturn() and
|
||||
not call.getParameter(0).getAValueReachingRhs() =
|
||||
not call.getParameter(0).getAValueReachingSink() =
|
||||
DataFlow::moduleImport("markdown-it-sanitizer")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -140,11 +140,9 @@ module NestJS {
|
||||
private class ValidationNodeEntry extends API::EntryPoint {
|
||||
ValidationNodeEntry() { this = "ValidationNodeEntry" }
|
||||
|
||||
override DataFlow::SourceNode getAUse() {
|
||||
override DataFlow::SourceNode getASource() {
|
||||
result.(DataFlow::ClassNode).getName() = "ValidationPipe"
|
||||
}
|
||||
|
||||
override DataFlow::Node getARhs() { none() }
|
||||
}
|
||||
|
||||
/** Gets an API node referring to the constructor of `ValidationPipe` */
|
||||
@@ -181,7 +179,7 @@ module NestJS {
|
||||
predicate hasGlobalValidationPipe(Folder folder) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call.getCalleeName() = "useGlobalPipes" and
|
||||
call.getArgument(0) = validationPipe().getInstance().getAUse() and
|
||||
call.getArgument(0) = validationPipe().getInstance().getAValueReachableFromSource() and
|
||||
folder = call.getFile().getParentContainer()
|
||||
)
|
||||
or
|
||||
@@ -193,7 +191,7 @@ module NestJS {
|
||||
.getAMember()
|
||||
.getMember("useFactory")
|
||||
.getReturn()
|
||||
.getARhs() = validationPipe().getInstance().getAUse() and
|
||||
.asSink() = validationPipe().getInstance().getAValueReachableFromSource() and
|
||||
folder = decorator.getFile().getParentContainer()
|
||||
)
|
||||
or
|
||||
@@ -204,7 +202,7 @@ module NestJS {
|
||||
* Holds if `param` is affected by a pipe that sanitizes inputs.
|
||||
*/
|
||||
private predicate hasSanitizingPipe(NestJSRequestInput param, boolean dependsOnType) {
|
||||
param.getAPipe() = sanitizingPipe(dependsOnType).getAUse()
|
||||
param.getAPipe() = sanitizingPipe(dependsOnType).getAValueReachableFromSource()
|
||||
or
|
||||
hasGlobalValidationPipe(param.getFile().getParentContainer()) and
|
||||
dependsOnType = true
|
||||
@@ -395,11 +393,11 @@ module NestJS {
|
||||
|
||||
/** Gets a parameter with this decorator applied. */
|
||||
DataFlow::ParameterNode getADecoratedParameter() {
|
||||
result.getADecorator() = getReturn().getReturn().getAUse()
|
||||
result.getADecorator() = getReturn().getReturn().getAValueReachableFromSource()
|
||||
}
|
||||
|
||||
/** Gets a value returned by the decorator's callback, which becomes the value of the decorated parameter. */
|
||||
DataFlow::Node getResult() { result = getParameter(0).getReturn().getARhs() }
|
||||
DataFlow::Node getResult() { result = getParameter(0).getReturn().asSink() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -427,7 +425,7 @@ module NestJS {
|
||||
private class ExpressRequestSource extends Express::RequestSource {
|
||||
ExpressRequestSource() {
|
||||
this.(DataFlow::ParameterNode).getADecorator() =
|
||||
nestjs().getMember(["Req", "Request"]).getReturn().getAnImmediateUse()
|
||||
nestjs().getMember(["Req", "Request"]).getReturn().asSource()
|
||||
or
|
||||
this =
|
||||
executionContext()
|
||||
@@ -435,7 +433,7 @@ module NestJS {
|
||||
.getReturn()
|
||||
.getMember("getRequest")
|
||||
.getReturn()
|
||||
.getAnImmediateUse()
|
||||
.asSource()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -452,7 +450,7 @@ module NestJS {
|
||||
private class ExpressResponseSource extends Express::ResponseSource {
|
||||
ExpressResponseSource() {
|
||||
this.(DataFlow::ParameterNode).getADecorator() =
|
||||
nestjs().getMember(["Res", "Response"]).getReturn().getAnImmediateUse()
|
||||
nestjs().getMember(["Res", "Response"]).getReturn().asSource()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -252,6 +252,6 @@ module NextJS {
|
||||
.getParameter(0)
|
||||
.getParameter(0)
|
||||
.getMember("router")
|
||||
.getAnImmediateUse()
|
||||
.asSource()
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user