mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Merge branch 'main' into js/no-type-extraction
This commit is contained in:
@@ -31,7 +31,6 @@ ql/javascript/ql/src/Security/CWE-079/Xss.ql
|
||||
ql/javascript/ql/src/Security/CWE-079/XssThroughDom.ql
|
||||
ql/javascript/ql/src/Security/CWE-089/SqlInjection.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/CodeInjection.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/ExpressionInjection.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/ImproperCodeSanitization.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/UnsafeDynamicMethodAccess.ql
|
||||
ql/javascript/ql/src/Security/CWE-1004/ClientExposedCookie.ql
|
||||
@@ -48,7 +47,6 @@ ql/javascript/ql/src/Security/CWE-201/PostMessageStar.ql
|
||||
ql/javascript/ql/src/Security/CWE-209/StackTraceExposure.ql
|
||||
ql/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql
|
||||
ql/javascript/ql/src/Security/CWE-300/InsecureDependencyResolution.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/ActionsArtifactLeak.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/BuildArtifactLeak.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/CleartextLogging.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/CleartextStorage.ql
|
||||
|
||||
@@ -119,7 +119,6 @@ ql/javascript/ql/src/Security/CWE-079/Xss.ql
|
||||
ql/javascript/ql/src/Security/CWE-079/XssThroughDom.ql
|
||||
ql/javascript/ql/src/Security/CWE-089/SqlInjection.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/CodeInjection.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/ExpressionInjection.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/ImproperCodeSanitization.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/UnsafeCodeConstruction.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/UnsafeDynamicMethodAccess.ql
|
||||
@@ -140,7 +139,6 @@ ql/javascript/ql/src/Security/CWE-201/PostMessageStar.ql
|
||||
ql/javascript/ql/src/Security/CWE-209/StackTraceExposure.ql
|
||||
ql/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql
|
||||
ql/javascript/ql/src/Security/CWE-300/InsecureDependencyResolution.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/ActionsArtifactLeak.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/BuildArtifactLeak.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/CleartextLogging.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/CleartextStorage.ql
|
||||
|
||||
@@ -34,7 +34,6 @@ ql/javascript/ql/src/Security/CWE-079/Xss.ql
|
||||
ql/javascript/ql/src/Security/CWE-079/XssThroughDom.ql
|
||||
ql/javascript/ql/src/Security/CWE-089/SqlInjection.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/CodeInjection.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/ExpressionInjection.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/ImproperCodeSanitization.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/UnsafeCodeConstruction.ql
|
||||
ql/javascript/ql/src/Security/CWE-094/UnsafeDynamicMethodAccess.ql
|
||||
@@ -55,7 +54,6 @@ ql/javascript/ql/src/Security/CWE-201/PostMessageStar.ql
|
||||
ql/javascript/ql/src/Security/CWE-209/StackTraceExposure.ql
|
||||
ql/javascript/ql/src/Security/CWE-295/DisablingCertificateValidation.ql
|
||||
ql/javascript/ql/src/Security/CWE-300/InsecureDependencyResolution.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/ActionsArtifactLeak.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/BuildArtifactLeak.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/CleartextLogging.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/CleartextStorage.ql
|
||||
|
||||
@@ -67,7 +67,6 @@ ql/javascript/ql/src/Summary/TaintSinks.ql
|
||||
ql/javascript/ql/src/Summary/TaintSources.ql
|
||||
ql/javascript/ql/src/definitions.ql
|
||||
ql/javascript/ql/src/experimental/Security/CWE-094-dataURL/CodeInjection.ql
|
||||
ql/javascript/ql/src/experimental/Security/CWE-094/UntrustedCheckout.ql
|
||||
ql/javascript/ql/src/experimental/Security/CWE-099/EnvValueAndKeyInjection.ql
|
||||
ql/javascript/ql/src/experimental/Security/CWE-099/EnvValueInjection.ql
|
||||
ql/javascript/ql/src/experimental/Security/CWE-340/TokenBuiltFromUUID.ql
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
## 2.6.6
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Calls to `sinon.match()` are no longer incorrectly identified as regular expression operations.
|
||||
* Improved data flow tracking through middleware to handle default value and similar patterns.
|
||||
* Added `req._parsedUrl` as a remote input source.
|
||||
* Improved taint tracking through calls to `serialize-javascript`.
|
||||
* Removed `encodeURI` and `escape` functions from the sanitizer list for request forgery.
|
||||
* The JavaScript extractor now skips generated JavaScript files if the original TypeScript files are already present. It also skips any files in the output directory specified in the `compilerOptions` part of the `tsconfig.json` file.
|
||||
* Added support for Axios instances in the `axios` module.
|
||||
|
||||
## 2.6.5
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The JavaScript extractor now skips generated JavaScript files if the original TypeScript files are already present. It also skips any files in the output directory specified in the `compilerOptions` part of the `tsconfig.json` file.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Removed `encodeURI` and `escape` functions from the sanitizer list for request forgery.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved data flow tracking through middleware to handle default value and similar patterns.
|
||||
* Added `req._parsedUrl` as a remote input source.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved taint tracking through calls to `serialize-javascript`.
|
||||
4
javascript/ql/lib/change-notes/2025-06-20-execa.md
Normal file
4
javascript/ql/lib/change-notes/2025-06-20-execa.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Enhanced modeling for the `execa` library, adding support for command execution methods `execaCommand`, `execaCommandSync`, `$`, and `$.sync`, as well as file system operations through `inputFile`, `pipeStdout`, `pipeAll`, and `pipeStderr`.
|
||||
11
javascript/ql/lib/change-notes/released/2.6.6.md
Normal file
11
javascript/ql/lib/change-notes/released/2.6.6.md
Normal file
@@ -0,0 +1,11 @@
|
||||
## 2.6.6
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Calls to `sinon.match()` are no longer incorrectly identified as regular expression operations.
|
||||
* Improved data flow tracking through middleware to handle default value and similar patterns.
|
||||
* Added `req._parsedUrl` as a remote input source.
|
||||
* Improved taint tracking through calls to `serialize-javascript`.
|
||||
* Removed `encodeURI` and `escape` functions from the sanitizer list for request forgery.
|
||||
* The JavaScript extractor now skips generated JavaScript files if the original TypeScript files are already present. It also skips any files in the output directory specified in the `compilerOptions` part of the `tsconfig.json` file.
|
||||
* Added support for Axios instances in the `axios` module.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 2.6.5
|
||||
lastReleaseVersion: 2.6.6
|
||||
|
||||
6
javascript/ql/lib/ext/react.model.yml
Normal file
6
javascript/ql/lib/ext/react.model.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
extensions:
|
||||
- addsTo:
|
||||
pack: codeql/javascript-all
|
||||
extensible: summaryModel
|
||||
data:
|
||||
- ["react", "Member[use]", "Argument[0].Awaited", "ReturnValue", "value"]
|
||||
@@ -92,6 +92,7 @@ import semmle.javascript.frameworks.DigitalOcean
|
||||
import semmle.javascript.frameworks.DomEvents
|
||||
import semmle.javascript.frameworks.Electron
|
||||
import semmle.javascript.frameworks.EventEmitter
|
||||
import semmle.javascript.frameworks.Execa
|
||||
import semmle.javascript.frameworks.Files
|
||||
import semmle.javascript.frameworks.Firebase
|
||||
import semmle.javascript.frameworks.FormParsers
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-all
|
||||
version: 2.6.6-dev
|
||||
version: 2.6.7-dev
|
||||
groups: javascript
|
||||
dbscheme: semmlecode.javascript.dbscheme
|
||||
extractor: javascript
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/**
|
||||
* PENDING DEPRECATION. Models for GitHub Actions workflow files are part of the actions qlpack now.
|
||||
*
|
||||
* Libraries for modeling GitHub Actions workflow files written in YAML.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
||||
*/
|
||||
|
||||
@@ -998,6 +998,8 @@ private predicate isUsedAsNonMatchObject(DataFlow::MethodCallNode call) {
|
||||
or
|
||||
// Result is obviously unused
|
||||
call.asExpr() = any(ExprStmt stmt).getExpr()
|
||||
or
|
||||
call = API::moduleImport("sinon").getMember("match").getACall()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ module ClientRequest {
|
||||
method = "request" and
|
||||
result = this.getOptionArgument(0, "data")
|
||||
or
|
||||
method = ["post", "put"] and
|
||||
method = ["post", "put", "patch"] and
|
||||
result = [this.getArgument(1), this.getOptionArgument(2, "data")]
|
||||
or
|
||||
method = ["postForm", "putForm", "patchForm"] and result = this.getArgument(1)
|
||||
@@ -289,6 +289,69 @@ module ClientRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A model of a `axios` instance request.
|
||||
*/
|
||||
class AxiosInstanceRequest extends ClientRequest::Range, API::CallNode {
|
||||
string method;
|
||||
API::CallNode instance;
|
||||
|
||||
// Instances of axios, e.g. `axios.create({ ... })`
|
||||
AxiosInstanceRequest() {
|
||||
instance = axios().getMember(["create", "createInstance"]).getACall() and
|
||||
method = [httpMethodName(), "request", "postForm", "putForm", "patchForm", "getUri"] and
|
||||
this = instance.getReturn().getMember(method).getACall()
|
||||
}
|
||||
|
||||
private int getOptionsArgIndex() {
|
||||
(method = "get" or method = "delete" or method = "head") and
|
||||
result = 0
|
||||
or
|
||||
(method = "post" or method = "put" or method = "patch") and
|
||||
result = 1
|
||||
}
|
||||
|
||||
private DataFlow::Node getOptionArgument(string name) {
|
||||
result = this.getOptionArgument(this.getOptionsArgIndex(), name)
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrl() {
|
||||
result = this.getArgument(0) or
|
||||
result = this.getOptionArgument(urlPropertyName())
|
||||
}
|
||||
|
||||
override DataFlow::Node getHost() { result = instance.getOptionArgument(0, "baseURL") }
|
||||
|
||||
override DataFlow::Node getADataNode() {
|
||||
method = ["post", "put", "patch"] and
|
||||
result = [this.getArgument(1), this.getOptionArgument(2, "data")]
|
||||
or
|
||||
method = ["postForm", "putForm", "patchForm"] and result = this.getArgument(1)
|
||||
or
|
||||
result = this.getOptionArgument([0 .. 2], ["headers", "params"])
|
||||
}
|
||||
|
||||
/** Gets the response type from the options passed in. */
|
||||
string getResponseType() {
|
||||
exists(DataFlow::Node option | option = instance.getOptionArgument(0, "responseType") |
|
||||
option.mayHaveStringValue(result)
|
||||
)
|
||||
or
|
||||
not exists(this.getOptionArgument("responseType")) and
|
||||
result = "json"
|
||||
}
|
||||
|
||||
override DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
|
||||
responseType = this.getResponseType() and
|
||||
promise = true and
|
||||
result = this
|
||||
or
|
||||
responseType = this.getResponseType() and
|
||||
promise = false and
|
||||
result = this.getReturn().getPromisedError().getMember("response").asSource()
|
||||
}
|
||||
}
|
||||
|
||||
/** An expression that is used as a credential in a request. */
|
||||
private class AuthorizationHeader extends CredentialsNode {
|
||||
AuthorizationHeader() {
|
||||
|
||||
@@ -58,6 +58,9 @@ module Execa {
|
||||
or
|
||||
this = API::moduleImport("execa").getMember("execaSync").getACall() and
|
||||
isSync = true
|
||||
or
|
||||
this = API::moduleImport("execa").getACall() and
|
||||
isSync = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,4 +211,86 @@ module Execa {
|
||||
private predicate isExecaShellEnable(API::Node n) {
|
||||
n.getMember("shell").asSink().asExpr().(BooleanLiteral).getValue() = "true"
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `execa.node`
|
||||
*/
|
||||
class ExecaNodeCall extends SystemCommandExecution, API::CallNode {
|
||||
ExecaNodeCall() { this = API::moduleImport("execa").getMember("node").getACall() }
|
||||
|
||||
override DataFlow::Node getACommandArgument() { result = this.getArgument(0) }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { none() }
|
||||
|
||||
override DataFlow::Node getArgumentList() {
|
||||
result = this.getArgument(1) and
|
||||
not result.asExpr() instanceof ObjectExpr
|
||||
}
|
||||
|
||||
override predicate isSync() { none() }
|
||||
|
||||
override DataFlow::Node getOptionsArg() {
|
||||
result = this.getLastArgument() and
|
||||
result.asExpr() instanceof ObjectExpr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `execa.stdout`, `execa.stderr`, or `execa.sync`
|
||||
*/
|
||||
class ExecaStreamCall extends SystemCommandExecution, API::CallNode {
|
||||
string methodName;
|
||||
|
||||
ExecaStreamCall() {
|
||||
methodName in ["stdout", "stderr", "sync"] and
|
||||
this = API::moduleImport("execa").getMember(methodName).getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getACommandArgument() { result = this.getArgument(0) }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
arg = this.getArgument(0) and
|
||||
isExecaShellEnable(this.getParameter([1, 2]))
|
||||
}
|
||||
|
||||
override DataFlow::Node getArgumentList() {
|
||||
result = this.getArgument(1) and
|
||||
not result.asExpr() instanceof ObjectExpr
|
||||
}
|
||||
|
||||
override predicate isSync() { methodName = "sync" }
|
||||
|
||||
override DataFlow::Node getOptionsArg() {
|
||||
result = this.getLastArgument() and
|
||||
result.asExpr() instanceof ObjectExpr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `execa.shell` or `execa.shellSync`
|
||||
*/
|
||||
class ExecaShellCall extends SystemCommandExecution, API::CallNode {
|
||||
boolean sync;
|
||||
|
||||
ExecaShellCall() {
|
||||
this = API::moduleImport("execa").getMember("shell").getACall() and
|
||||
sync = false
|
||||
or
|
||||
this = API::moduleImport("execa").getMember("shellSync").getACall() and
|
||||
sync = true
|
||||
}
|
||||
|
||||
override DataFlow::Node getACommandArgument() { result = this.getArgument(0) }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getACommandArgument() }
|
||||
|
||||
override DataFlow::Node getArgumentList() { none() }
|
||||
|
||||
override predicate isSync() { sync = true }
|
||||
|
||||
override DataFlow::Node getOptionsArg() {
|
||||
result = this.getArgument(1) and
|
||||
result.asExpr() instanceof ObjectExpr
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,6 +446,61 @@ module NestJS {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A NestJS Middleware Class
|
||||
*/
|
||||
private class NestMiddlewareClass extends DataFlow::ClassNode {
|
||||
NestMiddlewareClass() {
|
||||
exists(ClassDefinition cls |
|
||||
this = cls.flow() and
|
||||
cls.getASuperInterface().hasUnderlyingType("@nestjs/common", "NestMiddleware")
|
||||
)
|
||||
}
|
||||
|
||||
DataFlow::FunctionNode getUseFunction() { result = this.getInstanceMethod("use") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A NestJS Middleware Class route handler (the `use` method)
|
||||
*/
|
||||
private class MiddlewareRouteHandler extends Http::RouteHandler, DataFlow::FunctionNode {
|
||||
MiddlewareRouteHandler() { this = any(NestMiddlewareClass m).getUseFunction() }
|
||||
|
||||
override Http::HeaderDefinition getAResponseHeader(string name) { none() }
|
||||
|
||||
/**
|
||||
* Gets the request object used by this route
|
||||
*/
|
||||
DataFlow::ParameterNode getRequest() { result = this.getParameter(0) }
|
||||
|
||||
/**
|
||||
* Gets the response object used by this route
|
||||
*/
|
||||
DataFlow::ParameterNode getResponse() { result = this.getParameter(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of `express` request objects for NestJS middlewares
|
||||
*/
|
||||
private class MiddlewareRequestSource extends Express::RequestSource {
|
||||
MiddlewareRouteHandler middlewareRouteHandler;
|
||||
|
||||
MiddlewareRequestSource() { this = middlewareRouteHandler.getRequest() }
|
||||
|
||||
override Http::RouteHandler getRouteHandler() { result = middlewareRouteHandler }
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of `express` response objects for NestJS middlewares
|
||||
*/
|
||||
private class MiddlewareResponseSource extends Express::ResponseSource {
|
||||
MiddlewareRouteHandler middlewareRouteHandler;
|
||||
|
||||
MiddlewareResponseSource() { this = middlewareRouteHandler.getResponse() }
|
||||
|
||||
override Http::RouteHandler getRouteHandler() { result = middlewareRouteHandler }
|
||||
}
|
||||
|
||||
/**
|
||||
* A value passed in the `providers` array in:
|
||||
* ```js
|
||||
@@ -454,21 +509,53 @@ module NestJS {
|
||||
* ```
|
||||
*/
|
||||
private DataFlow::Node providerTuple() {
|
||||
result =
|
||||
DataFlow::moduleImport("@nestjs/common")
|
||||
.getAPropertyRead("Module")
|
||||
.getACall()
|
||||
.getOptionArgument(0, "providers")
|
||||
.getALocalSource()
|
||||
.(DataFlow::ArrayCreationNode)
|
||||
.getAnElement()
|
||||
exists(DataFlow::CallNode moduleCall |
|
||||
moduleCall = DataFlow::moduleImport("@nestjs/common").getAPropertyRead("Module").getACall() and
|
||||
result = providerTupleAux(moduleCall.getArgument(0).getALocalSource())
|
||||
)
|
||||
}
|
||||
|
||||
private DataFlow::Node providerTupleAux(DataFlow::ObjectLiteralNode o) {
|
||||
(
|
||||
result =
|
||||
o.getAPropertyWrite("providers")
|
||||
.getRhs()
|
||||
.getALocalSource()
|
||||
.(DataFlow::ArrayCreationNode)
|
||||
.getAnElement()
|
||||
or
|
||||
result =
|
||||
providerTupleAux(o.getAPropertyWrite("imports")
|
||||
.getRhs()
|
||||
.getALocalSource()
|
||||
.(DataFlow::ArrayCreationNode)
|
||||
.getAnElement()
|
||||
.(DataFlow::CallNode)
|
||||
.getCalleeNode()
|
||||
.getAFunctionValue()
|
||||
.getFunction()
|
||||
.getAReturnedExpr()
|
||||
.flow())
|
||||
)
|
||||
}
|
||||
|
||||
private DataFlow::Node getConcreteClassFromProviderTuple(DataFlow::SourceNode tuple) {
|
||||
result = tuple.getAPropertyWrite("useClass").getRhs()
|
||||
or
|
||||
exists(DataFlow::FunctionNode f |
|
||||
f = tuple.getAPropertyWrite("useFactory").getRhs().getAFunctionValue() and
|
||||
result.getAstNode() = f.getFunction().getAReturnedExpr().getType().(ClassType).getClass()
|
||||
)
|
||||
or
|
||||
result.getAstNode() =
|
||||
tuple.getAPropertyWrite("useValue").getRhs().asExpr().getType().(ClassType).getClass()
|
||||
}
|
||||
|
||||
private predicate providerPair(DataFlow::Node interface, DataFlow::Node concreteClass) {
|
||||
exists(DataFlow::SourceNode tuple |
|
||||
tuple = providerTuple().getALocalSource() and
|
||||
interface = tuple.getAPropertyWrite("provide").getRhs() and
|
||||
concreteClass = tuple.getAPropertyWrite("useClass").getRhs()
|
||||
concreteClass = getConcreteClassFromProviderTuple(tuple)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -875,3 +875,22 @@ private class ReactPropAsViewComponentInput extends ViewComponentInput {
|
||||
|
||||
override string getSourceType() { result = "React props" }
|
||||
}
|
||||
|
||||
private predicate isServerFunction(DataFlow::FunctionNode func) {
|
||||
exists(Directive::UseServerDirective useServer |
|
||||
useServer.getContainer() = func.getFunction()
|
||||
or
|
||||
useServer.getContainer().(Module).getAnExportedValue(_).getAFunctionValue() = func
|
||||
)
|
||||
}
|
||||
|
||||
private class ServerFunctionRemoteFlowSource extends RemoteFlowSource {
|
||||
ServerFunctionRemoteFlowSource() {
|
||||
exists(DataFlow::FunctionNode func |
|
||||
isServerFunction(func) and
|
||||
this = func.getAParameter()
|
||||
)
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "React server function parameter" }
|
||||
}
|
||||
|
||||
@@ -16,17 +16,6 @@ private predicate execApi(
|
||||
cmdArg = 0 and
|
||||
shell = false and
|
||||
optionsArg = -1
|
||||
or
|
||||
mod = "execa" and
|
||||
optionsArg = -1 and
|
||||
(
|
||||
shell = false and
|
||||
fn = ["node", "stdout", "stderr", "sync"]
|
||||
or
|
||||
shell = true and
|
||||
fn = ["command", "commandSync", "shell", "shellSync"]
|
||||
) and
|
||||
cmdArg = 0
|
||||
)
|
||||
}
|
||||
|
||||
@@ -38,8 +27,6 @@ private predicate execApi(string mod, int cmdArg, int optionsArg, boolean shell)
|
||||
mod = "cross-spawn-async" and cmdArg = 0 and optionsArg = -1
|
||||
or
|
||||
mod = "exec-async" and cmdArg = 0 and optionsArg = -1
|
||||
or
|
||||
mod = "execa" and cmdArg = 0 and optionsArg = -1
|
||||
)
|
||||
or
|
||||
shell = true and
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
## 1.7.0
|
||||
|
||||
### Query Metadata Changes
|
||||
|
||||
* The `quality` tag has been added to multiple JavaScript quality queries, with tags for `reliability` or `maintainability` categories and their sub-categories. See [Query file metadata and alert message style guide](https://github.com/github/codeql/blob/main/docs/query-metadata-style-guide.md#quality-query-sub-category-tags) for more information about these categories.
|
||||
* Added `reliability` tag to the `js/suspicious-method-name-declaration` query.
|
||||
* Added `reliability` and `language-features` tags to the `js/template-syntax-in-string-literal` query.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `js/loop-iteration-skipped-due-to-shifting` query now has the `reliability` tag.
|
||||
* Fixed false positives in the `js/loop-iteration-skipped-due-to-shifting` query when the return value of `splice` is used to decide whether to adjust the loop counter.
|
||||
* Fixed false positives in the `js/template-syntax-in-string-literal` query where template syntax in string concatenation and "manual string interpolation" patterns were incorrectly flagged.
|
||||
* The `js/useless-expression` query now correctly flags only the innermost expressions with no effect, avoiding duplicate alerts on compound expressions.
|
||||
|
||||
## 1.6.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Using user-controlled input in GitHub Actions may lead to
|
||||
code injection in contexts like <i>run:</i> or <i>script:</i>.
|
||||
</p>
|
||||
<p>
|
||||
Code injection in GitHub Actions may allow an attacker to
|
||||
exfiltrate any secrets used in the workflow and
|
||||
the temporary GitHub repository authorization token.
|
||||
The token might have write access to the repository, allowing an attacker
|
||||
to use the token to make changes to the repository.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
The best practice to avoid code injection vulnerabilities
|
||||
in GitHub workflows is to set the untrusted input value of the expression
|
||||
to an intermediate environment variable and then use the environment variable
|
||||
using the native syntax of the shell/script interpreter (that is, not <i>${{ env.VAR }}</i>).
|
||||
</p>
|
||||
<p>
|
||||
It is also recommended to limit the permissions of any tokens used
|
||||
by a workflow such as the GITHUB_TOKEN.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example lets a user inject an arbitrary shell command:
|
||||
</p>
|
||||
<sample src="examples/comment_issue_bad.yml" />
|
||||
|
||||
<p>
|
||||
The following example uses an environment variable, but
|
||||
<b>still allows the injection</b> because of the use of expression syntax:
|
||||
</p>
|
||||
<sample src="examples/comment_issue_bad_env.yml" />
|
||||
|
||||
<p>
|
||||
The following example uses shell syntax to read
|
||||
the environment variable and will prevent the attack:
|
||||
</p>
|
||||
<sample src="examples/comment_issue_good.yml" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>GitHub Security Lab Research: <a href="https://securitylab.github.com/research/github-actions-untrusted-input">Keeping your GitHub Actions and workflows secure: Untrusted input</a>.</li>
|
||||
<li>GitHub Docs: <a href="https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions">Security hardening for GitHub Actions</a>.</li>
|
||||
<li>GitHub Docs: <a href="https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token">Permissions for the GITHUB_TOKEN</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,270 +0,0 @@
|
||||
/**
|
||||
* @name Expression injection in Actions
|
||||
* @description Using user-controlled GitHub Actions contexts like `run:` or `script:` may allow a malicious
|
||||
* user to inject code into the GitHub action.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 9.3
|
||||
* @precision high
|
||||
* @id js/actions/command-injection
|
||||
* @tags actions
|
||||
* security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.Actions
|
||||
|
||||
/**
|
||||
* A `script:` field within an Actions `with:` specific to `actions/github-script` action.
|
||||
*
|
||||
* For example:
|
||||
* ```
|
||||
* uses: actions/github-script@v3
|
||||
* with:
|
||||
* script: console.log('${{ github.event.pull_request.head.sha }}')
|
||||
* ```
|
||||
*/
|
||||
class GitHubScript extends YamlNode, YamlString {
|
||||
GitHubScriptWith with;
|
||||
|
||||
GitHubScript() { with.lookup("script") = this }
|
||||
|
||||
/** Gets the `with` field this field belongs to. */
|
||||
GitHubScriptWith getWith() { result = with }
|
||||
}
|
||||
|
||||
/**
|
||||
* A step that uses `actions/github-script` action.
|
||||
*/
|
||||
class GitHubScriptStep extends Actions::Step {
|
||||
GitHubScriptStep() { this.getUses().getGitHubRepository() = "actions/github-script" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `with:` field sibling to `uses: actions/github-script`.
|
||||
*/
|
||||
class GitHubScriptWith extends YamlNode, YamlMapping {
|
||||
GitHubScriptStep step;
|
||||
|
||||
GitHubScriptWith() { step.lookup("with") = this }
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
GitHubScriptStep getStep() { result = step }
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledIssue(string context) {
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*issue\\s*\\.\\s*title\\b") or
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*issue\\s*\\.\\s*body\\b")
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledPullRequest(string context) {
|
||||
exists(string reg |
|
||||
reg =
|
||||
[
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*title\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*body\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*label\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*repo\\s*\\.\\s*default_branch\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*repo\\s*\\.\\s*description\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*repo\\s*\\.\\s*homepage\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*ref\\b",
|
||||
"\\bgithub\\s*\\.\\s*head_ref\\b"
|
||||
]
|
||||
|
|
||||
context.regexpMatch(reg)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledReview(string context) {
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*review\\s*\\.\\s*body\\b")
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledComment(string context) {
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*comment\\s*\\.\\s*body\\b")
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledGollum(string context) {
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pages\\[[0-9]+\\]\\s*\\.\\s*page_name\\b") or
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pages\\[[0-9]+\\]\\s*\\.\\s*title\\b")
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledCommit(string context) {
|
||||
exists(string reg |
|
||||
reg =
|
||||
[
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits\\[[0-9]+\\]\\s*\\.\\s*message\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*message\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*name\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*committer\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*committer\\s*\\.\\s*name\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits\\[[0-9]+\\]\\s*\\.\\s*author\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits\\[[0-9]+\\]\\s*\\.\\s*author\\s*\\.\\s*name\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits\\[[0-9]+\\]\\s*\\.\\s*committer\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits\\[[0-9]+\\]\\s*\\.\\s*committer\\s*\\.\\s*name\\b",
|
||||
]
|
||||
|
|
||||
context.regexpMatch(reg)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledDiscussion(string context) {
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*discussion\\s*\\.\\s*title\\b") or
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*discussion\\s*\\.\\s*body\\b")
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledWorkflowRun(string context) {
|
||||
exists(string reg |
|
||||
reg =
|
||||
[
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_branch\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*display_title\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_repository\\b\\s*\\.\\s*description\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*message\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*author\\b\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*author\\b\\s*\\.\\s*name\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*committer\\b\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*committer\\b\\s*\\.\\s*name\\b",
|
||||
]
|
||||
|
|
||||
context.regexpMatch(reg)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if environment name in the `injection` (in a form of `env.name`)
|
||||
* is tainted by the `context` (in a form of `github.event.xxx.xxx`).
|
||||
*/
|
||||
bindingset[injection]
|
||||
predicate isEnvInterpolationTainted(string injection, string context) {
|
||||
exists(Actions::Env env, string envName, YamlString envValue |
|
||||
envValue = env.lookup(envName) and
|
||||
Actions::getEnvName(injection) = envName and
|
||||
Actions::getASimpleReferenceExpression(envValue) = context
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `run` contains any expression interpolation `${{ e }}`.
|
||||
* Sets `context` to the initial untrusted value assignment in case of `${{ env... }}` interpolation
|
||||
*/
|
||||
predicate isRunInjectable(Actions::Run run, string injection, string context) {
|
||||
Actions::getASimpleReferenceExpression(run) = injection and
|
||||
(
|
||||
injection = context
|
||||
or
|
||||
isEnvInterpolationTainted(injection, context)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `actions/github-script` contains any expression interpolation `${{ e }}`.
|
||||
* Sets `context` to the initial untrusted value assignment in case of `${{ env... }}` interpolation
|
||||
*/
|
||||
predicate isScriptInjectable(GitHubScript script, string injection, string context) {
|
||||
Actions::getASimpleReferenceExpression(script) = injection and
|
||||
(
|
||||
injection = context
|
||||
or
|
||||
isEnvInterpolationTainted(injection, context)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the composite action contains untrusted expression interpolation `${{ e }}`.
|
||||
*/
|
||||
YamlNode getInjectableCompositeActionNode(Actions::Runs runs, string injection, string context) {
|
||||
exists(Actions::Run run |
|
||||
isRunInjectable(run, injection, context) and
|
||||
result = run and
|
||||
run.getStep().getRuns() = runs
|
||||
)
|
||||
or
|
||||
exists(GitHubScript script |
|
||||
isScriptInjectable(script, injection, context) and
|
||||
result = script and
|
||||
script.getWith().getStep().getRuns() = runs
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the workflow contains untrusted expression interpolation `${{ e }}`.
|
||||
*/
|
||||
YamlNode getInjectableWorkflowNode(Actions::On on, string injection, string context) {
|
||||
exists(Actions::Run run |
|
||||
isRunInjectable(run, injection, context) and
|
||||
result = run and
|
||||
run.getStep().getJob().getWorkflow().getOn() = on
|
||||
)
|
||||
or
|
||||
exists(GitHubScript script |
|
||||
isScriptInjectable(script, injection, context) and
|
||||
result = script and
|
||||
script.getWith().getStep().getJob().getWorkflow().getOn() = on
|
||||
)
|
||||
}
|
||||
|
||||
from YamlNode node, string injection, string context
|
||||
where
|
||||
exists(Actions::CompositeAction action, Actions::Runs runs |
|
||||
action.getRuns() = runs and
|
||||
node = getInjectableCompositeActionNode(runs, injection, context) and
|
||||
(
|
||||
isExternalUserControlledIssue(context) or
|
||||
isExternalUserControlledPullRequest(context) or
|
||||
isExternalUserControlledReview(context) or
|
||||
isExternalUserControlledComment(context) or
|
||||
isExternalUserControlledGollum(context) or
|
||||
isExternalUserControlledCommit(context) or
|
||||
isExternalUserControlledDiscussion(context) or
|
||||
isExternalUserControlledWorkflowRun(context)
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(Actions::On on |
|
||||
node = getInjectableWorkflowNode(on, injection, context) and
|
||||
(
|
||||
exists(on.getNode("issues")) and
|
||||
isExternalUserControlledIssue(context)
|
||||
or
|
||||
exists(on.getNode("pull_request_target")) and
|
||||
isExternalUserControlledPullRequest(context)
|
||||
or
|
||||
exists(on.getNode("pull_request_review")) and
|
||||
(isExternalUserControlledReview(context) or isExternalUserControlledPullRequest(context))
|
||||
or
|
||||
exists(on.getNode("pull_request_review_comment")) and
|
||||
(isExternalUserControlledComment(context) or isExternalUserControlledPullRequest(context))
|
||||
or
|
||||
exists(on.getNode("issue_comment")) and
|
||||
(isExternalUserControlledComment(context) or isExternalUserControlledIssue(context))
|
||||
or
|
||||
exists(on.getNode("gollum")) and
|
||||
isExternalUserControlledGollum(context)
|
||||
or
|
||||
exists(on.getNode("push")) and
|
||||
isExternalUserControlledCommit(context)
|
||||
or
|
||||
exists(on.getNode("discussion")) and
|
||||
isExternalUserControlledDiscussion(context)
|
||||
or
|
||||
exists(on.getNode("discussion_comment")) and
|
||||
(isExternalUserControlledDiscussion(context) or isExternalUserControlledComment(context))
|
||||
or
|
||||
exists(on.getNode("workflow_run")) and
|
||||
isExternalUserControlledWorkflowRun(context)
|
||||
)
|
||||
)
|
||||
select node,
|
||||
"Potential injection from the ${{ " + injection +
|
||||
" }}, which may be controlled by an external user."
|
||||
@@ -1,8 +0,0 @@
|
||||
on: issue_comment
|
||||
|
||||
jobs:
|
||||
echo-body:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: |
|
||||
echo '${{ github.event.comment.body }}'
|
||||
@@ -1,10 +0,0 @@
|
||||
on: issue_comment
|
||||
|
||||
jobs:
|
||||
echo-body:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- env:
|
||||
BODY: ${{ github.event.issue.body }}
|
||||
run: |
|
||||
echo '${{ env.BODY }}'
|
||||
@@ -1,10 +0,0 @@
|
||||
on: issue_comment
|
||||
|
||||
jobs:
|
||||
echo-body:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- env:
|
||||
BODY: ${{ github.event.issue.body }}
|
||||
run: |
|
||||
echo "$BODY"
|
||||
@@ -1,30 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Sensitive information included in a GitHub Actions artifact can allow an attacker to access
|
||||
the sensitive information if the artifact is published.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Only store information that is meant to be publicly available in a GitHub Actions artifact.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example uses <code>actions/checkout</code> to checkout code which stores the GITHUB_TOKEN in the `.git/config` file
|
||||
and then stores the contents of the `.git` repository into the artifact:
|
||||
</p>
|
||||
<sample src="examples/actions-artifact-leak.yml"/>
|
||||
<p>
|
||||
The issue has been fixed below, where the <code>actions/upload-artifact</code> uses a version (v4+) which does not include hidden files or
|
||||
directories into the artifact.
|
||||
</p>
|
||||
<sample src="examples/actions-artifact-leak-fixed.yml"/>
|
||||
</example>
|
||||
</qhelp>
|
||||
@@ -1,112 +0,0 @@
|
||||
/**
|
||||
* @name Storage of sensitive information in GitHub Actions artifact
|
||||
* @description Including sensitive information in a GitHub Actions artifact can
|
||||
* expose it to an attacker.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @id js/actions/actions-artifact-leak
|
||||
* @tags actions
|
||||
* security
|
||||
* external/cwe/cwe-312
|
||||
* external/cwe/cwe-315
|
||||
* external/cwe/cwe-359
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.Actions
|
||||
|
||||
/**
|
||||
* A step that uses `actions/checkout` action.
|
||||
*/
|
||||
class ActionsCheckoutStep extends Actions::Step {
|
||||
ActionsCheckoutStep() { this.getUses().getGitHubRepository() = "actions/checkout" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `with:`/`persist-credentials` field sibling to `uses: actions/checkout`.
|
||||
*/
|
||||
class ActionsCheckoutWithPersistCredentials extends YamlNode, YamlScalar {
|
||||
ActionsCheckoutStep step;
|
||||
|
||||
ActionsCheckoutWithPersistCredentials() {
|
||||
step.lookup("with").(YamlMapping).lookup("persist-credentials") = this
|
||||
}
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
ActionsCheckoutStep getStep() { result = step }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `with:`/`path` field sibling to `uses: actions/checkout`.
|
||||
*/
|
||||
class ActionsCheckoutWithPath extends YamlNode, YamlString {
|
||||
ActionsCheckoutStep step;
|
||||
|
||||
ActionsCheckoutWithPath() { step.lookup("with").(YamlMapping).lookup("path") = this }
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
ActionsCheckoutStep getStep() { result = step }
|
||||
}
|
||||
|
||||
/**
|
||||
* A step that uses `actions/upload-artifact` action.
|
||||
*/
|
||||
class ActionsUploadArtifactStep extends Actions::Step {
|
||||
ActionsUploadArtifactStep() { this.getUses().getGitHubRepository() = "actions/upload-artifact" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `with:`/`path` field sibling to `uses: actions/upload-artifact`.
|
||||
*/
|
||||
class ActionsUploadArtifactWithPath extends YamlNode, YamlString {
|
||||
ActionsUploadArtifactStep step;
|
||||
|
||||
ActionsUploadArtifactWithPath() { step.lookup("with").(YamlMapping).lookup("path") = this }
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
ActionsUploadArtifactStep getStep() { result = step }
|
||||
}
|
||||
|
||||
from ActionsCheckoutStep checkout, ActionsUploadArtifactStep upload, Actions::Job job, int i, int j
|
||||
where
|
||||
checkout.getJob() = job and
|
||||
upload.getJob() = job and
|
||||
job.getStep(i) = checkout and
|
||||
job.getStep(j) = upload and
|
||||
j = i + 1 and
|
||||
upload.getUses().getVersion() =
|
||||
[
|
||||
"v4.3.6", "834a144ee995460fba8ed112a2fc961b36a5ec5a", //
|
||||
"v4.3.5", "89ef406dd8d7e03cfd12d9e0a4a378f454709029", //
|
||||
"v4.3.4", "0b2256b8c012f0828dc542b3febcab082c67f72b", //
|
||||
"v4.3.3", "65462800fd760344b1a7b4382951275a0abb4808", //
|
||||
"v4.3.2", "1746f4ab65b179e0ea60a494b83293b640dd5bba", //
|
||||
"v4.3.1", "5d5d22a31266ced268874388b861e4b58bb5c2f3", //
|
||||
"v4.3.0", "26f96dfa697d77e81fd5907df203aa23a56210a8", //
|
||||
"v4.2.0", "694cdabd8bdb0f10b2cea11669e1bf5453eed0a6", //
|
||||
"v4.1.0", "1eb3cb2b3e0f29609092a73eb033bb759a334595", //
|
||||
"v4.0.0", "c7d193f32edcb7bfad88892161225aeda64e9392", //
|
||||
] and
|
||||
(
|
||||
not exists(ActionsCheckoutWithPersistCredentials persist | persist.getStep() = checkout)
|
||||
or
|
||||
exists(ActionsCheckoutWithPersistCredentials persist |
|
||||
persist.getStep() = checkout and
|
||||
persist.getValue() = "true"
|
||||
)
|
||||
) and
|
||||
(
|
||||
not exists(ActionsCheckoutWithPath path | path.getStep() = checkout) and
|
||||
exists(ActionsUploadArtifactWithPath path |
|
||||
path.getStep() = upload and path.getValue() = [".", "*"]
|
||||
)
|
||||
or
|
||||
exists(ActionsCheckoutWithPath checkout_path, ActionsUploadArtifactWithPath upload_path |
|
||||
checkout_path.getValue() + ["", "/*"] = upload_path.getValue() and
|
||||
checkout_path.getStep() = checkout and
|
||||
upload_path.getStep() = upload
|
||||
)
|
||||
)
|
||||
select upload, "A secret may be exposed in an artifact."
|
||||
@@ -1,14 +0,0 @@
|
||||
name: secrets-in-artifacts
|
||||
on:
|
||||
pull_request:
|
||||
jobs:
|
||||
a-job: # NOT VULNERABLE
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: file
|
||||
path: .
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
name: secrets-in-artifacts
|
||||
on:
|
||||
pull_request:
|
||||
jobs:
|
||||
a-job: # VULNERABLE
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@1746f4ab65b179e0ea60a494b83293b640dd5bba # v4.3.2
|
||||
with:
|
||||
name: file
|
||||
path: .
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `js/useless-expression` query now correctly flags only the innermost expressions with no effect, avoiding duplicate alerts on compound expressions.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Fixed false positives in the `js/loop-iteration-skipped-due-to-shifting` query when the return value of `splice` is used to decide whether to adjust the loop counter.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `js/loop-iteration-skipped-due-to-shifting` query now has the `reliability` tag.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Fixed false positives in the `js/template-syntax-in-string-literal` query where template syntax in string concatenation and "manual string interpolation" patterns were incorrectly flagged.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: queryMetadata
|
||||
---
|
||||
* Added `reliability` tag to the `js/suspicious-method-name-declaration` query.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: queryMetadata
|
||||
---
|
||||
* Added `reliability` and `language-features` tags to the `js/template-syntax-in-string-literal` query.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: queryMetadata
|
||||
---
|
||||
* The `quality` tag has been added to multiple JavaScript quality queries, with tags for `reliability` or `maintainability` categories and their sub-categories. See [Query file metadata and alert message style guide](https://github.com/github/codeql/blob/main/docs/query-metadata-style-guide.md#quality-query-sub-category-tags) for more information about these categories.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* Taint is now tracked through the React `use` function.
|
||||
* Parameters of React server functions, marked with the `"use server"` directive, are now seen as taint sources.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Removed three queries from the JS qlpack, which have been superseded by newer queries that are part of the Actions qlpack:
|
||||
* `js/actions/pull-request-target` has been superseded by `actions/untrusted-checkout/{medium,high,critical}`
|
||||
* `js/actions/actions-artifact-leak` has been superseded by `actions/secrets-in-artifacts`
|
||||
* `js/actions/command-injection` has been superseded by `actions/command-injection/{medium,critical}`
|
||||
14
javascript/ql/src/change-notes/released/1.7.0.md
Normal file
14
javascript/ql/src/change-notes/released/1.7.0.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## 1.7.0
|
||||
|
||||
### Query Metadata Changes
|
||||
|
||||
* The `quality` tag has been added to multiple JavaScript quality queries, with tags for `reliability` or `maintainability` categories and their sub-categories. See [Query file metadata and alert message style guide](https://github.com/github/codeql/blob/main/docs/query-metadata-style-guide.md#quality-query-sub-category-tags) for more information about these categories.
|
||||
* Added `reliability` tag to the `js/suspicious-method-name-declaration` query.
|
||||
* Added `reliability` and `language-features` tags to the `js/template-syntax-in-string-literal` query.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `js/loop-iteration-skipped-due-to-shifting` query now has the `reliability` tag.
|
||||
* Fixed false positives in the `js/loop-iteration-skipped-due-to-shifting` query when the return value of `splice` is used to decide whether to adjust the loop counter.
|
||||
* Fixed false positives in the `js/template-syntax-in-string-literal` query where template syntax in string concatenation and "manual string interpolation" patterns were incorrectly flagged.
|
||||
* The `js/useless-expression` query now correctly flags only the innermost expressions with no effect, avoiding duplicate alerts on compound expressions.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.6.2
|
||||
lastReleaseVersion: 1.7.0
|
||||
|
||||
@@ -1,24 +1,7 @@
|
||||
- description: Security-and-quality queries for JavaScript
|
||||
- queries: .
|
||||
- include:
|
||||
kind:
|
||||
- problem
|
||||
- path-problem
|
||||
precision:
|
||||
- high
|
||||
- very-high
|
||||
tags contain:
|
||||
- security
|
||||
- include:
|
||||
kind:
|
||||
- problem
|
||||
- path-problem
|
||||
precision: medium
|
||||
problem.severity:
|
||||
- error
|
||||
- warning
|
||||
tags contain:
|
||||
- security
|
||||
- apply: security-and-frozen-quality-selectors.yml
|
||||
from: codeql/suite-helpers
|
||||
- include:
|
||||
id:
|
||||
- js/node/assignment-to-exports-variable
|
||||
@@ -123,16 +106,3 @@
|
||||
- js/diagnostics/successfully-extracted-files
|
||||
- js/summary/lines-of-code
|
||||
- js/summary/lines-of-user-code
|
||||
- include:
|
||||
kind:
|
||||
- diagnostic
|
||||
- include:
|
||||
kind:
|
||||
- metric
|
||||
tags contain:
|
||||
- summary
|
||||
- exclude:
|
||||
deprecated: //
|
||||
- exclude:
|
||||
query path:
|
||||
- /^experimental\/.*/
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
|
||||
<p>
|
||||
|
||||
Combining <i>pull_request_target</i> workflow trigger with an explicit checkout
|
||||
of an untrusted pull request is a dangerous practice
|
||||
that may lead to repository compromise.
|
||||
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
|
||||
The best practice is to handle the potentially untrusted pull request
|
||||
via the <i>pull_request</i> trigger so that it is isolated in
|
||||
an unprivileged environment. The workflow processing the pull request
|
||||
should then store any results like code coverage or failed/passed tests
|
||||
in artifacts and exit. The following workflow then starts on <i>workflow_run</i>
|
||||
where it is granted write permission to the target repository and access to
|
||||
repository secrets, so that it can download the artifacts and make
|
||||
any necessary modifications to the repository or interact with third party services
|
||||
that require repository secrets (e.g. API tokens).
|
||||
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
|
||||
<p>
|
||||
|
||||
The following example allows unauthorized repository modification
|
||||
and secrets exfiltration:
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="examples/pull_request_target_bad.yml" />
|
||||
|
||||
<p>
|
||||
|
||||
The following example uses two workflows to handle potentially untrusted
|
||||
pull request in a secure manner. The receive_pr.yml is triggered first:
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="examples/receive_pr.yml" />
|
||||
<p>The comment_pr.yml is triggered after receive_pr.yml completes:</p>
|
||||
<sample src="examples/comment_pr.yml" />
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>GitHub Security Lab Research: <a href="https://securitylab.github.com/research/github-actions-preventing-pwn-requests">Keeping your GitHub Actions and workflows secure: Preventing pwn requests</a>.</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* @name Checkout of untrusted code in trusted context
|
||||
* @description Workflows triggered on `pull_request_target` have read/write access to the base repository and access to secrets.
|
||||
* By explicitly checking out and running the build script from a fork the untrusted code is running in an environment
|
||||
* that is able to push to the base repository and to access secrets.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @precision low
|
||||
* @id js/actions/pull-request-target
|
||||
* @tags actions
|
||||
* security
|
||||
* experimental
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.Actions
|
||||
|
||||
/**
|
||||
* An action step that doesn't contain `actor` check in `if:` or
|
||||
* the check requires manual analysis.
|
||||
*/
|
||||
class ProbableStep extends Actions::Step {
|
||||
// some simplistic checks to eleminate likely false positives:
|
||||
ProbableStep() {
|
||||
// no if at all
|
||||
not exists(this.getIf().getValue())
|
||||
or
|
||||
// needs manual analysis if there is OR
|
||||
this.getIf().getValue().matches("%||%")
|
||||
or
|
||||
// actor check means only the user is able to run it
|
||||
not exists(this.getIf().getValue().regexpFind("\\bgithub\\s*\\.\\s*actor\\s*==", _, _))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An action job that doesn't contain `actor` check in `if:` or
|
||||
* the check requires manual analysis.
|
||||
*/
|
||||
class ProbableJob extends Actions::Job {
|
||||
// some simplistic checks to eleminate likely false positives:
|
||||
ProbableJob() {
|
||||
// no if at all
|
||||
not exists(this.getIf().getValue())
|
||||
or
|
||||
// needs manual analysis if there is OR
|
||||
this.getIf().getValue().matches("%||%")
|
||||
or
|
||||
// actor check means only the user is able to run it
|
||||
not exists(this.getIf().getValue().regexpFind("\\bgithub\\s*\\.\\s*actor\\s*==", _, _))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `on: pull_request_target`.
|
||||
*/
|
||||
class ProbablePullRequestTarget extends Actions::On, YamlMappingLikeNode {
|
||||
ProbablePullRequestTarget() {
|
||||
// The `on:` is triggered on `pull_request_target`
|
||||
exists(this.getNode("pull_request_target"))
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
Actions::Ref ref, Actions::Uses uses, Actions::Step step, Actions::Job job,
|
||||
ProbablePullRequestTarget pullRequestTarget
|
||||
where
|
||||
pullRequestTarget.getWorkflow() = job.getWorkflow() and
|
||||
uses.getStep() = step and
|
||||
ref.getWith().getStep() = step and
|
||||
step.getJob() = job and
|
||||
uses.getGitHubRepository() = "actions/checkout" and
|
||||
ref.getValue()
|
||||
.matches([
|
||||
"%github.event.pull_request.head.ref%", "%github.event.pull_request.head.sha%",
|
||||
"%github.event.pull_request.number%", "%github.event.number%", "%github.head_ref%"
|
||||
]) and
|
||||
step instanceof ProbableStep and
|
||||
job instanceof ProbableJob
|
||||
select step, "Potential unsafe checkout of untrusted pull request on 'pull_request_target'."
|
||||
@@ -1,52 +0,0 @@
|
||||
name: Comment on the pull request
|
||||
|
||||
# read-write repo token
|
||||
# access to secrets
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Receive PR"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
${{ github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- name: 'Download artifact'
|
||||
uses: actions/github-script@v3.1.0
|
||||
with:
|
||||
script: |
|
||||
var artifacts = await github.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{github.event.workflow_run.id }},
|
||||
});
|
||||
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "pr"
|
||||
})[0];
|
||||
var download = await github.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
var fs = require('fs');
|
||||
fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data));
|
||||
- run: unzip pr.zip
|
||||
|
||||
- name: 'Comment on PR'
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
var fs = require('fs');
|
||||
var issue_number = Number(fs.readFileSync('./NR'));
|
||||
await github.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue_number,
|
||||
body: 'Everything is OK. Thank you for the PR!'
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
on:
|
||||
pull_request_target
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- uses: actions/setup-node@v1
|
||||
- run: |
|
||||
npm install
|
||||
npm build
|
||||
|
||||
- uses: completely/fakeaction@v2
|
||||
with:
|
||||
arg1: ${{ secrets.supersecret }}
|
||||
|
||||
- uses: fakerepo/comment-on-pr@v1
|
||||
with:
|
||||
message: |
|
||||
Thank you!
|
||||
@@ -1,26 +0,0 @@
|
||||
name: Receive PR
|
||||
|
||||
# read-only repo token
|
||||
# no access to secrets
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# imitation of a build process
|
||||
- name: Build
|
||||
run: /bin/bash ./build.sh
|
||||
|
||||
- name: Save PR number
|
||||
run: |
|
||||
mkdir -p ./pr
|
||||
echo ${{ github.event.number }} > ./pr/NR
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: pr
|
||||
path: pr/
|
||||
@@ -146,11 +146,42 @@ module ExperimentalSql {
|
||||
override DataFlow::Node getAQueryArgument() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a TypeORM `Repository` (https://orkhan.gitbook.io/typeorm/docs/repository-api)
|
||||
*/
|
||||
private class RepositoryCall extends DatabaseAccess {
|
||||
API::Node repository;
|
||||
|
||||
RepositoryCall() {
|
||||
(
|
||||
repository = API::moduleImport("typeorm").getMember("Repository").getInstance() or
|
||||
repository = dataSource().getMember("getRepository").getReturn()
|
||||
) and
|
||||
this = repository.getMember(_).asSource()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAResult() {
|
||||
result =
|
||||
repository
|
||||
.getMember([
|
||||
"find", "findBy", "findOne", "findOneBy", "findOneOrFail", "findOneByOrFail",
|
||||
"findAndCount", "findAndCountBy"
|
||||
])
|
||||
.getReturn()
|
||||
.asSource()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAQueryArgument() {
|
||||
result = repository.getMember("query").getParameter(0).asSink()
|
||||
}
|
||||
}
|
||||
|
||||
/** An expression that is passed to the `query` function and hence interpreted as SQL. */
|
||||
class QueryString extends SQL::SqlString {
|
||||
QueryString() {
|
||||
this = any(QueryRunner qr).getAQueryArgument() or
|
||||
this = any(QueryBuilderCall qb).getAQueryArgument()
|
||||
this = any(QueryBuilderCall qb).getAQueryArgument() or
|
||||
this = any(RepositoryCall rc).getAQueryArgument()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-queries
|
||||
version: 1.6.3-dev
|
||||
version: 1.7.1-dev
|
||||
groups:
|
||||
- javascript
|
||||
- queries
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
passingPositiveTests
|
||||
| PASSED | CommandInjection | tests.js:11:46:11:70 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:12:43:12:67 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:13:63:13:87 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:14:62:14:86 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:15:60:15:84 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:17:45:17:69 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:18:42:18:66 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:19:62:19:86 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:20:63:20:87 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:21:60:21:84 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:23:43:23:67 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:24:40:24:64 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:25:40:25:64 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:26:60:26:84 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:28:41:28:65 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:29:58:29:82 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:31:51:31:75 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:32:68:32:92 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:34:49:34:73 | // test ... jection |
|
||||
| PASSED | CommandInjection | tests.js:35:66:35:90 | // test ... jection |
|
||||
failingPositiveTests
|
||||
@@ -1,36 +0,0 @@
|
||||
import { execa, execaSync, execaCommand, execaCommandSync, $ } from 'execa';
|
||||
import http from 'node:http'
|
||||
import url from 'url'
|
||||
|
||||
http.createServer(async function (req, res) {
|
||||
let cmd = url.parse(req.url, true).query["cmd"][0];
|
||||
let arg1 = url.parse(req.url, true).query["arg1"];
|
||||
let arg2 = url.parse(req.url, true).query["arg2"];
|
||||
let arg3 = url.parse(req.url, true).query["arg3"];
|
||||
|
||||
await $`${cmd} ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
|
||||
await $`ssh ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
|
||||
$({ shell: false }).sync`${cmd} ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
|
||||
$({ shell: true }).sync`${cmd} ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
|
||||
$({ shell: false }).sync`ssh ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
|
||||
|
||||
$.sync`${cmd} ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
|
||||
$.sync`ssh ${arg1} ${arg2} ${arg3}`; // test: CommandInjection
|
||||
await $({ shell: true })`${cmd} ${arg1} ${arg2} ${arg3}` // test: CommandInjection
|
||||
await $({ shell: false })`${cmd} ${arg1} ${arg2} ${arg3}` // test: CommandInjection
|
||||
await $({ shell: false })`ssh ${arg1} ${arg2} ${arg3}` // test: CommandInjection
|
||||
|
||||
await execa(cmd, [arg1, arg2, arg3]); // test: CommandInjection
|
||||
await execa(cmd, { shell: true }); // test: CommandInjection
|
||||
await execa(cmd, { shell: true }); // test: CommandInjection
|
||||
await execa(cmd, [arg1, arg2, arg3], { shell: true }); // test: CommandInjection
|
||||
|
||||
execaSync(cmd, [arg1, arg2, arg3]); // test: CommandInjection
|
||||
execaSync(cmd, [arg1, arg2, arg3], { shell: true }); // test: CommandInjection
|
||||
|
||||
await execaCommand(cmd + arg1 + arg2 + arg3); // test: CommandInjection
|
||||
await execaCommand(cmd + arg1 + arg2 + arg3, { shell: true }); // test: CommandInjection
|
||||
|
||||
execaCommandSync(cmd + arg1 + arg2 + arg3); // test: CommandInjection
|
||||
execaCommandSync(cmd + arg1 + arg2 + arg3, { shell: true }); // test: CommandInjection
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
import javascript
|
||||
|
||||
class InlineTest extends LineComment {
|
||||
string tests;
|
||||
|
||||
InlineTest() { tests = this.getText().regexpCapture("\\s*test:(.*)", 1) }
|
||||
|
||||
string getPositiveTest() {
|
||||
result = tests.trim().splitAt(",").trim() and not result.matches("!%")
|
||||
}
|
||||
|
||||
predicate hasPositiveTest(string test) { test = this.getPositiveTest() }
|
||||
|
||||
predicate inNode(DataFlow::Node n) {
|
||||
this.getLocation().getFile() = n.getFile() and
|
||||
this.getLocation().getStartLine() = n.getStartLine()
|
||||
}
|
||||
}
|
||||
|
||||
import experimental.semmle.javascript.Execa
|
||||
|
||||
query predicate passingPositiveTests(string res, string expectation, InlineTest t) {
|
||||
res = "PASSED" and
|
||||
t.hasPositiveTest(expectation) and
|
||||
expectation = "CommandInjection" and
|
||||
exists(SystemCommandExecution n |
|
||||
t.inNode(n.getArgumentList()) or t.inNode(n.getACommandArgument())
|
||||
)
|
||||
}
|
||||
|
||||
query predicate failingPositiveTests(string res, string expectation, InlineTest t) {
|
||||
res = "FAILED" and
|
||||
t.hasPositiveTest(expectation) and
|
||||
expectation = "CommandInjection" and
|
||||
not exists(SystemCommandExecution n |
|
||||
t.inNode(n.getArgumentList()) or t.inNode(n.getACommandArgument())
|
||||
)
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
passingPositiveTests
|
||||
| PASSED | PathInjection | tests.js:9:43:9:64 | // test ... jection |
|
||||
| PASSED | PathInjection | tests.js:12:50:12:71 | // test ... jection |
|
||||
| PASSED | PathInjection | tests.js:15:61:15:82 | // test ... jection |
|
||||
| PASSED | PathInjection | tests.js:18:73:18:94 | // test ... jection |
|
||||
failingPositiveTests
|
||||
@@ -1,34 +0,0 @@
|
||||
import javascript
|
||||
|
||||
class InlineTest extends LineComment {
|
||||
string tests;
|
||||
|
||||
InlineTest() { tests = this.getText().regexpCapture("\\s*test:(.*)", 1) }
|
||||
|
||||
string getPositiveTest() {
|
||||
result = tests.trim().splitAt(",").trim() and not result.matches("!%")
|
||||
}
|
||||
|
||||
predicate hasPositiveTest(string test) { test = this.getPositiveTest() }
|
||||
|
||||
predicate inNode(DataFlow::Node n) {
|
||||
this.getLocation().getFile() = n.getFile() and
|
||||
this.getLocation().getStartLine() = n.getStartLine()
|
||||
}
|
||||
}
|
||||
|
||||
import experimental.semmle.javascript.Execa
|
||||
|
||||
query predicate passingPositiveTests(string res, string expectation, InlineTest t) {
|
||||
res = "PASSED" and
|
||||
t.hasPositiveTest(expectation) and
|
||||
expectation = "PathInjection" and
|
||||
exists(FileSystemReadAccess n | t.inNode(n.getAPathArgument()))
|
||||
}
|
||||
|
||||
query predicate failingPositiveTests(string res, string expectation, InlineTest t) {
|
||||
res = "FAILED" and
|
||||
t.hasPositiveTest(expectation) and
|
||||
expectation = "PathInjection" and
|
||||
not exists(FileSystemReadAccess n | t.inNode(n.getAPathArgument()))
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
job1:
|
||||
if: contains(github.event.issue.labels.*.name, 'ok')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
job2:
|
||||
if: github.event.label.name == 'ok'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
job3:
|
||||
if: github.actor == 'ok'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
job4:
|
||||
if: github.actor == 'ok' || true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
job5:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
@@ -1,31 +0,0 @@
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
job1:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
if: contains(github.event.issue.labels.*.name, 'ok')
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
if: github.event.label.name == 'ok'
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
if: github.actor == 'ok'
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
if: github.actor == 'ok' || true
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
@@ -1,12 +0,0 @@
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
push:
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
@@ -1,13 +0,0 @@
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
labeled:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
@@ -1,15 +0,0 @@
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
labeled:
|
||||
opened:
|
||||
closed:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
@@ -1,12 +0,0 @@
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled, opened]
|
||||
push:
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
@@ -1,10 +0,0 @@
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
@@ -1,10 +0,0 @@
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: master
|
||||
@@ -1,11 +0,0 @@
|
||||
on: pull_request_target
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- run: make
|
||||
@@ -1,9 +0,0 @@
|
||||
on: [pull_request_target, push]
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
@@ -1,15 +0,0 @@
|
||||
| .github/workflows/pull_request_target_if_job.yml:9:7:12:2 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_job.yml:16:7:19:2 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_job.yml:30:7:33:2 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_job.yml:36:7:38:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_step.yml:9:7:14:4 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_step.yml:14:7:19:4 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_step.yml:24:7:29:4 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_step.yml:29:7:31:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_label_only.yml:10:7:12:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_label_only_mapping.yml:11:7:13:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_labels_mapping.yml:13:7:15:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_labels_sequence.yml:10:7:12:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_mapping.yml:8:7:10:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_run.yml:7:7:11:4 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_sequence.yml:7:7:9:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
@@ -1 +0,0 @@
|
||||
experimental/Security/CWE-094/UntrustedCheckout.ql
|
||||
@@ -217,4 +217,9 @@ AppDataSource.initialize().then(async () => {
|
||||
qb.where(BadInput).orWhere(BadInput) // test: SQLInjectionPoint
|
||||
}),
|
||||
).getMany()
|
||||
|
||||
// Repository.query sink
|
||||
await AppDataSource.getRepository(User2)
|
||||
.query(BadInput) // test: SQLInjectionPoint
|
||||
|
||||
}).catch(error => console.log(error))
|
||||
|
||||
@@ -29,4 +29,5 @@ passingPositiveTests
|
||||
| PASSED | SQLInjectionPoint | test.ts:210:28:210:53 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:213:56:213:81 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:217:56:217:81 | // test ... onPoint |
|
||||
| PASSED | SQLInjectionPoint | test.ts:223:29:223:54 | // test ... onPoint |
|
||||
failingPositiveTests
|
||||
|
||||
12
javascript/ql/test/library-tests/TripleDot/react-use.js
vendored
Normal file
12
javascript/ql/test/library-tests/TripleDot/react-use.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { use } from "react";
|
||||
|
||||
async function fetchData() {
|
||||
return new Promise((resolve) => {
|
||||
resolve(source("fetchedData"));
|
||||
});
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const data = use(fetchData());
|
||||
sink(data); // $ hasValueFlow=fetchedData
|
||||
}
|
||||
@@ -5,6 +5,8 @@ test_ClientRequest
|
||||
| apollo.js:17:1:17:34 | new Pre ... yurl"}) |
|
||||
| apollo.js:20:1:20:77 | createN ... phql'}) |
|
||||
| apollo.js:23:1:23:31 | new Web ... wsUri}) |
|
||||
| axios.ts:14:32:14:65 | api.get ... repo}`) |
|
||||
| axios.ts:25:32:25:73 | api.pat ... , data) |
|
||||
| axiosTest.js:4:5:7:6 | axios({ ... \\n }) |
|
||||
| axiosTest.js:12:5:17:6 | axios({ ... \\n }) |
|
||||
| puppeteer.ts:6:11:6:42 | page.go ... e.com') |
|
||||
@@ -111,6 +113,7 @@ test_ClientRequest
|
||||
| tst.js:349:5:349:30 | axios.g ... url }) |
|
||||
| tst.js:352:5:352:66 | axiosIn ... text"}) |
|
||||
test_getADataNode
|
||||
| axios.ts:25:32:25:73 | api.pat ... , data) | axios.ts:25:69:25:72 | data |
|
||||
| axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:15:18:15:55 | { 'Cont ... json' } |
|
||||
| axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:16:15:16:35 | {x: 'te ... 'test'} |
|
||||
| superagent.js:6:5:6:32 | superag ... st(url) | superagent.js:6:39:6:42 | data |
|
||||
@@ -159,6 +162,8 @@ test_getADataNode
|
||||
| tst.js:347:5:347:30 | axios.p ... , data) | tst.js:347:26:347:29 | data |
|
||||
| tst.js:348:5:348:38 | axios.p ... config) | tst.js:348:26:348:29 | data |
|
||||
test_getHost
|
||||
| axios.ts:14:32:14:65 | api.get ... repo}`) | axios.ts:4:14:4:37 | "https: ... ub.com" |
|
||||
| axios.ts:25:32:25:73 | api.pat ... , data) | axios.ts:4:14:4:37 | "https: ... ub.com" |
|
||||
| tst.js:87:5:87:39 | http.ge ... host}) | tst.js:87:34:87:37 | host |
|
||||
| tst.js:89:5:89:23 | axios({host: host}) | tst.js:89:18:89:21 | host |
|
||||
| tst.js:91:5:91:34 | got(rel ... host}) | tst.js:91:29:91:32 | host |
|
||||
@@ -173,6 +178,8 @@ test_getUrl
|
||||
| apollo.js:17:1:17:34 | new Pre ... yurl"}) | apollo.js:17:26:17:32 | "myurl" |
|
||||
| apollo.js:20:1:20:77 | createN ... phql'}) | apollo.js:20:30:20:75 | 'https: ... raphql' |
|
||||
| apollo.js:23:1:23:31 | new Web ... wsUri}) | apollo.js:23:25:23:29 | wsUri |
|
||||
| axios.ts:14:32:14:65 | api.get ... repo}`) | axios.ts:14:40:14:64 | `/repos ... {repo}` |
|
||||
| axios.ts:25:32:25:73 | api.pat ... , data) | axios.ts:25:42:25:66 | `/repos ... {repo}` |
|
||||
| axiosTest.js:4:5:7:6 | axios({ ... \\n }) | axiosTest.js:4:11:7:5 | {\\n ... ,\\n } |
|
||||
| axiosTest.js:4:5:7:6 | axios({ ... \\n }) | axiosTest.js:6:14:6:16 | url |
|
||||
| axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:12:11:17:5 | {\\n ... }\\n } |
|
||||
@@ -289,6 +296,8 @@ test_getUrl
|
||||
| tst.js:352:5:352:66 | axiosIn ... text"}) | tst.js:352:19:352:65 | {method ... "text"} |
|
||||
| tst.js:352:5:352:66 | axiosIn ... text"}) | tst.js:352:40:352:42 | url |
|
||||
test_getAResponseDataNode
|
||||
| axios.ts:14:32:14:65 | api.get ... repo}`) | axios.ts:14:32:14:65 | api.get ... repo}`) | json | true |
|
||||
| axios.ts:25:32:25:73 | api.pat ... , data) | axios.ts:25:32:25:73 | api.pat ... , data) | json | true |
|
||||
| axiosTest.js:4:5:7:6 | axios({ ... \\n }) | axiosTest.js:4:5:7:6 | axios({ ... \\n }) | json | true |
|
||||
| axiosTest.js:12:5:17:6 | axios({ ... \\n }) | axiosTest.js:12:5:17:6 | axios({ ... \\n }) | json | true |
|
||||
| superagent.js:4:5:4:26 | superag ... ', url) | superagent.js:4:5:4:26 | superag ... ', url) | stream | true |
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import axios from "axios";
|
||||
|
||||
let api = axios.create({
|
||||
baseURL: "https://api.github.com",
|
||||
timeout: 1000,
|
||||
responseType: "json",
|
||||
headers: { "X-Custom-Header": "foobar" }
|
||||
});
|
||||
|
||||
export default api;
|
||||
|
||||
export async function getRepo(owner: string, repo: string) {
|
||||
try {
|
||||
const response = await api.get(`/repos/${owner}/${repo}`);
|
||||
console.log("Repository data:", response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching repo:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateUser(owner: string, repo: string, data: any) {
|
||||
try {
|
||||
const response = await api.patch(`/repos/${owner}/${repo}`, data);
|
||||
console.log("User updated:", response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error updating user:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,27 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Controller } from './validation';
|
||||
import { Foo } from './foo.interface';
|
||||
import { FooImpl } from './foo.impl';
|
||||
import { Imports } from './imports';
|
||||
import { Foo, Foo2, Foo3 } from './foo.interface';
|
||||
import { FooImpl, Foo2Impl, Foo3Impl } from './foo.impl';
|
||||
|
||||
const foo3 = new Foo3Impl()
|
||||
|
||||
@Module({
|
||||
controllers: [Controller],
|
||||
providers: [{
|
||||
provide: Foo, useClass: FooImpl
|
||||
}],
|
||||
controllers: [Controller],
|
||||
imports: [Imports.forRoot()],
|
||||
providers: [
|
||||
{
|
||||
provide: Foo,
|
||||
useClass: FooImpl
|
||||
},
|
||||
{
|
||||
provide: Foo2,
|
||||
useFactory: () => new Foo2Impl()
|
||||
},
|
||||
{
|
||||
provide: Foo3,
|
||||
useValue: foo3
|
||||
}
|
||||
],
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
import { Foo } from "./foo.interface";
|
||||
import { Foo, Foo2, Foo3, Foo4 } from "./foo.interface";
|
||||
|
||||
export class FooImpl extends Foo {
|
||||
fooMethod(x: string) {
|
||||
sink(x); // $ hasValueFlow=x
|
||||
}
|
||||
}
|
||||
|
||||
export class Foo2Impl extends Foo2 {
|
||||
fooMethod(x: string) {
|
||||
sink(x); // $ hasValueFlow=x
|
||||
}
|
||||
}
|
||||
|
||||
export class Foo3Impl extends Foo3 {
|
||||
fooMethod(x: string) {
|
||||
sink(x); // $ hasValueFlow=x
|
||||
}
|
||||
}
|
||||
|
||||
export class Foo4Impl extends Foo4 {
|
||||
fooMethod(x: string) {
|
||||
sink(x); // $ hasValueFlow=x
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,15 @@
|
||||
export abstract class Foo {
|
||||
abstract fooMethod(x: string): void;
|
||||
}
|
||||
|
||||
export abstract class Foo2 {
|
||||
abstract fooMethod(x: string): void;
|
||||
}
|
||||
|
||||
export abstract class Foo3 {
|
||||
abstract fooMethod(x: string): void;
|
||||
}
|
||||
|
||||
export abstract class Foo4 {
|
||||
abstract fooMethod(x: string): void;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { DynamicModule } from '@nestjs/common';
|
||||
import { Foo4Impl } from './foo.impl';
|
||||
import { Foo4 } from './foo.interface';
|
||||
|
||||
export class Imports {
|
||||
static forRoot(): DynamicModule {
|
||||
return {
|
||||
providers: [
|
||||
{
|
||||
provide: Foo4,
|
||||
useClass: Foo4Impl,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Get, Query } from '@nestjs/common';
|
||||
import { IsIn } from 'class-validator';
|
||||
import { Foo } from './foo.interface';
|
||||
import { Foo, Foo2, Foo3, Foo4 } from './foo.interface';
|
||||
|
||||
export class Controller {
|
||||
constructor(
|
||||
private readonly foo: Foo
|
||||
private readonly foo: Foo, private readonly foo2: Foo2, private readonly foo3: Foo3, private readonly foo4: Foo4
|
||||
) { }
|
||||
|
||||
@Get()
|
||||
@@ -16,6 +16,9 @@ export class Controller {
|
||||
@Get()
|
||||
route2(@Query('x') x: string) {
|
||||
this.foo.fooMethod(x);
|
||||
this.foo2.fooMethod(x);
|
||||
this.foo3.fooMethod(x);
|
||||
this.foo4.fooMethod(x);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import { Response, NextFunction } from 'express';
|
||||
import { CustomRequest } from '@randomPackage/request';
|
||||
|
||||
@Injectable()
|
||||
export class LoggerMiddleware implements NestMiddleware {
|
||||
// The request can be a custom type that extends the express Request
|
||||
use(req: CustomRequest, res: Response, next: NextFunction) {
|
||||
console.log(req.query.abc);
|
||||
next();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
testFailures
|
||||
routeHandler
|
||||
| global/validation.ts:11:3:14:3 | route1( ... OK\\n } |
|
||||
| global/validation.ts:17:3:19:3 | route2( ... x);\\n } |
|
||||
| global/validation.ts:17:3:22:3 | route2( ... x);\\n } |
|
||||
| local/customDecorator.ts:18:3:20:3 | sneaky( ... OK\\n } |
|
||||
| local/customDecorator.ts:23:3:25:3 | safe(@S ... OK\\n } |
|
||||
| local/customPipe.ts:20:5:22:5 | sanitiz ... K\\n } |
|
||||
@@ -10,6 +10,7 @@ routeHandler
|
||||
| local/customPipe.ts:36:5:38:5 | propaga ... K\\n } |
|
||||
| local/customPipe.ts:41:5:43:5 | propaga ... K\\n } |
|
||||
| local/customPipe.ts:47:5:49:5 | propaga ... K\\n } |
|
||||
| local/middleware.ts:8:3:11:3 | use(req ... ();\\n } |
|
||||
| local/routes.ts:6:3:8:3 | getFoo( ... o';\\n } |
|
||||
| local/routes.ts:11:3:13:3 | postFoo ... o';\\n } |
|
||||
| local/routes.ts:16:3:18:3 | getRoot ... o';\\n } |
|
||||
@@ -29,9 +30,11 @@ routeHandler
|
||||
| local/validation.ts:42:3:45:3 | route6( ... OK\\n } |
|
||||
requestSource
|
||||
| local/customDecorator.ts:5:21:5:51 | ctx.swi ... quest() |
|
||||
| local/middleware.ts:8:7:8:9 | req |
|
||||
| local/routes.ts:30:12:30:14 | req |
|
||||
| local/routes.ts:61:23:61:25 | req |
|
||||
responseSource
|
||||
| local/middleware.ts:8:27:8:29 | res |
|
||||
| local/routes.ts:61:35:61:37 | res |
|
||||
| local/routes.ts:62:5:62:25 | res.sen ... uery.x) |
|
||||
requestInputAccess
|
||||
@@ -44,6 +47,7 @@ requestInputAccess
|
||||
| parameter | local/customDecorator.ts:6:12:6:41 | request ... ryParam |
|
||||
| parameter | local/customPipe.ts:5:15:5:19 | value |
|
||||
| parameter | local/customPipe.ts:13:15:13:19 | value |
|
||||
| parameter | local/middleware.ts:9:17:9:29 | req.query.abc |
|
||||
| parameter | local/routes.ts:27:17:27:17 | x |
|
||||
| parameter | local/routes.ts:28:14:28:21 | queryObj |
|
||||
| parameter | local/routes.ts:29:20:29:23 | name |
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import semmle.javascript.frameworks.React
|
||||
|
||||
query predicate test_ReactComponent(ReactComponent c) { any() }
|
||||
@@ -1,5 +0,0 @@
|
||||
import javascript
|
||||
|
||||
query predicate test_ReactComponent_getACandidatePropsValue(DataFlow::Node res) {
|
||||
exists(ReactComponent c | res = c.getACandidatePropsValue(_))
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import semmle.javascript.frameworks.React
|
||||
|
||||
query predicate test_ReactComponent_getACandidateStateSource(
|
||||
ReactComponent c, DataFlow::SourceNode res
|
||||
) {
|
||||
res = c.getACandidateStateSource()
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import semmle.javascript.frameworks.React
|
||||
|
||||
query predicate test_ReactComponent_getADirectPropsSource(ReactComponent c, DataFlow::SourceNode res) {
|
||||
res = c.getADirectPropsAccess()
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import semmle.javascript.frameworks.React
|
||||
|
||||
query predicate test_ReactComponent_getAPreviousStateSource(
|
||||
ReactComponent c, DataFlow::SourceNode res
|
||||
) {
|
||||
res = c.getAPreviousStateSource()
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import semmle.javascript.frameworks.React
|
||||
|
||||
query predicate test_ReactComponent_getAPropRead(ReactComponent c, string n, DataFlow::PropRead res) {
|
||||
res = c.getAPropRead(n)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import semmle.javascript.frameworks.React
|
||||
|
||||
query predicate test_ReactComponent_getInstanceMethod(ReactComponent c, string n, Function res) {
|
||||
res = c.getInstanceMethod(n)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import semmle.javascript.frameworks.React
|
||||
|
||||
query predicate test_ReactComponent_ref(ReactComponent c, DataFlow::Node res) { res = c.ref() }
|
||||
@@ -1,17 +0,0 @@
|
||||
import semmle.javascript.frameworks.React
|
||||
|
||||
query predicate test_JSXname(JsxElement element, JsxName jsxname, string name, string type) {
|
||||
name = jsxname.getValue() and
|
||||
(
|
||||
jsxname instanceof Identifier and type = "Identifier"
|
||||
or
|
||||
jsxname instanceof ThisExpr and type = "thisExpr"
|
||||
or
|
||||
jsxname.(DotExpr).getBase() instanceof JsxName and type = "dot"
|
||||
or
|
||||
jsxname instanceof JsxQualifiedName and type = "qualifiedName"
|
||||
) and
|
||||
element.getNameExpr() = jsxname
|
||||
}
|
||||
|
||||
query ThisExpr test_JsxName_this(JsxElement element) { result.getParentExpr+() = element }
|
||||
@@ -1,14 +1,14 @@
|
||||
var Hello = React.createClass({
|
||||
displayName: 'Hello',
|
||||
render: function() {
|
||||
return <div>Hello {this.props.name}</div>;
|
||||
return <div>Hello {this.props.name}</div>; // $ threatModelSource=view-component-input
|
||||
},
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
name: 'world'
|
||||
name: 'world' // $ getACandidatePropsValue
|
||||
};
|
||||
}
|
||||
});
|
||||
}); // $ reactComponent
|
||||
|
||||
Hello.info = function() {
|
||||
return "Nothing to see here.";
|
||||
@@ -17,6 +17,6 @@ Hello.info = function() {
|
||||
var createReactClass = require('create-react-class');
|
||||
var Greeting = createReactClass({
|
||||
render: function() {
|
||||
return <h1>Hello, {this.props.name}</h1>;
|
||||
return <h1>Hello, {this.props.name}</h1>; // $ threatModelSource=view-component-input
|
||||
}
|
||||
});
|
||||
}); // $ reactComponent
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
class Hello extends React.Component {
|
||||
class Hello extends React.Component { // $ threatModelSource=view-component-input
|
||||
render() {
|
||||
return <div>Hello {this.props.name}</div>;
|
||||
return <div>Hello {this.props.name}</div>; // $ threatModelSource=view-component-input
|
||||
}
|
||||
static info() {
|
||||
return "Nothing to see here.";
|
||||
}
|
||||
}
|
||||
} // $ reactComponent
|
||||
Hello.displayName = 'Hello';
|
||||
Hello.defaultProps = {
|
||||
name: 'world'
|
||||
@@ -17,4 +17,4 @@ class Hello2 extends React.Component {
|
||||
this.state.bar.foo = 42;
|
||||
this.state = { baz: 42};
|
||||
}
|
||||
}
|
||||
} // $ reactComponent
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export function MyComponent(props) {
|
||||
export function MyComponent(props) { // $ threatModelSource=view-component-input
|
||||
return <div style={{color: props.color}}/>
|
||||
}
|
||||
} // $ reactComponent
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import semmle.javascript.frameworks.React
|
||||
|
||||
query predicate test_getADirectStateAccess(ReactComponent c, DataFlow::SourceNode res) {
|
||||
res = c.getADirectStateAccess()
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MyComponent } from "./exportedComponent";
|
||||
|
||||
export function render({color, location}) {
|
||||
return <MyComponent color={color}/>
|
||||
}
|
||||
export function render({color, location}) { // $ threatModelSource=view-component-input locationSource threatModelSource=remote
|
||||
return <MyComponent color={color}/> // $ getACandidatePropsValue
|
||||
} // $ reactComponent
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component } from "react";
|
||||
|
||||
class C extends Component {}
|
||||
class C extends Component {} // $ threatModelSource=view-component-input reactComponent
|
||||
|
||||
class D extends C {}
|
||||
class D extends C {} // $ threatModelSource=view-component-input reactComponent
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
function Hello(props) {
|
||||
function Hello(props) { // $ threatModelSource=view-component-input
|
||||
return <div>Hello {props.name}</div>;
|
||||
}
|
||||
} // $ reactComponent
|
||||
|
||||
function Hello2(props) {
|
||||
function Hello2(props) { // $ threatModelSource=view-component-input
|
||||
return React.createElement("div");
|
||||
}
|
||||
} // $ reactComponent
|
||||
|
||||
function Hello3(props) {
|
||||
function Hello3(props) { // $ threatModelSource=view-component-input
|
||||
var x = React.createElement("div");
|
||||
return x;
|
||||
}
|
||||
} // $ reactComponent
|
||||
|
||||
function NotAComponent(props) {
|
||||
if (y)
|
||||
@@ -17,8 +17,8 @@ function NotAComponent(props) {
|
||||
return g();
|
||||
}
|
||||
|
||||
function SpuriousComponent(props) {
|
||||
function SpuriousComponent(props) { // $ threatModelSource=view-component-input
|
||||
if (y)
|
||||
return React.createElement("div");
|
||||
return 42;
|
||||
}
|
||||
} // $ reactComponent
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
class Hello extends Preact.Component {
|
||||
render(props, state) {
|
||||
class Hello extends Preact.Component { // $ threatModelSource=view-component-input
|
||||
render(props, state) { // $ threatModelSource=view-component-input
|
||||
props.name;
|
||||
state.name;
|
||||
return <div/>;
|
||||
}
|
||||
}
|
||||
} // $ reactComponent
|
||||
|
||||
class Hello extends preact.Component {
|
||||
class Hello extends preact.Component { // $ threatModelSource=view-component-input
|
||||
|
||||
}
|
||||
} // $ reactComponent
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Hello extends Component {
|
||||
class Hello extends Component { // $ threatModelSource=view-component-input
|
||||
render() {
|
||||
this.props.name;
|
||||
this.props.name; // $ threatModelSource=view-component-input
|
||||
return <div/>;
|
||||
}
|
||||
}
|
||||
} // $ reactComponent
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
function ES2015() {
|
||||
class C extends React.Component {
|
||||
}
|
||||
class C extends React.Component { // $ threatModelSource=view-component-input
|
||||
} // $ reactComponent
|
||||
|
||||
C.defaultProps = { propFromDefaultProps: "propFromDefaultProps" };
|
||||
C.defaultProps = { propFromDefaultProps: "propFromDefaultProps" }; // $ getACandidatePropsValue
|
||||
|
||||
(<C propFromJSX={"propFromJSX"}/>);
|
||||
(<C propFromJSX={"propFromJSX"}/>); // $ getACandidatePropsValue
|
||||
|
||||
new C({propFromConstructor: "propFromConstructor"});
|
||||
new C({propFromConstructor: "propFromConstructor"}); // $ getACandidatePropsValue
|
||||
}
|
||||
|
||||
function ES5() {
|
||||
var C = React.createClass({
|
||||
getDefaultProps() {
|
||||
return { propFromDefaultProps: "propFromDefaultProps" };
|
||||
return { propFromDefaultProps: "propFromDefaultProps" }; // $ getACandidatePropsValue
|
||||
}
|
||||
});
|
||||
}); // $ reactComponent
|
||||
|
||||
(<C propFromJSX={"propFromJSX"}/>);
|
||||
(<C propFromJSX={"propFromJSX"}/>); // $ getACandidatePropsValue
|
||||
|
||||
C({propFromConstructor: "propFromConstructor"});
|
||||
C({propFromConstructor: "propFromConstructor"}); // $ getACandidatePropsValue
|
||||
|
||||
}
|
||||
|
||||
function Functional() {
|
||||
function C(props) {
|
||||
function C(props) { // $ threatModelSource=view-component-input
|
||||
return <div/>;
|
||||
}
|
||||
} // $ reactComponent
|
||||
|
||||
C.defaultProps = { propFromDefaultProps: "propFromDefaultProps" };
|
||||
C.defaultProps = { propFromDefaultProps: "propFromDefaultProps" }; // $ getACandidatePropsValue
|
||||
|
||||
(<C propFromJSX={"propFromJSX"}/>);
|
||||
(<C propFromJSX={"propFromJSX"}/>); // $ getACandidatePropsValue
|
||||
|
||||
new C({propFromConstructor: "propFromConstructor"});
|
||||
new C({propFromConstructor: "propFromConstructor"}); // $ getACandidatePropsValue
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class C extends React.Component {
|
||||
class C extends React.Component { // $ threatModelSource=view-component-input
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
return {};
|
||||
}
|
||||
@@ -8,4 +8,4 @@ class C extends React.Component {
|
||||
getSnapshotBeforeUpdate(prevProps, prevState) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
} // $ reactComponent
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user