Merge branch 'main' into js/no-type-extraction

This commit is contained in:
Asger F
2025-07-02 13:18:05 +02:00
3422 changed files with 206060 additions and 90419 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Removed `encodeURI` and `escape` functions from the sanitizer list for request forgery.

View File

@@ -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.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Improved taint tracking through calls to `serialize-javascript`.

View 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`.

View 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.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 2.6.5
lastReleaseVersion: 2.6.6

View File

@@ -0,0 +1,6 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: summaryModel
data:
- ["react", "Member[use]", "Argument[0].Awaited", "ReturnValue", "value"]

View File

@@ -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

View File

@@ -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

View File

@@ -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.
*/

View File

@@ -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()
)
}

View File

@@ -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() {

View File

@@ -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
}
}
}

View File

@@ -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)
)
}

View File

@@ -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" }
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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>

View File

@@ -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."

View File

@@ -1,8 +0,0 @@
on: issue_comment
jobs:
echo-body:
runs-on: ubuntu-latest
steps:
- run: |
echo '${{ github.event.comment.body }}'

View File

@@ -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 }}'

View File

@@ -1,10 +0,0 @@
on: issue_comment
jobs:
echo-body:
runs-on: ubuntu-latest
steps:
- env:
BODY: ${{ github.event.issue.body }}
run: |
echo "$BODY"

View File

@@ -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>

View File

@@ -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."

View File

@@ -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: .

View File

@@ -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: .

View File

@@ -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.

View File

@@ -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.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* The `js/loop-iteration-skipped-due-to-shifting` query now has the `reliability` tag.

View File

@@ -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.

View File

@@ -1,4 +0,0 @@
---
category: queryMetadata
---
* Added `reliability` tag to the `js/suspicious-method-name-declaration` query.

View File

@@ -1,4 +0,0 @@
---
category: queryMetadata
---
* Added `reliability` and `language-features` tags to the `js/template-syntax-in-string-literal` query.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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}`

View 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.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.6.2
lastReleaseVersion: 1.7.0

View File

@@ -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\/.*/

View File

@@ -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>

View File

@@ -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'."

View File

@@ -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!'
});

View File

@@ -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!

View File

@@ -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/

View File

@@ -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()
}
}
}

View File

@@ -1,5 +1,5 @@
name: codeql/javascript-queries
version: 1.6.3-dev
version: 1.7.1-dev
groups:
- javascript
- queries

View File

@@ -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

View File

@@ -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
});

View File

@@ -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())
)
}

View File

@@ -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

View File

@@ -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()))
}

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -1,10 +0,0 @@
on:
pull_request_target:
jobs:
echo-chamber:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: master

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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'. |

View File

@@ -1 +0,0 @@
experimental/Security/CWE-094/UntrustedCheckout.ql

View File

@@ -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))

View File

@@ -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

View 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
}

View File

@@ -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 |

View File

@@ -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;
}
}

View File

@@ -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 { }

View File

@@ -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
}
}

View File

@@ -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;
}

View File

@@ -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,
},
],
};
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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 |

View File

@@ -1,3 +0,0 @@
import semmle.javascript.frameworks.React
query predicate test_ReactComponent(ReactComponent c) { any() }

View File

@@ -1,5 +0,0 @@
import javascript
query predicate test_ReactComponent_getACandidatePropsValue(DataFlow::Node res) {
exists(ReactComponent c | res = c.getACandidatePropsValue(_))
}

View File

@@ -1,7 +0,0 @@
import semmle.javascript.frameworks.React
query predicate test_ReactComponent_getACandidateStateSource(
ReactComponent c, DataFlow::SourceNode res
) {
res = c.getACandidateStateSource()
}

View File

@@ -1,5 +0,0 @@
import semmle.javascript.frameworks.React
query predicate test_ReactComponent_getADirectPropsSource(ReactComponent c, DataFlow::SourceNode res) {
res = c.getADirectPropsAccess()
}

View File

@@ -1,7 +0,0 @@
import semmle.javascript.frameworks.React
query predicate test_ReactComponent_getAPreviousStateSource(
ReactComponent c, DataFlow::SourceNode res
) {
res = c.getAPreviousStateSource()
}

View File

@@ -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)
}

View File

@@ -1,5 +0,0 @@
import semmle.javascript.frameworks.React
query predicate test_ReactComponent_getInstanceMethod(ReactComponent c, string n, Function res) {
res = c.getInstanceMethod(n)
}

View File

@@ -1,3 +0,0 @@
import semmle.javascript.frameworks.React
query predicate test_ReactComponent_ref(ReactComponent c, DataFlow::Node res) { res = c.ref() }

View File

@@ -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 }

View File

@@ -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

View File

@@ -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

View File

@@ -1,3 +1,3 @@
export function MyComponent(props) {
export function MyComponent(props) { // $ threatModelSource=view-component-input
return <div style={{color: props.color}}/>
}
} // $ reactComponent

View File

@@ -1,5 +0,0 @@
import semmle.javascript.frameworks.React
query predicate test_getADirectStateAccess(ReactComponent c, DataFlow::SourceNode res) {
res = c.getADirectStateAccess()
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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