mirror of
https://github.com/github/codeql.git
synced 2026-04-29 18:55:14 +02:00
Merge pull request #1032 from xiemaisi/master-for-merge
Merge master into rc/1.20
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* This library contains the majority of the 'js/unused-parameter' query implementation.
|
||||
* Provides classes and predicates for the 'js/unused-parameter' query.
|
||||
*
|
||||
* In order to suppress alerts that are similar to the 'js/unused-parameter' alerts,
|
||||
* `isAnAccidentallyUnusedParameter` should be used since it holds iff that alert is active.
|
||||
|
||||
34
javascript/ql/src/Declarations/UnusedProperty.qhelp
Normal file
34
javascript/ql/src/Declarations/UnusedProperty.qhelp
Normal file
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Unused object properties make code harder to maintain and use. Clients that are unaware that a
|
||||
property is unused may perform nontrivial computations to compute a value that is ultimately
|
||||
unused.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Remove the unused property.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
In this code, the function <code>f</code> initializes a property <code>prop_a</code> with a
|
||||
call to the function <code>expensiveComputation</code>, but later on this property is never read.
|
||||
Removing <code>prop</code> would improve code quality and performance.
|
||||
</p>
|
||||
|
||||
<sample src="examples/UnusedProperty.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Coding Horror: <a href="http://blog.codinghorror.com/code-smells/">Code Smells</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
78
javascript/ql/src/Declarations/UnusedProperty.ql
Normal file
78
javascript/ql/src/Declarations/UnusedProperty.ql
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @name Unused property
|
||||
* @description Unused properties may be a symptom of a bug and should be examined carefully.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/unused-property
|
||||
* @tags maintainability
|
||||
* @precision high
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.dataflow.LocalObjects
|
||||
import UnusedVariable
|
||||
import UnusedParameter
|
||||
import Expressions.ExprHasNoEffect
|
||||
|
||||
predicate hasUnknownPropertyRead(LocalObject obj) {
|
||||
// dynamic reads
|
||||
exists(DataFlow::PropRead r | obj.getAPropertyRead() = r | not exists(r.getPropertyName()))
|
||||
or
|
||||
// reflective reads
|
||||
obj.flowsToExpr(any(EnhancedForLoop l).getIterationDomain())
|
||||
or
|
||||
obj.flowsToExpr(any(InExpr l).getRightOperand())
|
||||
or
|
||||
obj.flowsToExpr(any(SpreadElement e).getOperand())
|
||||
or
|
||||
exists(obj.getAPropertyRead("hasOwnProperty"))
|
||||
or
|
||||
exists(obj.getAPropertyRead("propertyIsEnumerable"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `obj` flows to an expression that must have a specific type.
|
||||
*/
|
||||
predicate flowsToTypeRestrictedExpression(LocalObject obj) {
|
||||
exists (Expr restricted, TypeExpr type |
|
||||
obj.flowsToExpr(restricted) and
|
||||
not type.isAny() |
|
||||
exists (TypeAssertion assertion |
|
||||
type = assertion.getTypeAnnotation() and
|
||||
restricted = assertion.getExpression()
|
||||
)
|
||||
or
|
||||
exists (BindingPattern v |
|
||||
type = v.getTypeAnnotation() and
|
||||
restricted = v.getAVariable().getAnAssignedExpr()
|
||||
)
|
||||
// no need to reason about writes to typed fields, captured nodes do not reach them
|
||||
)
|
||||
}
|
||||
|
||||
from DataFlow::PropWrite write, LocalObject obj, string name
|
||||
where
|
||||
write = obj.getAPropertyWrite(name) and
|
||||
not exists(obj.getAPropertyRead(name)) and
|
||||
// `obj` is the only base object for the write: it is not spurious
|
||||
not write.getBase().analyze().getAValue() != obj.analyze().getAValue() and
|
||||
not hasUnknownPropertyRead(obj) and
|
||||
// avoid reporting if the definition is unreachable
|
||||
write.getAstNode().getFirstControlFlowNode().getBasicBlock() instanceof ReachableBasicBlock and
|
||||
// avoid implicitly read properties
|
||||
not (
|
||||
name = "toString" or
|
||||
name = "valueOf" or
|
||||
name.matches("@@%") // @@iterator, for example
|
||||
) and
|
||||
// avoid flagging properties that a type system requires
|
||||
not flowsToTypeRestrictedExpression(obj) and
|
||||
// flagged by js/unused-local-variable
|
||||
not exists(UnusedLocal l | l.getAnAssignedExpr().getUnderlyingValue().flow() = obj) and
|
||||
// flagged by js/unused-parameter
|
||||
not exists(Parameter p | isAnAccidentallyUnusedParameter(p) |
|
||||
p.getDefault().getUnderlyingValue().flow() = obj
|
||||
) and
|
||||
// flagged by js/useless-expression
|
||||
not inVoidContext(obj.asExpr())
|
||||
select write, "Unused property " + name + "."
|
||||
@@ -10,23 +10,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* A local variable that is neither used nor exported, and is not a parameter
|
||||
* or a function name.
|
||||
*/
|
||||
class UnusedLocal extends LocalVariable {
|
||||
UnusedLocal() {
|
||||
not exists(getAnAccess()) and
|
||||
not exists(Parameter p | this = p.getAVariable()) and
|
||||
not exists(FunctionExpr fe | this = fe.getVariable()) and
|
||||
not exists(ClassExpr ce | this = ce.getVariable()) and
|
||||
not exists(ExportDeclaration ed | ed.exportsAs(this, _)) and
|
||||
not exists(LocalVarTypeAccess type | type.getVariable() = this) and
|
||||
// common convention: variables with leading underscore are intentionally unused
|
||||
getName().charAt(0) != "_"
|
||||
}
|
||||
}
|
||||
import UnusedVariable
|
||||
|
||||
/**
|
||||
* Holds if `v` is mentioned in a JSDoc comment in the same file, and that file
|
||||
@@ -206,6 +190,10 @@ predicate unusedImports(ImportVarDeclProvider provider, string msg) {
|
||||
|
||||
from ASTNode sel, string msg
|
||||
where
|
||||
unusedNonImports(sel, msg) or
|
||||
unusedImports(sel, msg)
|
||||
(
|
||||
unusedNonImports(sel, msg) or
|
||||
unusedImports(sel, msg)
|
||||
) and
|
||||
// avoid reporting if the definition is unreachable
|
||||
sel.getFirstControlFlowNode().getBasicBlock() instanceof ReachableBasicBlock
|
||||
select sel, msg
|
||||
|
||||
22
javascript/ql/src/Declarations/UnusedVariable.qll
Normal file
22
javascript/ql/src/Declarations/UnusedVariable.qll
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Provides classes and predicates for the 'js/unused-local-variable' query.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* A local variable that is neither used nor exported, and is not a parameter
|
||||
* or a function name.
|
||||
*/
|
||||
class UnusedLocal extends LocalVariable {
|
||||
UnusedLocal() {
|
||||
not exists(getAnAccess()) and
|
||||
not exists(Parameter p | this = p.getAVariable()) and
|
||||
not exists(FunctionExpr fe | this = fe.getVariable()) and
|
||||
not exists(ClassExpr ce | this = ce.getVariable()) and
|
||||
not exists(ExportDeclaration ed | ed.exportsAs(this, _)) and
|
||||
not exists(LocalVarTypeAccess type | type.getVariable() = this) and
|
||||
// common convention: variables with leading underscore are intentionally unused
|
||||
getName().charAt(0) != "_"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
function f() {
|
||||
var o = {
|
||||
prop_a: expensiveComputation(),
|
||||
prop_b: anotherComputation()
|
||||
};
|
||||
|
||||
return o.prop_b;
|
||||
}
|
||||
@@ -16,40 +16,7 @@ import javascript
|
||||
import DOMProperties
|
||||
import semmle.javascript.frameworks.xUnit
|
||||
import semmle.javascript.RestrictedLocations
|
||||
|
||||
/**
|
||||
* Holds if `e` appears in a syntactic context where its value is discarded.
|
||||
*/
|
||||
predicate inVoidContext(Expr e) {
|
||||
exists(ExprStmt parent |
|
||||
// e is a toplevel expression in an expression statement
|
||||
parent = e.getParent() and
|
||||
// but it isn't an HTML attribute or a configuration object
|
||||
not exists(TopLevel tl | tl = parent.getParent() |
|
||||
tl instanceof CodeInAttribute
|
||||
or
|
||||
// if the toplevel in its entirety is of the form `({ ... })`,
|
||||
// it is probably a configuration object (e.g., a require.js build configuration)
|
||||
tl.getNumChildStmt() = 1 and e.stripParens() instanceof ObjectExpr
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(SeqExpr seq, int i, int n |
|
||||
e = seq.getOperand(i) and
|
||||
n = seq.getNumOperands()
|
||||
|
|
||||
i < n - 1 or inVoidContext(seq)
|
||||
)
|
||||
or
|
||||
exists(ForStmt stmt | e = stmt.getUpdate())
|
||||
or
|
||||
exists(ForStmt stmt | e = stmt.getInit() |
|
||||
// Allow the pattern `for(i; i < 10; i++)`
|
||||
not e instanceof VarAccess
|
||||
)
|
||||
or
|
||||
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
|
||||
}
|
||||
import ExprHasNoEffect
|
||||
|
||||
/**
|
||||
* Holds if `e` is of the form `x;` or `e.p;` and has a JSDoc comment containing a tag.
|
||||
|
||||
39
javascript/ql/src/Expressions/ExprHasNoEffect.qll
Normal file
39
javascript/ql/src/Expressions/ExprHasNoEffect.qll
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Provides classes and predicates for the 'js/useless-expression' query.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Holds if `e` appears in a syntactic context where its value is discarded.
|
||||
*/
|
||||
predicate inVoidContext(Expr e) {
|
||||
exists(ExprStmt parent |
|
||||
// e is a toplevel expression in an expression statement
|
||||
parent = e.getParent() and
|
||||
// but it isn't an HTML attribute or a configuration object
|
||||
not exists(TopLevel tl | tl = parent.getParent() |
|
||||
tl instanceof CodeInAttribute
|
||||
or
|
||||
// if the toplevel in its entirety is of the form `({ ... })`,
|
||||
// it is probably a configuration object (e.g., a require.js build configuration)
|
||||
tl.getNumChildStmt() = 1 and e.stripParens() instanceof ObjectExpr
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(SeqExpr seq, int i, int n |
|
||||
e = seq.getOperand(i) and
|
||||
n = seq.getNumOperands()
|
||||
|
|
||||
i < n - 1 or inVoidContext(seq)
|
||||
)
|
||||
or
|
||||
exists(ForStmt stmt | e = stmt.getUpdate())
|
||||
or
|
||||
exists(ForStmt stmt | e = stmt.getInit() |
|
||||
// Allow the pattern `for(i; i < 10; i++)`
|
||||
not e instanceof VarAccess
|
||||
)
|
||||
or
|
||||
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
|
||||
}
|
||||
@@ -95,9 +95,9 @@ predicate isDerivedFromLength(DataFlow::Node length, DataFlow::Node operand) {
|
||||
or
|
||||
isDerivedFromLength(length.getAPredecessor(), operand)
|
||||
or
|
||||
exists(SubExpr sub |
|
||||
isDerivedFromLength(sub.getAnOperand().flow(), operand) and
|
||||
length = sub.flow()
|
||||
exists(BinaryExpr expr | expr instanceof SubExpr or expr instanceof AddExpr |
|
||||
isDerivedFromLength(expr.getAnOperand().flow(), operand) and
|
||||
length = expr.flow()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
67
javascript/ql/src/Security/CWE-022/ZipSlip.qhelp
Normal file
67
javascript/ql/src/Security/CWE-022/ZipSlip.qhelp
Normal file
@@ -0,0 +1,67 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>Extracting files from a malicious zip archive without validating that the destination file path
|
||||
is within the destination directory can cause files outside the destination directory to be
|
||||
overwritten, due to the possible presence of directory traversal elements (<code>..</code>) in
|
||||
archive paths.</p>
|
||||
|
||||
<p>Zip archives contain archive entries representing each file in the archive. These entries
|
||||
include a file path for the entry, but these file paths are not restricted and may contain
|
||||
unexpected special elements such as the directory traversal element (<code>..</code>). If these
|
||||
file paths are used to determine an output file to write the contents of the archive item to, then
|
||||
the file may be written to an unexpected location. This can result in sensitive information being
|
||||
revealed or deleted, or an attacker being able to influence behavior by modifying unexpected
|
||||
files.</p>
|
||||
|
||||
<p>For example, if a zip file contains a file entry <code>..\sneaky-file</code>, and the zip file
|
||||
is extracted to the directory <code>c:\output</code>, then naively combining the paths would result
|
||||
in an output file path of <code>c:\output\..\sneaky-file</code>, which would cause the file to be
|
||||
written to <code>c:\sneaky-file</code>.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Ensure that output paths constructed from zip archive entries are validated
|
||||
to prevent writing files to unexpected locations.</p>
|
||||
|
||||
<p>The recommended way of writing an output file from a zip archive entry is to check that
|
||||
<code>".."</code> does not occur in the path.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In this example an archive is extracted without validating file paths.
|
||||
If <code>archive.zip</code> contained relative paths (for
|
||||
instance, if it were created by something like <code>zip archive.zip
|
||||
../file.txt</code>) then executing this code could write to locations
|
||||
outside the destination directory.
|
||||
</p>
|
||||
|
||||
<sample src="ZipSlipBad.js" />
|
||||
|
||||
<p>To fix this vulnerability, we need to check that the path does not
|
||||
contain any <code>".."</code> elements in it.
|
||||
</p>
|
||||
|
||||
<sample src="ZipSlipGood.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
Snyk:
|
||||
<a href="https://snyk.io/research/zip-slip-vulnerability">Zip Slip Vulnerability</a>.
|
||||
</li>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/Path_traversal">Path Traversal</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
22
javascript/ql/src/Security/CWE-022/ZipSlip.ql
Normal file
22
javascript/ql/src/Security/CWE-022/ZipSlip.ql
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @name Arbitrary file write during zip extraction ("Zip Slip")
|
||||
* @description Extracting files from a malicious zip archive without validating that the
|
||||
* destination file path is within the destination directory can cause files outside
|
||||
* the destination directory to be overwritten.
|
||||
* @kind path-problem
|
||||
* @id js/zipslip
|
||||
* @problem.severity error
|
||||
* @precision medium
|
||||
* @tags security
|
||||
* external/cwe/cwe-022
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.ZipSlip::ZipSlip
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"Unsanitized zip archive $@, which may contain '..', is used in a file system operation.",
|
||||
source.getNode(), "item path"
|
||||
10
javascript/ql/src/Security/CWE-022/ZipSlipBad.js
Normal file
10
javascript/ql/src/Security/CWE-022/ZipSlipBad.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const fs = require('fs');
|
||||
const unzip = require('unzip');
|
||||
|
||||
fs.createReadStream('archive.zip')
|
||||
.pipe(unzip.Parse())
|
||||
.on('entry', entry => {
|
||||
const fileName = entry.path;
|
||||
// BAD: This could write any file on the filesystem.
|
||||
entry.pipe(fs.createWriteStream(fileName));
|
||||
});
|
||||
15
javascript/ql/src/Security/CWE-022/ZipSlipGood.js
Normal file
15
javascript/ql/src/Security/CWE-022/ZipSlipGood.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const fs = require('fs');
|
||||
const unzip = require('unzip');
|
||||
|
||||
fs.createReadStream('archive.zip')
|
||||
.pipe(unzip.Parse())
|
||||
.on('entry', entry => {
|
||||
const fileName = entry.path;
|
||||
// GOOD: ensures the path is safe to write to.
|
||||
if (fileName.indexOf('..') == -1) {
|
||||
entry.pipe(fs.createWriteStream(fileName));
|
||||
}
|
||||
else {
|
||||
console.log('skipping bad path', fileName);
|
||||
}
|
||||
});
|
||||
@@ -112,14 +112,18 @@ module StringOps {
|
||||
}
|
||||
|
||||
/**
|
||||
* A call of form `_.startsWith(A, B)` or `ramda.startsWith(A, B)`.
|
||||
* A call of form `_.startsWith(A, B)` or `ramda.startsWith(A, B)` or `goog.string.startsWith(A, B)`.
|
||||
*/
|
||||
private class StartsWith_Library extends Range, DataFlow::CallNode {
|
||||
StartsWith_Library() {
|
||||
getNumArgument() = 2 and
|
||||
exists(DataFlow::SourceNode callee | this = callee.getACall() |
|
||||
callee = LodashUnderscore::member("startsWith") or
|
||||
callee = DataFlow::moduleMember("ramda", "startsWith")
|
||||
callee = DataFlow::moduleMember("ramda", "startsWith") or
|
||||
exists(string name |
|
||||
callee = Closure::moduleImport("goog.string." + name) and
|
||||
(name = "startsWith" or name = "caseInsensitiveStartsWith")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -250,6 +254,9 @@ module StringOps {
|
||||
exists(string name |
|
||||
this = LodashUnderscore::member(name).getACall() and
|
||||
(name = "includes" or name = "include" or name = "contains")
|
||||
or
|
||||
this = Closure::moduleImport("goog.string." + name).getACall() and
|
||||
(name = "contains" or name = "caseInsensitiveContains")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -416,7 +423,11 @@ module StringOps {
|
||||
getNumArgument() = 2 and
|
||||
exists(DataFlow::SourceNode callee | this = callee.getACall() |
|
||||
callee = LodashUnderscore::member("endsWith") or
|
||||
callee = DataFlow::moduleMember("ramda", "endsWith")
|
||||
callee = DataFlow::moduleMember("ramda", "endsWith") or
|
||||
exists(string name |
|
||||
callee = Closure::moduleImport("goog.string." + name) and
|
||||
(name = "endsWith" or name = "caseInsensitiveEndsWith")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -583,7 +583,18 @@ module DataFlow {
|
||||
|
||||
override string getPropertyName() { result = prop.getName() }
|
||||
|
||||
override Node getRhs() { result = parameterNode(prop.getParameter()) }
|
||||
override Node getRhs() {
|
||||
exists(Parameter param, Node paramNode |
|
||||
param = prop.getParameter() and
|
||||
parameterNode(paramNode, param)
|
||||
|
|
||||
result = paramNode
|
||||
or
|
||||
// special case: there is no SSA flow step for unused parameters
|
||||
paramNode instanceof UnusedParameterNode and
|
||||
result = param.getDefault().flow()
|
||||
)
|
||||
}
|
||||
|
||||
override ControlFlowNode getWriteNode() { result = prop.getParameter() }
|
||||
}
|
||||
@@ -1078,6 +1089,16 @@ module DataFlow {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a step from `pred` to `succ` through a field accessed through `this` in a class.
|
||||
*/
|
||||
predicate localFieldStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists (ClassNode cls, string prop |
|
||||
pred = cls.getAReceiverNode().getAPropertyWrite(prop).getRhs() and
|
||||
succ = cls.getAReceiverNode().getAPropertyRead(prop)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data flow node representing the source of definition `def`, taking
|
||||
* flow through IIFE calls into account.
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Provides classes for the local objects that the dataflow library can reason about soundly.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Holds if the dataflow library can not track flow through `escape` due to `cause`.
|
||||
*/
|
||||
private predicate isEscape(DataFlow::Node escape, string cause) {
|
||||
escape = any(DataFlow::InvokeNode invk).getAnArgument() and cause = "argument"
|
||||
or
|
||||
escape = any(DataFlow::FunctionNode fun).getAReturn() and cause = "return"
|
||||
or
|
||||
escape = any(ThrowStmt t).getExpr().flow() and cause = "throw"
|
||||
or
|
||||
escape = any(DataFlow::GlobalVariable v).getAnAssignedExpr().flow() and cause = "global"
|
||||
or
|
||||
escape = any(DataFlow::PropWrite write).getRhs() and cause = "heap"
|
||||
or
|
||||
escape = any(ExportDeclaration e).getSourceNode(_) and cause = "export"
|
||||
or
|
||||
exists (WithStmt with, Assignment assign |
|
||||
with.mayAffect(assign.getLhs()) and
|
||||
assign.getRhs().flow() = escape and
|
||||
cause = "heap"
|
||||
)
|
||||
}
|
||||
|
||||
private DataFlow::Node getAnEscape() {
|
||||
isEscape(result, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` can flow to a `this`-variable.
|
||||
*/
|
||||
private predicate exposedAsReceiver(DataFlow::SourceNode n) {
|
||||
// pragmatic limitation: guarantee for object literals only
|
||||
not n instanceof DataFlow::ObjectLiteralNode
|
||||
or
|
||||
exists(AbstractValue v | n.getAPropertyWrite().getRhs().analyze().getALocalValue() = v |
|
||||
v.isIndefinite(_) or
|
||||
exists(ThisExpr dis | dis.getBinder() = v.(AbstractCallable).getFunction())
|
||||
)
|
||||
or
|
||||
n.flowsToExpr(any(FunctionBindExpr bind).getObject())
|
||||
or
|
||||
// technically, the builtin prototypes could have a `this`-using function through which this node escapes, but we ignore that here
|
||||
// (we also ignore `o['__' + 'proto__'] = ...`)
|
||||
exists(n.getAPropertyWrite("__proto__"))
|
||||
or
|
||||
// could check the assigned value of all affected variables, but it is unlikely to matter in practice
|
||||
exists(WithStmt with | n.flowsToExpr(with.getExpr()))
|
||||
}
|
||||
|
||||
/**
|
||||
* An object that is entirely local, in the sense that the dataflow
|
||||
* library models all of its flow.
|
||||
*
|
||||
* All uses of this node are modeled by `this.flowsTo(_)` and related predicates.
|
||||
*/
|
||||
class LocalObject extends DataFlow::SourceNode {
|
||||
LocalObject() {
|
||||
// pragmatic limitation: object literals only
|
||||
this instanceof DataFlow::ObjectLiteralNode and
|
||||
not flowsTo(getAnEscape()) and
|
||||
not exposedAsReceiver(this)
|
||||
}
|
||||
|
||||
predicate hasOwnProperty(string name) {
|
||||
// the property is defined in the initializer,
|
||||
any(DataFlow::PropWrite write).writes(this, name, _) and
|
||||
// and it is never deleted
|
||||
not exists(DeleteExpr del, DataFlow::PropRef ref |
|
||||
del.getOperand().flow() = ref and
|
||||
flowsTo(ref.getBase()) and
|
||||
(ref.getPropertyName() = name or not exists(ref.getPropertyName()))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -650,6 +650,15 @@ class ClassNode extends DataFlow::SourceNode {
|
||||
.(AbstractCallable)
|
||||
.getFunction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the receiver of an instance member or constructor of this class.
|
||||
*/
|
||||
DataFlow::SourceNode getAReceiverNode() {
|
||||
result = getConstructor().getReceiver()
|
||||
or
|
||||
result = getAnInstanceMember().getReceiver()
|
||||
}
|
||||
}
|
||||
|
||||
module ClassNode {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import javascript
|
||||
import AbstractValuesImpl
|
||||
import semmle.javascript.dataflow.LocalObjects
|
||||
|
||||
/**
|
||||
* Flow analysis for `this` expressions inside functions.
|
||||
@@ -230,3 +231,49 @@ private class TypeInferredCalleeWithAnalyzedReturnFlow extends CallWithNonLocalA
|
||||
|
||||
override AnalyzedFunction getACallee() { result = fun }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `call` uses `receiver` as its only receiver value.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate hasDefiniteReceiver(
|
||||
DataFlow::MethodCallNode call, LocalObject receiver
|
||||
) {
|
||||
call = receiver.getAMethodCall() and
|
||||
exists (DataFlow::AnalyzedNode receiverNode, AbstractValue abstractCapturedReceiver |
|
||||
receiverNode = call.getReceiver() and
|
||||
not receiverNode.getALocalValue().isIndefinite(_) and
|
||||
abstractCapturedReceiver = receiver.analyze().getALocalValue() and
|
||||
forall(DataFlow::AbstractValue v |
|
||||
receiverNode.getALocalValue() = v |
|
||||
v = abstractCapturedReceiver
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables inter-procedural type inference for the return value of a
|
||||
* method call to a flow-insensitively type-inferred callee.
|
||||
*/
|
||||
private class TypeInferredMethodWithAnalyzedReturnFlow extends CallWithNonLocalAnalyzedReturnFlow {
|
||||
DataFlow::FunctionNode fun;
|
||||
|
||||
TypeInferredMethodWithAnalyzedReturnFlow() {
|
||||
exists(LocalObject obj, DataFlow::PropWrite write, string name |
|
||||
this.(DataFlow::MethodCallNode).getMethodName() = name and
|
||||
obj.hasOwnProperty(name) and
|
||||
hasDefiniteReceiver(this, obj) and
|
||||
// include all potential callees
|
||||
// by construction, there are no unknown methods on `obj`
|
||||
write = obj.getAPropertyWrite() and
|
||||
fun.flowsTo(write.getRhs()) and
|
||||
(
|
||||
not exists(write.getPropertyName())
|
||||
or
|
||||
write.getPropertyName() = name
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override AnalyzedFunction getACallee() { result = fun }
|
||||
}
|
||||
@@ -604,14 +604,15 @@ module Express {
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument passed to the `send` method of an HTTP response object.
|
||||
* An argument passed to the `send` or `end` method of an HTTP response object.
|
||||
*/
|
||||
private class ResponseSendArgument extends HTTP::ResponseSendArgument {
|
||||
RouteHandler rh;
|
||||
|
||||
ResponseSendArgument() {
|
||||
exists(MethodCallExpr mce |
|
||||
mce.calls(rh.getAResponseExpr(), "send") and
|
||||
exists(MethodCallExpr mce, string name |
|
||||
mce.calls(rh.getAResponseExpr(), name) and
|
||||
(name = "send" or name = "end") and
|
||||
this = mce.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Provides a taint tracking configuration for reasoning about unsafe zip extraction.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
module ZipSlip {
|
||||
/**
|
||||
* A data flow source for unsafe zip extraction.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for unsafe zip extraction.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer guard for unsafe zip extraction.
|
||||
*/
|
||||
abstract class SanitizerGuard extends TaintTracking::SanitizerGuardNode, DataFlow::ValueNode { }
|
||||
|
||||
/** A taint tracking configuration for unsafe zip extraction. */
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "ZipSlip" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode nd) {
|
||||
nd instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that can be a parsed zip archive.
|
||||
*/
|
||||
private DataFlow::SourceNode parsedArchive() {
|
||||
result = DataFlow::moduleImport("unzip").getAMemberCall("Parse")
|
||||
or
|
||||
// `streamProducer.pipe(unzip.Parse())` is a typical (but not
|
||||
// universal) pattern when using nodejs streams, whose return
|
||||
// value is the parsed stream.
|
||||
exists(DataFlow::MethodCallNode pipe |
|
||||
pipe = result and
|
||||
pipe.getMethodName() = "pipe" and
|
||||
parsedArchive().flowsTo(pipe.getArgument(0))
|
||||
)
|
||||
}
|
||||
|
||||
/** A zip archive entry path access, as a source for unsafe zip extraction. */
|
||||
class UnzipEntrySource extends Source {
|
||||
// For example, in
|
||||
// ```javascript
|
||||
// const unzip = require('unzip');
|
||||
//
|
||||
// fs.createReadStream('archive.zip')
|
||||
// .pipe(unzip.Parse())
|
||||
// .on('entry', entry => {
|
||||
// const path = entry.path;
|
||||
// });
|
||||
// ```
|
||||
// there is an `UnzipEntrySource` node corresponding to
|
||||
// the expression `entry.path`.
|
||||
UnzipEntrySource() {
|
||||
exists(DataFlow::CallNode cn |
|
||||
cn = parsedArchive().getAMemberCall("on") and
|
||||
cn.getArgument(0).mayHaveStringValue("entry") and
|
||||
this = cn.getCallback(1)
|
||||
.getParameter(0)
|
||||
.getAPropertyRead("path"))
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `fs.createWriteStream`, as a sink for unsafe zip extraction. */
|
||||
class CreateWriteStreamSink extends Sink {
|
||||
CreateWriteStreamSink() {
|
||||
// This is not covered by `FileSystemWriteSink`, because it is
|
||||
// required that a write actually takes place to the stream.
|
||||
// However, we want to consider even the bare `createWriteStream`
|
||||
// to be a zipslip vulnerability since it may truncate an
|
||||
// existing file.
|
||||
this = DataFlow::moduleImport("fs").getAMemberCall("createWriteStream").getArgument(0)
|
||||
}
|
||||
}
|
||||
|
||||
/** A file path of a file write, as a sink for unsafe zip extraction. */
|
||||
class FileSystemWriteSink extends Sink {
|
||||
FileSystemWriteSink() { exists(FileSystemWriteAccess fsw | fsw.getAPathArgument() = this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string which is sufficient to exclude to make
|
||||
* a filepath definitely not refer to parent directories.
|
||||
*/
|
||||
private string getAParentDirName() { result = ".." or result = "../" }
|
||||
|
||||
/** A check that a path string does not include '..' */
|
||||
class NoParentDirSanitizerGuard extends SanitizerGuard {
|
||||
StringOps::Includes incl;
|
||||
|
||||
NoParentDirSanitizerGuard() { this = incl }
|
||||
|
||||
override predicate sanitizes(boolean outcome, Expr e) {
|
||||
incl.getPolarity().booleanNot() = outcome and
|
||||
incl.getBaseString().asExpr() = e and
|
||||
incl.getSubstring().mayHaveStringValue(getAParentDirName())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
| tst2.js:3:14:3:14 | x | tst2.js:7:5:7:10 | this.x |
|
||||
| tst2.js:3:14:3:14 | x | tst2.js:8:25:8:30 | this.x |
|
||||
| tst2.js:3:14:3:14 | x | tst2.js:12:12:12:17 | this.x |
|
||||
5
javascript/ql/test/library-tests/ClassNode/FieldStep.ql
Normal file
5
javascript/ql/test/library-tests/ClassNode/FieldStep.ql
Normal file
@@ -0,0 +1,5 @@
|
||||
import javascript
|
||||
|
||||
from DataFlow::Node pred, DataFlow::Node succ
|
||||
where DataFlow::localFieldStep(pred, succ)
|
||||
select pred, succ
|
||||
@@ -1,4 +1,6 @@
|
||||
| namespace.js:5:32:5:44 | function() {} | Baz.method | method |
|
||||
| tst2.js:6:9:9:3 | () {\\n ... .x;\\n } | C.method | method |
|
||||
| tst2.js:11:13:13:3 | () {\\n ... .x;\\n } | C.getter | getter |
|
||||
| tst.js:4:17:4:21 | () {} | A.instanceMethod | method |
|
||||
| tst.js:7:6:7:10 | () {} | A.bar | method |
|
||||
| tst.js:9:10:9:14 | () {} | A.baz | getter |
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
| namespace.js:5:32:5:44 | function() {} | Baz.method |
|
||||
| tst2.js:6:9:9:3 | () {\\n ... .x;\\n } | C.method |
|
||||
| tst.js:4:17:4:21 | () {} | A.instanceMethod |
|
||||
| tst.js:7:6:7:10 | () {} | A.bar |
|
||||
| tst.js:17:19:17:31 | function() {} | B.foo |
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
| namespace.js:3:15:3:31 | function Baz() {} | namespace.js:3:15:3:14 | this |
|
||||
| namespace.js:3:15:3:31 | function Baz() {} | namespace.js:5:32:5:31 | this |
|
||||
| tst2.js:1:1:14:1 | class C ... ;\\n }\\n} | tst2.js:2:14:2:13 | this |
|
||||
| tst2.js:1:1:14:1 | class C ... ;\\n }\\n} | tst2.js:6:9:6:8 | this |
|
||||
| tst2.js:1:1:14:1 | class C ... ;\\n }\\n} | tst2.js:11:13:11:12 | this |
|
||||
| tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:3:9:3:8 | this |
|
||||
| tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:4:17:4:16 | this |
|
||||
| tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:7:6:7:5 | this |
|
||||
| tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:9:10:9:9 | this |
|
||||
| tst.js:13:1:13:21 | class A ... ds A {} | tst.js:13:20:13:19 | this |
|
||||
| tst.js:15:1:15:15 | function B() {} | tst.js:15:1:15:0 | this |
|
||||
| tst.js:15:1:15:15 | function B() {} | tst.js:17:19:17:18 | this |
|
||||
| tst.js:19:1:19:15 | function C() {} | tst.js:19:1:19:0 | this |
|
||||
| tst.js:19:1:19:15 | function C() {} | tst.js:21:19:21:18 | this |
|
||||
| tst.js:23:1:23:15 | function D() {} | tst.js:23:1:23:0 | this |
|
||||
| tst.js:23:1:23:15 | function D() {} | tst.js:25:13:25:12 | this |
|
||||
| tst.js:23:1:23:15 | function D() {} | tst.js:26:13:26:12 | this |
|
||||
| tst.js:23:1:23:15 | function D() {} | tst.js:27:4:27:3 | this |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from DataFlow::ClassNode cls
|
||||
select cls, cls.getAReceiverNode()
|
||||
14
javascript/ql/test/library-tests/ClassNode/tst2.js
Normal file
14
javascript/ql/test/library-tests/ClassNode/tst2.js
Normal file
@@ -0,0 +1,14 @@
|
||||
class C {
|
||||
constructor(x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
method() {
|
||||
this.x;
|
||||
let closure = () => this.x;
|
||||
}
|
||||
|
||||
get getter() {
|
||||
return this.x;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} |
|
||||
| method-calls.js:11:23:11:24 | {} |
|
||||
| method-calls.js:16:11:16:12 | {} |
|
||||
| method-calls.js:25:11:25:17 | {m: f1} |
|
||||
| method-calls.js:29:11:29:17 | {m: f2} |
|
||||
| method-calls.js:34:12:34:18 | {m: f3} |
|
||||
| method-calls.js:38:15:38:21 | {m: f4} |
|
||||
| method-calls.js:46:16:46:28 | {m: () => 42} |
|
||||
| method-calls.js:50:17:50:29 | {m: () => 42} |
|
||||
| method-calls.js:54:16:54:28 | {m: () => 42} |
|
||||
| method-calls.js:57:16:57:28 | {m: () => 42} |
|
||||
| tst.js:3:18:3:19 | {} |
|
||||
| tst.js:4:18:4:36 | { f: function(){} } |
|
||||
| tst.js:5:18:5:34 | { [unknown]: 42 } |
|
||||
| tst.js:38:18:38:26 | { p: 42 } |
|
||||
| tst.js:42:18:42:33 | { p: 42, q: 42 } |
|
||||
| tst.js:52:23:52:24 | {} |
|
||||
| tst.js:56:20:56:35 | { p: 42, p: 42 } |
|
||||
| tst.js:59:20:59:28 | { p: 42 } |
|
||||
| tst.js:63:20:63:28 | { p: 42 } |
|
||||
| tst.js:68:19:68:27 | { p: 42 } |
|
||||
| tst.js:73:18:73:20 | { } |
|
||||
| tst.js:76:18:76:26 | { p: 42 } |
|
||||
| tst.js:77:18:77:28 | { p: true } |
|
||||
| tst.js:82:20:82:21 | {} |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
import semmle.javascript.dataflow.LocalObjects
|
||||
|
||||
select any(LocalObject n)
|
||||
@@ -0,0 +1,22 @@
|
||||
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} | m1 |
|
||||
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} | m2 |
|
||||
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} | m3 |
|
||||
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} | m4 |
|
||||
| method-calls.js:25:11:25:17 | {m: f1} | m |
|
||||
| method-calls.js:29:11:29:17 | {m: f2} | m |
|
||||
| method-calls.js:34:12:34:18 | {m: f3} | m |
|
||||
| method-calls.js:38:15:38:21 | {m: f4} | m |
|
||||
| method-calls.js:46:16:46:28 | {m: () => 42} | m |
|
||||
| method-calls.js:50:17:50:29 | {m: () => 42} | m |
|
||||
| method-calls.js:54:16:54:28 | {m: () => 42} | m |
|
||||
| method-calls.js:57:16:57:28 | {m: () => 42} | m |
|
||||
| tst.js:4:18:4:36 | { f: function(){} } | f |
|
||||
| tst.js:38:18:38:26 | { p: 42 } | p |
|
||||
| tst.js:42:18:42:33 | { p: 42, q: 42 } | p |
|
||||
| tst.js:42:18:42:33 | { p: 42, q: 42 } | q |
|
||||
| tst.js:56:20:56:35 | { p: 42, p: 42 } | p |
|
||||
| tst.js:59:20:59:28 | { p: 42 } | p |
|
||||
| tst.js:63:20:63:28 | { p: 42 } | p |
|
||||
| tst.js:68:19:68:27 | { p: 42 } | p |
|
||||
| tst.js:76:18:76:26 | { p: 42 } | p |
|
||||
| tst.js:77:18:77:28 | { p: true } | p |
|
||||
@@ -0,0 +1,6 @@
|
||||
import javascript
|
||||
import semmle.javascript.dataflow.LocalObjects
|
||||
|
||||
from LocalObject src, string name
|
||||
where src.hasOwnProperty(name)
|
||||
select src, name
|
||||
@@ -0,0 +1,12 @@
|
||||
| method-calls.js:8:2:8:8 | o1.m1() | object |
|
||||
| method-calls.js:9:2:9:8 | o1.m2() | object |
|
||||
| method-calls.js:12:2:12:7 | o.m3() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:21:2:21:7 | o2.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:25:11:25:21 | {m: f1}.m() | object |
|
||||
| method-calls.js:30:11:30:16 | o2.m() | object |
|
||||
| method-calls.js:34:11:34:23 | ({m: f3}).m() | object |
|
||||
| method-calls.js:41:11:41:16 | o4.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:47:12:47:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:51:12:51:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:55:12:55:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:60:12:60:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from DataFlow::MethodCallNode call
|
||||
select call, call.analyze().ppTypes()
|
||||
@@ -0,0 +1,4 @@
|
||||
| method-calls.js:25:11:25:21 | {m: f1}.m() | method-calls.js:26:2:26:3 | v1 | method-calls.js:24:23:24:24 | object literal |
|
||||
| method-calls.js:30:11:30:16 | o2.m() | method-calls.js:31:2:31:3 | v2 | method-calls.js:28:23:28:24 | object literal |
|
||||
| method-calls.js:34:11:34:23 | ({m: f3}).m() | method-calls.js:35:2:35:3 | v3 | method-calls.js:33:23:33:24 | object literal |
|
||||
| method-calls.js:41:11:41:16 | o4.m() | method-calls.js:42:2:42:3 | v4 | file://:0:0:0:0 | indefinite value (call) |
|
||||
@@ -0,0 +1,5 @@
|
||||
import javascript
|
||||
|
||||
from DataFlow::MethodCallNode call, DataFlow::Node use
|
||||
where call.flowsTo(use) and use != call and not exists(use.getASuccessor())
|
||||
select call, use, use.analyze().getAValue()
|
||||
@@ -0,0 +1,63 @@
|
||||
(function() {
|
||||
var o1 = {
|
||||
m1: function(){ return {}; },
|
||||
m2: function(){ return {}; },
|
||||
m3: function(){ return {}; },
|
||||
m4: function(){ return {}; }
|
||||
};
|
||||
o1.m1(); // analyzed precisely
|
||||
o1.m2(); // analyzed precisely
|
||||
unknown(o1.m2);
|
||||
var o = unknown? o1: {};
|
||||
o.m3(); // not analyzed precisely: `o1` is not the only receiver.
|
||||
var m4 = o.m4;
|
||||
m4(); // (not a method call)
|
||||
|
||||
var o2 = {};
|
||||
o2.m = function() { return {}; };
|
||||
// not analyzed precisely: `m` may be in the prototype since `m`
|
||||
// is not in the initializer, and we do not attempt to reason flow
|
||||
// sensitively beyond that at the moment
|
||||
o2.m();
|
||||
});
|
||||
(function(){
|
||||
function f1(){return {};}
|
||||
var v1 = {m: f1}.m(); // analyzed precisely
|
||||
v1 === true;
|
||||
|
||||
function f2(){return {};}
|
||||
var o2 = {m: f2};
|
||||
var v2 = o2.m(); // analyzed precisely
|
||||
v2 === true;
|
||||
|
||||
function f3(){return {};}
|
||||
var v3 = ({m: f3}).m(); // analyzed precisely
|
||||
v3 === true;
|
||||
|
||||
function f4(){return {};}
|
||||
var { o4 } = {m: f4};
|
||||
// not analyzed precisely: o4 is from a destructuring assignment
|
||||
// (and it is even `undefined` in this case)
|
||||
var v4 = o4.m();
|
||||
v4 === true;
|
||||
});
|
||||
|
||||
(function(){
|
||||
(function(o = {m: () => 42}){
|
||||
var v1 = o.m(); // not analyzed precisely: `o` may be `unknown`
|
||||
})(unknown);
|
||||
|
||||
function f(o = {m: () => 42}){
|
||||
var v2 = o.m(); // not analyzed precisely: `o` may be `unknown`
|
||||
};
|
||||
f(unknown);
|
||||
(function(o = {m: () => 42}){
|
||||
var v3 = o.m(); // not analyzed precisely: `o.m` may be `unknown`
|
||||
})({m: unknown});
|
||||
(function(o = {m: () => 42}){
|
||||
// not analyzed precisely: we only support unique receivers at
|
||||
// the moment
|
||||
var v4 = o.m();
|
||||
})({m: () => true});
|
||||
|
||||
});
|
||||
93
javascript/ql/test/library-tests/LocalObjects/tst.js
Normal file
93
javascript/ql/test/library-tests/LocalObjects/tst.js
Normal file
@@ -0,0 +1,93 @@
|
||||
(function capturedSource(){
|
||||
|
||||
let captured1 = {};
|
||||
let captured2 = { f: function(){} };
|
||||
let captured3 = { [unknown]: 42 };
|
||||
|
||||
unknown({});
|
||||
|
||||
function known(){}
|
||||
known({});
|
||||
|
||||
function known_escaping(e){unknown(e)}
|
||||
known_escaping({});
|
||||
|
||||
(function(){return {}});
|
||||
|
||||
(function(){throw {}});
|
||||
|
||||
global = {};
|
||||
|
||||
this.p = {};
|
||||
|
||||
let local_in_with;
|
||||
with (unknown) {
|
||||
local_in_with = {};
|
||||
}
|
||||
|
||||
with({}){}
|
||||
|
||||
({ m: function(){ this; } });
|
||||
({ m: unknown });
|
||||
let indirectlyUnknown = unknown? unknown: function(){};
|
||||
({ m: indirectlyUnknown });
|
||||
});
|
||||
|
||||
(function capturedProperty(){
|
||||
|
||||
let captured1 = { p: 42 };
|
||||
captured1.p;
|
||||
captured1.p;
|
||||
|
||||
let captured2 = { p: 42, q: 42 };
|
||||
captured2.p;
|
||||
captured2.p;
|
||||
captured2.q = 42;
|
||||
captured2 = 42;
|
||||
|
||||
let nonObject = function(){}
|
||||
nonObject.p = 42;
|
||||
nonObject.p;
|
||||
|
||||
let nonInitializer = {};
|
||||
nonInitializer.p = 42;
|
||||
nonInitializer.p;
|
||||
|
||||
let overridden1 = { p: 42, p: 42 };
|
||||
overridden1.p;
|
||||
|
||||
let overridden2 = { p: 42 };
|
||||
overridden2.p = 42;
|
||||
overridden2.p;
|
||||
|
||||
let overridden3 = { p: 42 };
|
||||
overridden3[x] = 42;
|
||||
overridden3.p;
|
||||
|
||||
function f(o) {
|
||||
let captured3 = { p: 42 };
|
||||
o = o || captured3;
|
||||
o.p;
|
||||
}
|
||||
|
||||
let captured4 = { };
|
||||
captured4.p;
|
||||
|
||||
let captured5 = { p: 42 },
|
||||
captured6 = { p: true };
|
||||
(unknown? captured5: captured6).p; // could support this with a bit of extra work
|
||||
|
||||
(function(semiCaptured7){
|
||||
if(unknown)
|
||||
semiCaptured7 = {};
|
||||
semiCaptured7.p = 42;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
(function (){
|
||||
let bound = {};
|
||||
bound::unknown();
|
||||
});
|
||||
|
||||
// semmle-extractor-options: --experimental
|
||||
6
javascript/ql/test/library-tests/LocalObjects/tst.ts
Normal file
6
javascript/ql/test/library-tests/LocalObjects/tst.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
class C {
|
||||
constructor(
|
||||
private readonly F: { timeout: number } = { timeout: 1500 }
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,10 @@
|
||||
| classes.ts:4:3:4:24 | instanc ... foo(); |
|
||||
| classes.ts:8:3:8:39 | constru ... eld) {} |
|
||||
| classes.ts:8:15:8:35 | public ... erField |
|
||||
| classes.ts:12:5:12:68 | constru ... + 42; } |
|
||||
| classes.ts:12:17:12:37 | public ... erField |
|
||||
| classes.ts:16:5:16:46 | constru ... {}) {} |
|
||||
| classes.ts:16:17:16:37 | public ... erField |
|
||||
| tst.js:3:5:3:8 | x: 4 |
|
||||
| tst.js:4:5:6:5 | func: f ... ;\\n } |
|
||||
| tst.js:7:5:9:5 | f() {\\n ... ;\\n } |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
| classes.ts:4:3:4:24 | instanc ... foo(); | classes.ts:3:21:3:20 | this |
|
||||
| classes.ts:8:15:8:35 | public ... erField | classes.ts:8:3:8:2 | this |
|
||||
| classes.ts:12:17:12:37 | public ... erField | classes.ts:12:5:12:4 | this |
|
||||
| classes.ts:16:17:16:37 | public ... erField | classes.ts:16:5:16:4 | this |
|
||||
| tst.js:3:5:3:8 | x: 4 | tst.js:2:11:10:1 | {\\n x ... }\\n} |
|
||||
| tst.js:4:5:6:5 | func: f ... ;\\n } | tst.js:2:11:10:1 | {\\n x ... }\\n} |
|
||||
| tst.js:7:5:9:5 | f() {\\n ... ;\\n } | tst.js:2:11:10:1 | {\\n x ... }\\n} |
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
| classes.ts:4:3:4:24 | instanc ... foo(); | instanceField |
|
||||
| classes.ts:8:3:8:39 | constru ... eld) {} | constructor |
|
||||
| classes.ts:8:15:8:35 | public ... erField | parameterField |
|
||||
| classes.ts:12:5:12:68 | constru ... + 42; } | constructor |
|
||||
| classes.ts:12:17:12:37 | public ... erField | parameterField |
|
||||
| classes.ts:16:5:16:46 | constru ... {}) {} | constructor |
|
||||
| classes.ts:16:17:16:37 | public ... erField | parameterField |
|
||||
| tst.js:3:5:3:8 | x: 4 | x |
|
||||
| tst.js:4:5:6:5 | func: f ... ;\\n } | func |
|
||||
| tst.js:7:5:9:5 | f() {\\n ... ;\\n } | f |
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
| classes.ts:4:3:4:24 | instanc ... foo(); | classes.ts:4:19:4:23 | foo() |
|
||||
| classes.ts:8:3:8:39 | constru ... eld) {} | classes.ts:8:3:8:39 | constru ... eld) {} |
|
||||
| classes.ts:8:15:8:35 | public ... erField | classes.ts:8:22:8:35 | parameterField |
|
||||
| classes.ts:12:5:12:68 | constru ... + 42; } | classes.ts:12:5:12:68 | constru ... + 42; } |
|
||||
| classes.ts:12:17:12:37 | public ... erField | classes.ts:12:24:12:37 | parameterField |
|
||||
| classes.ts:16:5:16:46 | constru ... {}) {} | classes.ts:16:5:16:46 | constru ... {}) {} |
|
||||
| classes.ts:16:17:16:37 | public ... erField | classes.ts:16:24:16:37 | parameterField |
|
||||
| classes.ts:16:17:16:37 | public ... erField | classes.ts:16:41:16:42 | {} |
|
||||
| tst.js:3:5:3:8 | x: 4 | tst.js:3:8:3:8 | 4 |
|
||||
| tst.js:4:5:6:5 | func: f ... ;\\n } | tst.js:4:11:6:5 | functio ... ;\\n } |
|
||||
| tst.js:7:5:9:5 | f() {\\n ... ;\\n } | tst.js:7:6:9:5 | () {\\n ... ;\\n } |
|
||||
|
||||
@@ -7,3 +7,11 @@ class InstanceField {
|
||||
class ParameterField {
|
||||
constructor(public parameterField) {}
|
||||
}
|
||||
|
||||
class ParameterFieldInit {
|
||||
constructor(public parameterField = {}) { parameterField + 42; }
|
||||
}
|
||||
|
||||
class ParameterFieldInitUnused {
|
||||
constructor(public parameterField = {}) {}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
| classes.ts:3:21:3:20 | this | classes.ts:4:3:4:24 | instanc ... foo(); |
|
||||
| classes.ts:8:3:8:2 | this | classes.ts:8:15:8:35 | public ... erField |
|
||||
| classes.ts:12:5:12:4 | this | classes.ts:12:17:12:37 | public ... erField |
|
||||
| classes.ts:16:5:16:4 | this | classes.ts:16:17:16:37 | public ... erField |
|
||||
| tst.js:1:1:1:0 | this | tst.js:23:15:23:29 | this.someMethod |
|
||||
| tst.js:1:1:1:0 | this | tst.js:24:36:24:45 | this.state |
|
||||
| tst.js:2:11:10:1 | {\\n x ... }\\n} | tst.js:3:5:3:8 | x: 4 |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
| classes.ts:3:21:3:20 | this | instanceField | classes.ts:4:3:4:24 | instanc ... foo(); |
|
||||
| classes.ts:8:3:8:2 | this | parameterField | classes.ts:8:15:8:35 | public ... erField |
|
||||
| classes.ts:12:5:12:4 | this | parameterField | classes.ts:12:17:12:37 | public ... erField |
|
||||
| classes.ts:16:5:16:4 | this | parameterField | classes.ts:16:17:16:37 | public ... erField |
|
||||
| tst.js:1:1:1:0 | this | someMethod | tst.js:23:15:23:29 | this.someMethod |
|
||||
| tst.js:1:1:1:0 | this | state | tst.js:24:36:24:45 | this.state |
|
||||
| tst.js:2:11:10:1 | {\\n x ... }\\n} | f | tst.js:7:5:9:5 | f() {\\n ... ;\\n } |
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
| classes.ts:3:21:3:20 | this | instanceField | classes.ts:4:19:4:23 | foo() |
|
||||
| classes.ts:8:3:8:2 | this | parameterField | classes.ts:8:22:8:35 | parameterField |
|
||||
| classes.ts:12:5:12:4 | this | parameterField | classes.ts:12:24:12:37 | parameterField |
|
||||
| classes.ts:12:5:12:4 | this | parameterField | classes.ts:12:41:12:42 | {} |
|
||||
| classes.ts:16:5:16:4 | this | parameterField | classes.ts:16:24:16:37 | parameterField |
|
||||
| classes.ts:16:5:16:4 | this | parameterField | classes.ts:16:41:16:42 | {} |
|
||||
| tst.js:2:11:10:1 | {\\n x ... }\\n} | f | tst.js:7:6:9:5 | () {\\n ... ;\\n } |
|
||||
| tst.js:2:11:10:1 | {\\n x ... }\\n} | func | tst.js:4:11:6:5 | functio ... ;\\n } |
|
||||
| tst.js:12:1:19:1 | class C ... ;\\n }\\n} | func | tst.js:13:14:15:3 | (x) {\\n ... x);\\n } |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
| classes.ts:3:21:3:20 | this | classes.ts:4:3:4:24 | instanc ... foo(); |
|
||||
| classes.ts:8:3:8:2 | this | classes.ts:8:15:8:35 | public ... erField |
|
||||
| classes.ts:12:5:12:4 | this | classes.ts:12:17:12:37 | public ... erField |
|
||||
| classes.ts:16:5:16:4 | this | classes.ts:16:17:16:37 | public ... erField |
|
||||
| tst.js:2:11:10:1 | {\\n x ... }\\n} | tst.js:3:5:3:8 | x: 4 |
|
||||
| tst.js:2:11:10:1 | {\\n x ... }\\n} | tst.js:4:5:6:5 | func: f ... ;\\n } |
|
||||
| tst.js:2:11:10:1 | {\\n x ... }\\n} | tst.js:7:5:9:5 | f() {\\n ... ;\\n } |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
| classes.ts:3:21:3:20 | this | instanceField | classes.ts:4:3:4:24 | instanc ... foo(); |
|
||||
| classes.ts:8:3:8:2 | this | parameterField | classes.ts:8:15:8:35 | public ... erField |
|
||||
| classes.ts:12:5:12:4 | this | parameterField | classes.ts:12:17:12:37 | public ... erField |
|
||||
| classes.ts:16:5:16:4 | this | parameterField | classes.ts:16:17:16:37 | public ... erField |
|
||||
| tst.js:2:11:10:1 | {\\n x ... }\\n} | f | tst.js:7:5:9:5 | f() {\\n ... ;\\n } |
|
||||
| tst.js:2:11:10:1 | {\\n x ... }\\n} | func | tst.js:4:5:6:5 | func: f ... ;\\n } |
|
||||
| tst.js:2:11:10:1 | {\\n x ... }\\n} | x | tst.js:3:5:3:8 | x: 4 |
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
| classes.ts:3:21:3:20 | this | instanceField | classes.ts:4:19:4:23 | foo() |
|
||||
| classes.ts:8:3:8:2 | this | parameterField | classes.ts:8:22:8:35 | parameterField |
|
||||
| classes.ts:12:5:12:4 | this | parameterField | classes.ts:12:24:12:37 | parameterField |
|
||||
| classes.ts:16:5:16:4 | this | parameterField | classes.ts:16:24:16:37 | parameterField |
|
||||
| classes.ts:16:5:16:4 | this | parameterField | classes.ts:16:41:16:42 | {} |
|
||||
| tst.js:2:11:10:1 | {\\n x ... }\\n} | f | tst.js:7:6:9:5 | () {\\n ... ;\\n } |
|
||||
| tst.js:2:11:10:1 | {\\n x ... }\\n} | func | tst.js:4:11:6:5 | functio ... ;\\n } |
|
||||
| tst.js:2:11:10:1 | {\\n x ... }\\n} | x | tst.js:3:8:3:8 | 4 |
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
| tst.js:5:7:5:19 | A.endsWith(B) | tst.js:5:7:5:7 | A | tst.js:5:18:5:18 | B | true |
|
||||
| tst.js:6:7:6:22 | _.endsWith(A, B) | tst.js:6:18:6:18 | A | tst.js:6:21:6:21 | B | true |
|
||||
| tst.js:7:7:7:22 | R.endsWith(A, B) | tst.js:7:18:7:18 | A | tst.js:7:21:7:21 | B | true |
|
||||
| tst.js:6:7:6:19 | A.endsWith(B) | tst.js:6:7:6:7 | A | tst.js:6:18:6:18 | B | true |
|
||||
| tst.js:7:7:7:22 | _.endsWith(A, B) | tst.js:7:18:7:18 | A | tst.js:7:21:7:21 | B | true |
|
||||
| tst.js:8:7:8:22 | R.endsWith(A, B) | tst.js:8:18:8:18 | A | tst.js:8:21:8:21 | B | true |
|
||||
| tst.js:9:7:9:28 | strings ... h(A, B) | tst.js:9:24:9:24 | A | tst.js:9:27:9:27 | B | true |
|
||||
| tst.js:10:7:10:43 | strings ... h(A, B) | tst.js:10:39:10:39 | A | tst.js:10:42:10:42 | B | true |
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import * as _ from 'underscore';
|
||||
import * as R from 'ramda';
|
||||
let strings = goog.require('goog.string');
|
||||
|
||||
function test() {
|
||||
if (A.endsWith(B)) {}
|
||||
if (_.endsWith(A, B)) {}
|
||||
if (R.endsWith(A, B)) {}
|
||||
if (strings.endsWith(A, B)) {}
|
||||
if (strings.caseInsensitiveEndsWith(A, B)) {}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
| tst.js:4:7:4:19 | A.includes(B) | tst.js:4:7:4:7 | A | tst.js:4:18:4:18 | B | true |
|
||||
| tst.js:5:7:5:22 | _.includes(A, B) | tst.js:5:18:5:18 | A | tst.js:5:21:5:21 | B | true |
|
||||
| tst.js:6:7:6:25 | A.indexOf(B) !== -1 | tst.js:6:7:6:7 | A | tst.js:6:17:6:17 | B | true |
|
||||
| tst.js:7:7:7:23 | A.indexOf(B) >= 0 | tst.js:7:7:7:7 | A | tst.js:7:17:7:17 | B | true |
|
||||
| tst.js:8:7:8:19 | ~A.indexOf(B) | tst.js:8:8:8:8 | A | tst.js:8:18:8:18 | B | true |
|
||||
| tst.js:11:7:11:25 | A.indexOf(B) === -1 | tst.js:11:7:11:7 | A | tst.js:11:17:11:17 | B | false |
|
||||
| tst.js:12:7:12:22 | A.indexOf(B) < 0 | tst.js:12:7:12:7 | A | tst.js:12:17:12:17 | B | false |
|
||||
| tst.js:5:7:5:19 | A.includes(B) | tst.js:5:7:5:7 | A | tst.js:5:18:5:18 | B | true |
|
||||
| tst.js:6:7:6:22 | _.includes(A, B) | tst.js:6:18:6:18 | A | tst.js:6:21:6:21 | B | true |
|
||||
| tst.js:7:7:7:25 | A.indexOf(B) !== -1 | tst.js:7:7:7:7 | A | tst.js:7:17:7:17 | B | true |
|
||||
| tst.js:8:7:8:23 | A.indexOf(B) >= 0 | tst.js:8:7:8:7 | A | tst.js:8:17:8:17 | B | true |
|
||||
| tst.js:9:7:9:19 | ~A.indexOf(B) | tst.js:9:8:9:8 | A | tst.js:9:18:9:18 | B | true |
|
||||
| tst.js:12:7:12:25 | A.indexOf(B) === -1 | tst.js:12:7:12:7 | A | tst.js:12:17:12:17 | B | false |
|
||||
| tst.js:13:7:13:22 | A.indexOf(B) < 0 | tst.js:13:7:13:7 | A | tst.js:13:17:13:17 | B | false |
|
||||
| tst.js:20:7:20:28 | strings ... s(A, B) | tst.js:20:24:20:24 | A | tst.js:20:27:20:27 | B | true |
|
||||
| tst.js:21:7:21:43 | strings ... s(A, B) | tst.js:21:39:21:39 | A | tst.js:21:42:21:42 | B | true |
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as _ from 'lodash';
|
||||
let strings = goog.require('goog.string');
|
||||
|
||||
function test() {
|
||||
if (A.includes(B)) {}
|
||||
@@ -15,4 +16,7 @@ function test() {
|
||||
if (A.indexOf(B) === 0) {}
|
||||
if (A.indexOf(B) !== 0) {}
|
||||
if (A.indexOf(B) > 0) {}
|
||||
|
||||
if (strings.contains(A, B)) {}
|
||||
if (strings.caseInsensitiveContains(A, B)) {}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
| tst.js:5:9:5:23 | A.startsWith(B) | tst.js:5:9:5:9 | A | tst.js:5:22:5:22 | B | true |
|
||||
| tst.js:6:9:6:26 | _.startsWith(A, B) | tst.js:6:22:6:22 | A | tst.js:6:25:6:25 | B | true |
|
||||
| tst.js:7:9:7:26 | R.startsWith(A, B) | tst.js:7:22:7:22 | A | tst.js:7:25:7:25 | B | true |
|
||||
| tst.js:8:9:8:26 | A.indexOf(B) === 0 | tst.js:8:9:8:9 | A | tst.js:8:19:8:19 | B | true |
|
||||
| tst.js:9:9:9:26 | A.indexOf(B) !== 0 | tst.js:9:9:9:9 | A | tst.js:9:19:9:19 | B | false |
|
||||
| tst.js:10:9:10:26 | 0 !== A.indexOf(B) | tst.js:10:15:10:15 | A | tst.js:10:25:10:25 | B | false |
|
||||
| tst.js:11:9:11:25 | 0 != A.indexOf(B) | tst.js:11:14:11:14 | A | tst.js:11:24:11:24 | B | false |
|
||||
| tst.js:12:9:12:20 | A.indexOf(B) | tst.js:12:9:12:9 | A | tst.js:12:19:12:19 | B | false |
|
||||
| tst.js:13:10:13:21 | A.indexOf(B) | tst.js:13:10:13:10 | A | tst.js:13:20:13:20 | B | false |
|
||||
| tst.js:14:11:14:22 | A.indexOf(B) | tst.js:14:11:14:11 | A | tst.js:14:21:14:21 | B | false |
|
||||
| tst.js:15:9:15:38 | A.subst ... ) === B | tst.js:15:9:15:9 | A | tst.js:15:38:15:38 | B | true |
|
||||
| tst.js:16:9:16:38 | A.subst ... ) !== B | tst.js:16:9:16:9 | A | tst.js:16:38:16:38 | B | false |
|
||||
| tst.js:17:9:17:35 | A.subst ... ) === B | tst.js:17:9:17:9 | A | tst.js:17:35:17:35 | B | true |
|
||||
| tst.js:18:9:18:36 | A.subst ... "web/" | tst.js:18:9:18:9 | A | tst.js:18:31:18:36 | "web/" | true |
|
||||
| tst.js:6:9:6:23 | A.startsWith(B) | tst.js:6:9:6:9 | A | tst.js:6:22:6:22 | B | true |
|
||||
| tst.js:7:9:7:26 | _.startsWith(A, B) | tst.js:7:22:7:22 | A | tst.js:7:25:7:25 | B | true |
|
||||
| tst.js:8:9:8:26 | R.startsWith(A, B) | tst.js:8:22:8:22 | A | tst.js:8:25:8:25 | B | true |
|
||||
| tst.js:9:9:9:26 | A.indexOf(B) === 0 | tst.js:9:9:9:9 | A | tst.js:9:19:9:19 | B | true |
|
||||
| tst.js:10:9:10:26 | A.indexOf(B) !== 0 | tst.js:10:9:10:9 | A | tst.js:10:19:10:19 | B | false |
|
||||
| tst.js:11:9:11:26 | 0 !== A.indexOf(B) | tst.js:11:15:11:15 | A | tst.js:11:25:11:25 | B | false |
|
||||
| tst.js:12:9:12:25 | 0 != A.indexOf(B) | tst.js:12:14:12:14 | A | tst.js:12:24:12:24 | B | false |
|
||||
| tst.js:13:9:13:20 | A.indexOf(B) | tst.js:13:9:13:9 | A | tst.js:13:19:13:19 | B | false |
|
||||
| tst.js:14:10:14:21 | A.indexOf(B) | tst.js:14:10:14:10 | A | tst.js:14:20:14:20 | B | false |
|
||||
| tst.js:15:11:15:22 | A.indexOf(B) | tst.js:15:11:15:11 | A | tst.js:15:21:15:21 | B | false |
|
||||
| tst.js:16:9:16:38 | A.subst ... ) === B | tst.js:16:9:16:9 | A | tst.js:16:38:16:38 | B | true |
|
||||
| tst.js:17:9:17:38 | A.subst ... ) !== B | tst.js:17:9:17:9 | A | tst.js:17:38:17:38 | B | false |
|
||||
| tst.js:18:9:18:35 | A.subst ... ) === B | tst.js:18:9:18:9 | A | tst.js:18:35:18:35 | B | true |
|
||||
| tst.js:19:9:19:36 | A.subst ... "web/" | tst.js:19:9:19:9 | A | tst.js:19:31:19:36 | "web/" | true |
|
||||
| tst.js:32:9:32:32 | strings ... h(A, B) | tst.js:32:28:32:28 | A | tst.js:32:31:32:31 | B | true |
|
||||
| tst.js:33:9:33:47 | strings ... h(A, B) | tst.js:33:43:33:43 | A | tst.js:33:46:33:46 | B | true |
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
let strings = goog.require('goog.string');
|
||||
|
||||
function f(A, B) {
|
||||
if (A.startsWith(B)) {}
|
||||
@@ -27,4 +28,7 @@ function f(A, B) {
|
||||
if (A.indexOf(B, 2)) {}
|
||||
if (~A.indexOf(B)) {} // checks for existence, not startsWith
|
||||
if (A.substring(B.length) === 0) {}
|
||||
|
||||
if (strings.startsWith(A, B)) {}
|
||||
if (strings.caseInsensitiveStartsWith(A, B)) {}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
| tst.js:11:5:11:8 | f1() | file://:0:0:0:0 | undefined |
|
||||
| tst.js:14:5:14:8 | f2() | file://:0:0:0:0 | undefined |
|
||||
| tst.js:15:5:15:8 | f2() | file://:0:0:0:0 | undefined |
|
||||
| tst.js:18:5:18:11 | o1.f3() | file://:0:0:0:0 | undefined |
|
||||
| tst.js:23:5:23:8 | f4() | tst.js:21:16:21:30 | function f5 |
|
||||
| tst.js:23:5:23:10 | f4()() | file://:0:0:0:0 | undefined |
|
||||
| tst.js:30:5:30:8 | f6() | tst.js:26:16:28:9 | function f7 |
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
| tst.js:11:5:11:8 | f1() | file://:0:0:0:0 | undefined |
|
||||
| tst.js:14:5:14:8 | f2() | file://:0:0:0:0 | undefined |
|
||||
| tst.js:15:5:15:8 | f2() | file://:0:0:0:0 | undefined |
|
||||
| tst.js:18:5:18:11 | o1.f3() | file://:0:0:0:0 | indefinite value (call) |
|
||||
| tst.js:18:5:18:11 | o1.f3() | file://:0:0:0:0 | undefined |
|
||||
| tst.js:23:5:23:8 | f4() | tst.js:21:16:21:30 | function f5 |
|
||||
| tst.js:23:5:23:10 | f4()() | file://:0:0:0:0 | undefined |
|
||||
| tst.js:30:5:30:8 | f6() | tst.js:26:16:28:9 | function f7 |
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
| tst.js:4:9:4:19 | unused1: 42 | Unused property unused1. |
|
||||
| tst.js:19:5:19:15 | unused9: 42 | Unused property unused9. |
|
||||
| tst.js:26:13:26:24 | unused11: 42 | Unused property unused11. |
|
||||
| tst.js:31:13:31:35 | used12_ ... lly: 42 | Unused property used12_butNotReally. |
|
||||
| tst.js:32:13:32:24 | unused12: 42 | Unused property unused12. |
|
||||
| tst.js:52:3:52:14 | unused14: 42 | Unused property unused14. |
|
||||
| tst.js:54:2:54:20 | captured14.unused14 | Unused property unused14. |
|
||||
| tst.js:55:2:55:20 | captured14.unused14 | Unused property unused14. |
|
||||
| tst.ts:24:21:24:25 | p: 42 | Unused property p. |
|
||||
@@ -0,0 +1 @@
|
||||
Declarations/UnusedProperty.ql
|
||||
@@ -0,0 +1,83 @@
|
||||
(function(){
|
||||
var captured1 = {
|
||||
used1: 42,
|
||||
unused1: 42
|
||||
};
|
||||
captured1.used1;
|
||||
|
||||
var unused2 = {
|
||||
unused2a: 42,
|
||||
unused2b: 42
|
||||
};
|
||||
|
||||
for (x.p in { used3: 42 });
|
||||
for (x.p of { used4: 42 });
|
||||
42 in { used5: 42 };
|
||||
f(...{used6: 42});
|
||||
[...{used7: 42}];
|
||||
({...{used8: 42}});
|
||||
({ unused9: 42 }) + "";
|
||||
({ used10: 42 }).hasOwnProperty;
|
||||
({ used10: 42 }).propertyIsEnumerable;
|
||||
|
||||
(function(){
|
||||
var captured11 = {
|
||||
used11: 42,
|
||||
unused11: 42
|
||||
};
|
||||
captured11.used11;
|
||||
|
||||
var captured12 = {
|
||||
used12_butNotReally: 42,
|
||||
unused12: 42
|
||||
};
|
||||
|
||||
throw x;
|
||||
|
||||
captured12.used12_butNotReally;
|
||||
|
||||
var captured13 = {
|
||||
used13: 42,
|
||||
unused13: 42
|
||||
};
|
||||
captured13.used13;
|
||||
});
|
||||
(function(options){
|
||||
if(unknown)
|
||||
options = {};
|
||||
options.output = 42;
|
||||
});
|
||||
|
||||
var captured14 = {
|
||||
unused14: 42
|
||||
};
|
||||
captured14.unused14 = 42;
|
||||
captured14.unused14 = 42;
|
||||
|
||||
|
||||
var captured15 = {
|
||||
semiUnused15: 42
|
||||
};
|
||||
captured15.semiUnused15 = 42;
|
||||
captured15.semiUnused15;
|
||||
});
|
||||
(function(unusedParam = {unusedProp: 42}){
|
||||
|
||||
});
|
||||
(function(){
|
||||
var unusedObj = {
|
||||
unusedProp: 42
|
||||
};
|
||||
});
|
||||
(function(){
|
||||
var unusedSpecials = {
|
||||
toString: function(){},
|
||||
valueOf: function(){},
|
||||
'@@iterator': function(){}
|
||||
};
|
||||
unusedSpecials.foo;
|
||||
});
|
||||
|
||||
(function(){
|
||||
({ unusedProp: 42 }, 42);
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
(function(){
|
||||
var o1: { p: int, q: int } = { p: 42, q: 42 };
|
||||
o1.q;
|
||||
|
||||
var o2 = <{ p: int, q: int }>{ p: 42, q: 42 };
|
||||
o2.q;
|
||||
|
||||
var o3: { p: int, q: int } = f();
|
||||
o3 = o3 || { p: 42, q: 42 };
|
||||
o3.q;
|
||||
|
||||
});
|
||||
|
||||
class C {
|
||||
private o: { p: int, q: int };
|
||||
|
||||
constructor() {
|
||||
this.o = { p: 42, q: 42 };
|
||||
this.o.q;
|
||||
}
|
||||
}
|
||||
|
||||
(function(){
|
||||
var o1: any = { p: 42, q: 42 };
|
||||
o1.q;
|
||||
var o2: any = { p: 42, q: 42 };
|
||||
var o3: { p: int, q: int } = o2;
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
(function(){
|
||||
throw 42;
|
||||
var x = 42;
|
||||
});
|
||||
@@ -8,3 +8,4 @@
|
||||
| tst.js:55:32:55:71 | x.index ... gth - 1 | This suffix check is missing a length comparison to correctly handle indexOf returning -1. |
|
||||
| tst.js:67:32:67:71 | x.index ... gth - 1 | This suffix check is missing a length comparison to correctly handle indexOf returning -1. |
|
||||
| tst.js:76:25:76:57 | index = ... gth - 1 | This suffix check is missing a length comparison to correctly handle indexOf returning -1. |
|
||||
| tst.js:80:10:80:57 | x.index ... th + 1) | This suffix check is missing a length comparison to correctly handle indexOf returning -1. |
|
||||
|
||||
@@ -75,3 +75,7 @@ function withIndexOfCheckBad(x, y) {
|
||||
let index = x.indexOf(y);
|
||||
return index !== 0 && index === x.length - y.length - 1; // NOT OK
|
||||
}
|
||||
|
||||
function plus(x, y) {
|
||||
return x.indexOf("." + y) === x.length - (y.length + 1); // NOT OK
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
nodes
|
||||
| ZipSlipBad2.js:5:9:5:46 | fileName |
|
||||
| ZipSlipBad2.js:5:20:5:46 | 'output ... ry.path |
|
||||
| ZipSlipBad2.js:5:37:5:46 | entry.path |
|
||||
| ZipSlipBad2.js:6:22:6:29 | fileName |
|
||||
| ZipSlipBad.js:7:11:7:31 | fileName |
|
||||
| ZipSlipBad.js:7:22:7:31 | entry.path |
|
||||
| ZipSlipBad.js:8:37:8:44 | fileName |
|
||||
edges
|
||||
| ZipSlipBad2.js:5:9:5:46 | fileName | ZipSlipBad2.js:6:22:6:29 | fileName |
|
||||
| ZipSlipBad2.js:5:20:5:46 | 'output ... ry.path | ZipSlipBad2.js:5:9:5:46 | fileName |
|
||||
| ZipSlipBad2.js:5:37:5:46 | entry.path | ZipSlipBad2.js:5:20:5:46 | 'output ... ry.path |
|
||||
| ZipSlipBad.js:7:11:7:31 | fileName | ZipSlipBad.js:8:37:8:44 | fileName |
|
||||
| ZipSlipBad.js:7:22:7:31 | entry.path | ZipSlipBad.js:7:11:7:31 | fileName |
|
||||
#select
|
||||
| ZipSlipBad2.js:6:22:6:29 | fileName | ZipSlipBad2.js:5:37:5:46 | entry.path | ZipSlipBad2.js:6:22:6:29 | fileName | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlipBad2.js:5:37:5:46 | entry.path | item path |
|
||||
| ZipSlipBad.js:8:37:8:44 | fileName | ZipSlipBad.js:7:22:7:31 | entry.path | ZipSlipBad.js:8:37:8:44 | fileName | Unsanitized zip archive $@, which may contain '..', is used in a file system operation. | ZipSlipBad.js:7:22:7:31 | entry.path | item path |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-022/ZipSlip.ql
|
||||
@@ -0,0 +1,9 @@
|
||||
const fs = require('fs');
|
||||
const unzip = require('unzip');
|
||||
|
||||
fs.createReadStream('archive.zip')
|
||||
.pipe(unzip.Parse())
|
||||
.on('entry', entry => {
|
||||
const fileName = entry.path;
|
||||
entry.pipe(fs.createWriteStream(fileName));
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
var fs = require('fs');
|
||||
var unzip = require('unzip');
|
||||
fs.readFile('path/to/archive.zip', function (err, zipContents) {
|
||||
unzip.Parse(zipContents).on('entry', function (entry) {
|
||||
var fileName = 'output/path/' + entry.path;
|
||||
fs.writeFileSync(fileName, entry.contents);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
const fs = require('fs');
|
||||
const unzip = require('unzip');
|
||||
|
||||
fs.createReadStream('archive.zip')
|
||||
.pipe(unzip.Parse())
|
||||
.on('entry', entry => {
|
||||
const fileName = entry.path;
|
||||
if (fileName.indexOf('..') == -1) {
|
||||
entry.pipe(fs.createWriteStream(fileName));
|
||||
}
|
||||
else {
|
||||
console.log('skipping bad path', fileName);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @externs
|
||||
*/
|
||||
var fs = {};
|
||||
|
||||
/**
|
||||
* @param {string} filename
|
||||
* @param {*} data
|
||||
* @return {void}
|
||||
*/
|
||||
fs.writeFileSync = function(filename, data) {};
|
||||
Reference in New Issue
Block a user