Merge tag 'codeql-cli/latest' into auto/sync-main-pr

Compatible with the latest released version of the CodeQL CLI
This commit is contained in:
dilanbhalla
2025-06-11 17:00:14 +00:00
1052 changed files with 28053 additions and 10586 deletions

View File

@@ -2,4 +2,5 @@ ql/javascript/ql/src/Declarations/IneffectiveParameterType.ql
ql/javascript/ql/src/Expressions/ExprHasNoEffect.ql
ql/javascript/ql/src/Expressions/MissingAwait.ql
ql/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql
ql/javascript/ql/src/Quality/UnhandledErrorInStreamPipeline.ql
ql/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql

View File

@@ -1,3 +1,9 @@
## 2.6.5
### Minor Analysis Improvements
* Added taint flow through the `URL` constructor from the `url` package, improving the identification of SSRF vulnerabilities.
## 2.6.4
### Minor Analysis Improvements

View File

@@ -0,0 +1,5 @@
## 2.6.5
### Minor Analysis Improvements
* Added taint flow through the `URL` constructor from the `url` package, improving the identification of SSRF vulnerabilities.

View File

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

View File

@@ -1,5 +1,5 @@
name: codeql/javascript-all
version: 2.6.4
version: 2.6.5
groups: javascript
dbscheme: semmlecode.javascript.dbscheme
extractor: javascript

View File

@@ -550,20 +550,25 @@ class DirectiveTargetName extends string {
*
* See https://docs.angularjs.org/api/ng/service/$location for details.
*/
private class LocationFlowSource extends RemoteFlowSource instanceof DataFlow::MethodCallNode {
private class LocationFlowSource extends ClientSideRemoteFlowSource instanceof DataFlow::MethodCallNode
{
private ClientSideRemoteFlowKind kind;
LocationFlowSource() {
exists(ServiceReference service, string m, int n |
service.getName() = "$location" and
this = service.getAMethodCall(m) and
n = super.getNumArgument()
|
m = "search" and n < 2
m = "search" and n < 2 and kind.isQuery()
or
m = "hash" and n = 0
m = "hash" and n = 0 and kind.isFragment()
)
}
override string getSourceType() { result = "$location" }
override ClientSideRemoteFlowKind getKind() { result = kind }
}
/**

View File

@@ -82,6 +82,13 @@ module RequestForgery {
pred = url.getArgument(0)
)
or
exists(DataFlow::NewNode url |
url = API::moduleImport("url").getMember("URL").getAnInstantiation()
|
succ = url and
pred = url.getArgument(0)
)
or
exists(HtmlSanitizerCall call |
pred = call.getInput() and
succ = call

View File

@@ -1,3 +1,7 @@
## 1.6.2
No user-facing changes.
## 1.6.1
### Minor Analysis Improvements

View File

@@ -0,0 +1,44 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
In Node.js, calling the <code>pipe()</code> method on a stream without proper error handling can lead to unexplained failures, where errors are dropped and not propagated downstream. This can result in unwanted behavior and make debugging difficult. To reliably handle all errors, every stream in the pipeline must have an error handler registered.
</p>
</overview>
<recommendation>
<p>
Instead of using <code>pipe()</code> with manual error handling, prefer using the <code>pipeline</code> function from the Node.js <code>stream</code> module. The <code>pipeline</code> function automatically handles errors and ensures proper cleanup of resources. This approach is more robust and eliminates the risk of forgetting to handle errors.
</p>
<p>
If you must use <code>pipe()</code>, always attach an error handler to the source stream using methods like <code>on('error', handler)</code> to ensure that any errors emitted by the input stream are properly handled. When multiple <code>pipe()</code> calls are chained, an error handler should be attached before each step of the pipeline.
</p>
</recommendation>
<example>
<p>
The following code snippet demonstrates a problematic usage of the <code>pipe()</code> method without error handling:
</p>
<sample src="examples/UnhandledStreamPipe.js" />
<p>
A better approach is to use the <code>pipeline</code> function, which automatically handles errors:
</p>
<sample src="examples/UnhandledStreamPipeGood.js" />
<p>
Alternatively, if you need to use <code>pipe()</code>, make sure to add error handling:
</p>
<sample src="examples/UnhandledStreamPipeManualError.js" />
</example>
<references>
<li>Node.js Documentation: <a href="https://nodejs.org/api/stream.html#streampipelinestreams-callback">stream.pipeline()</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,303 @@
/**
* @id js/unhandled-error-in-stream-pipeline
* @name Unhandled error in stream pipeline
* @description Calling `pipe()` on a stream without error handling will drop errors coming from the input stream
* @kind problem
* @problem.severity warning
* @precision high
* @tags quality
* maintainability
* error-handling
* frameworks/nodejs
*/
import javascript
import semmle.javascript.filters.ClassifyFiles
/**
* A call to the `pipe` method on a Node.js stream.
*/
class PipeCall extends DataFlow::MethodCallNode {
PipeCall() {
this.getMethodName() = "pipe" and
this.getNumArgument() = [1, 2] and
not this.getArgument([0, 1]).asExpr() instanceof Function and
not this.getArgument(0).asExpr() instanceof ObjectExpr and
not this.getArgument(0).getALocalSource() = getNonNodeJsStreamType()
}
/** Gets the source stream (receiver of the pipe call). */
DataFlow::Node getSourceStream() { result = this.getReceiver() }
/** Gets the destination stream (argument of the pipe call). */
DataFlow::Node getDestinationStream() { result = this.getArgument(0) }
}
/**
* Gets a reference to a value that is known to not be a Node.js stream.
* This is used to exclude pipe calls on non-stream objects from analysis.
*/
private DataFlow::Node getNonNodeJsStreamType() {
result = getNonStreamApi().getAValueReachableFromSource()
}
/**
* Gets API nodes from modules that are known to not provide Node.js streams.
* This includes reactive programming libraries, frontend frameworks, and other non-stream APIs.
*/
private API::Node getNonStreamApi() {
exists(string moduleName |
moduleName
.regexpMatch([
"rxjs(|/.*)", "@strapi(|/.*)", "highland(|/.*)", "execa(|/.*)", "arktype(|/.*)",
"@ngrx(|/.*)", "@datorama(|/.*)", "@angular(|/.*)", "react.*", "@langchain(|/.*)",
]) and
result = API::moduleImport(moduleName)
)
or
result = getNonStreamApi().getAMember()
or
result = getNonStreamApi().getAParameter().getAParameter()
or
result = getNonStreamApi().getReturn()
or
result = getNonStreamApi().getPromised()
}
/**
* Gets the method names used to register event handlers on Node.js streams.
* These methods are used to attach handlers for events like `error`.
*/
private string getEventHandlerMethodName() { result = ["on", "once", "addListener"] }
/**
* Gets the method names that are chainable on Node.js streams.
*/
private string getChainableStreamMethodName() {
result =
[
"setEncoding", "pause", "resume", "unpipe", "destroy", "cork", "uncork", "setDefaultEncoding",
"off", "removeListener", getEventHandlerMethodName()
]
}
/**
* Gets the method names that are not chainable on Node.js streams.
*/
private string getNonchainableStreamMethodName() {
result = ["read", "write", "end", "pipe", "unshift", "push", "isPaused", "wrap", "emit"]
}
/**
* Gets the property names commonly found on Node.js streams.
*/
private string getStreamPropertyName() {
result =
[
"readable", "writable", "destroyed", "closed", "readableHighWaterMark", "readableLength",
"readableObjectMode", "readableEncoding", "readableFlowing", "readableEnded", "flowing",
"writableHighWaterMark", "writableLength", "writableObjectMode", "writableFinished",
"writableCorked", "writableEnded", "defaultEncoding", "allowHalfOpen", "objectMode",
"errored", "pending", "autoDestroy", "encoding", "path", "fd", "bytesRead", "bytesWritten",
"_readableState", "_writableState"
]
}
/**
* Gets all method names commonly found on Node.js streams.
*/
private string getStreamMethodName() {
result = [getChainableStreamMethodName(), getNonchainableStreamMethodName()]
}
/**
* A call to register an event handler on a Node.js stream.
* This includes methods like `on`, `once`, and `addListener`.
*/
class ErrorHandlerRegistration extends DataFlow::MethodCallNode {
ErrorHandlerRegistration() {
this.getMethodName() = getEventHandlerMethodName() and
this.getArgument(0).getStringValue() = "error"
}
}
/**
* Holds if the stream in `node1` will propagate to `node2`.
*/
private predicate streamFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(PipeCall pipe |
node1 = pipe.getDestinationStream() and
node2 = pipe
)
or
exists(DataFlow::MethodCallNode chainable |
chainable.getMethodName() = getChainableStreamMethodName() and
node1 = chainable.getReceiver() and
node2 = chainable
)
}
/**
* Tracks the result of a pipe call as it flows through the program.
*/
private DataFlow::SourceNode destinationStreamRef(DataFlow::TypeTracker t, PipeCall pipe) {
t.start() and
(result = pipe or result = pipe.getDestinationStream().getALocalSource())
or
exists(DataFlow::SourceNode prev |
prev = destinationStreamRef(t.continue(), pipe) and
streamFlowStep(prev, result)
)
or
exists(DataFlow::TypeTracker t2 | result = destinationStreamRef(t2, pipe).track(t2, t))
}
/**
* Gets a reference to the result of a pipe call.
*/
private DataFlow::SourceNode destinationStreamRef(PipeCall pipe) {
result = destinationStreamRef(DataFlow::TypeTracker::end(), pipe)
}
/**
* Holds if the pipe call result is used to call a non-stream method.
* Since pipe() returns the destination stream, this finds cases where
* the destination stream is used with methods not typical of streams.
*/
private predicate isPipeFollowedByNonStreamMethod(PipeCall pipeCall) {
exists(DataFlow::MethodCallNode call |
call = destinationStreamRef(pipeCall).getAMethodCall() and
not call.getMethodName() = getStreamMethodName()
)
}
/**
* Holds if the pipe call result is used to access a property that is not typical of streams.
*/
private predicate isPipeFollowedByNonStreamProperty(PipeCall pipeCall) {
exists(DataFlow::PropRef propRef |
propRef = destinationStreamRef(pipeCall).getAPropertyRead() and
not propRef.getPropertyName() = [getStreamPropertyName(), getStreamMethodName()]
)
}
/**
* Holds if the pipe call result is used in a non-stream-like way,
* either by calling non-stream methods or accessing non-stream properties.
*/
private predicate isPipeFollowedByNonStreamAccess(PipeCall pipeCall) {
isPipeFollowedByNonStreamMethod(pipeCall) or
isPipeFollowedByNonStreamProperty(pipeCall)
}
/**
* Gets a reference to a stream that may be the source of the given pipe call.
* Uses type back-tracking to trace stream references in the data flow.
*/
private DataFlow::SourceNode sourceStreamRef(DataFlow::TypeBackTracker t, PipeCall pipeCall) {
t.start() and
result = pipeCall.getSourceStream().getALocalSource()
or
exists(DataFlow::SourceNode prev |
prev = sourceStreamRef(t.continue(), pipeCall) and
streamFlowStep(result.getALocalUse(), prev)
)
or
exists(DataFlow::TypeBackTracker t2 | result = sourceStreamRef(t2, pipeCall).backtrack(t2, t))
}
/**
* Gets a reference to a stream that may be the source of the given pipe call.
*/
private DataFlow::SourceNode sourceStreamRef(PipeCall pipeCall) {
result = sourceStreamRef(DataFlow::TypeBackTracker::end(), pipeCall)
}
/**
* Holds if the source stream of the given pipe call has an `error` handler registered.
*/
private predicate hasErrorHandlerRegistered(PipeCall pipeCall) {
exists(DataFlow::Node stream |
stream = sourceStreamRef(pipeCall).getALocalUse() and
(
stream.(DataFlow::SourceNode).getAMethodCall(_) instanceof ErrorHandlerRegistration
or
exists(DataFlow::SourceNode base, string propName |
stream = base.getAPropertyRead(propName) and
base.getAPropertyRead(propName).getAMethodCall(_) instanceof ErrorHandlerRegistration
)
or
exists(DataFlow::PropWrite propWrite, DataFlow::SourceNode instance |
propWrite.getRhs().getALocalSource() = stream and
instance = propWrite.getBase().getALocalSource() and
instance.getAPropertyRead(propWrite.getPropertyName()).getAMethodCall(_) instanceof
ErrorHandlerRegistration
)
)
)
or
hasPlumber(pipeCall)
}
/**
* Holds if the pipe call uses `gulp-plumber`, which automatically handles stream errors.
* `gulp-plumber` returns a stream that uses monkey-patching to ensure all subsequent streams in the pipeline propagate their errors.
*/
private predicate hasPlumber(PipeCall pipeCall) {
pipeCall.getDestinationStream().getALocalSource() = API::moduleImport("gulp-plumber").getACall()
or
sourceStreamRef+(pipeCall) = API::moduleImport("gulp-plumber").getACall()
}
/**
* Holds if the source or destination of the given pipe call is identified as a non-Node.js stream.
*/
private predicate hasNonNodeJsStreamSource(PipeCall pipeCall) {
sourceStreamRef(pipeCall) = getNonNodeJsStreamType() or
destinationStreamRef(pipeCall) = getNonNodeJsStreamType()
}
/**
* Holds if the source stream of the given pipe call is used in a non-stream-like way.
*/
private predicate hasNonStreamSourceLikeUsage(PipeCall pipeCall) {
exists(DataFlow::MethodCallNode call, string name |
call.getReceiver().getALocalSource() = sourceStreamRef(pipeCall) and
name = call.getMethodName() and
not name = getStreamMethodName()
)
or
exists(DataFlow::PropRef propRef, string propName |
propRef.getBase().getALocalSource() = sourceStreamRef(pipeCall) and
propName = propRef.getPropertyName() and
not propName = [getStreamPropertyName(), getStreamMethodName()]
)
}
/**
* Holds if the pipe call destination stream has an error handler registered.
*/
private predicate hasErrorHandlerDownstream(PipeCall pipeCall) {
exists(DataFlow::SourceNode stream |
stream = destinationStreamRef(pipeCall) and
(
exists(ErrorHandlerRegistration handler | handler.getReceiver().getALocalSource() = stream)
or
exists(DataFlow::SourceNode base, string propName |
stream = base.getAPropertyRead(propName) and
base.getAPropertyRead(propName).getAMethodCall(_) instanceof ErrorHandlerRegistration
)
)
)
}
from PipeCall pipeCall
where
not hasErrorHandlerRegistered(pipeCall) and
hasErrorHandlerDownstream(pipeCall) and
not isPipeFollowedByNonStreamAccess(pipeCall) and
not hasNonStreamSourceLikeUsage(pipeCall) and
not hasNonNodeJsStreamSource(pipeCall) and
not isTestFile(pipeCall.getFile())
select pipeCall,
"Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped."

View File

@@ -0,0 +1,8 @@
const fs = require('fs');
const source = fs.createReadStream('source.txt');
const destination = fs.createWriteStream('destination.txt');
// Bad: Only destination has error handling, source errors are unhandled
source.pipe(destination).on('error', (err) => {
console.error('Destination error:', err);
});

View File

@@ -0,0 +1,17 @@
const { pipeline } = require('stream');
const fs = require('fs');
const source = fs.createReadStream('source.txt');
const destination = fs.createWriteStream('destination.txt');
// Good: Using pipeline for automatic error handling
pipeline(
source,
destination,
(err) => {
if (err) {
console.error('Pipeline failed:', err);
} else {
console.log('Pipeline succeeded');
}
}
);

View File

@@ -0,0 +1,16 @@
const fs = require('fs');
const source = fs.createReadStream('source.txt');
const destination = fs.createWriteStream('destination.txt');
// Alternative Good: Manual error handling with pipe()
source.on('error', (err) => {
console.error('Source stream error:', err);
destination.destroy(err);
});
destination.on('error', (err) => {
console.error('Destination stream error:', err);
source.destroy(err);
});
source.pipe(destination);

View File

@@ -0,0 +1,3 @@
## 1.6.2
No user-facing changes.

View File

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

View File

@@ -1,5 +1,5 @@
name: codeql/javascript-queries
version: 1.6.1
version: 1.6.2
groups:
- javascript
- queries

View File

@@ -0,0 +1,19 @@
| test.js:4:5:4:28 | stream. ... nation) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| test.js:19:5:19:17 | s2.pipe(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| test.js:45:5:45:30 | stream2 ... ation2) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| test.js:60:5:60:30 | stream2 ... ation2) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| test.js:66:5:66:21 | stream.pipe(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| test.js:79:5:79:25 | s2.pipe ... ation2) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| test.js:94:5:94:21 | stream.pipe(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| test.js:110:11:110:22 | s.pipe(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| test.js:119:5:119:21 | stream.pipe(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| test.js:128:5:128:26 | getStre ... e(dest) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| test.js:146:5:146:62 | stream. ... itable) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| test.js:182:17:182:40 | notStre ... itable) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| test.js:192:5:192:32 | copyStr ... nation) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| tst.js:8:5:8:21 | source.pipe(gzip) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| tst.js:37:21:37:56 | wrapper ... Stream) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| tst.js:44:5:44:40 | wrapper ... Stream) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| tst.js:52:5:52:37 | source. ... Stream) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| tst.js:59:18:59:39 | stream. ... Stream) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |
| tst.js:111:5:111:26 | stream. ... Stream) | Stream pipe without error handling on the source stream. Errors won't propagate downstream and may be silently dropped. |

View File

@@ -0,0 +1,2 @@
query: Quality/UnhandledErrorInStreamPipeline.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -0,0 +1,3 @@
import { type } from 'arktype';
type.string.pipe(Number);

View File

@@ -0,0 +1,11 @@
const execa = require('execa');
(async () => {
const first = execa('node', ['empty.js']);
const second = execa('node', ['stdin.js']);
first.stdout.pipe(second.stdin);
const {stdout} = await second;
console.log(stdout);
})();

View File

@@ -0,0 +1,29 @@
import React, { Suspense } from "react";
import { renderToPipeableStream } from "react-dom/server";
import { PassThrough } from "stream";
import { act } from "react-dom/test-utils";
const writable = new PassThrough();
let output = "";
writable.on("data", chunk => { output += chunk.toString(); });
writable.on("end", () => { /* stream ended */ });
let errors = [];
let shellErrors = [];
await act(async () => {
renderToPipeableStream(
<Suspense fallback={<Fallback />}>
<Throw />
</Suspense>,
{
onError(err) {
errors.push(err.message);
},
onShellError(err) {
shellErrors.push(err.message);
}
}
).pipe(writable);
});

View File

@@ -0,0 +1,8 @@
const highland = require('highland');
const fs = require('fs');
highland(fs.createReadStream('input.txt'))
.map(line => {
if (line.length === 0) throw new Error('Empty line');
return line;
}).pipe(fs.createWriteStream('output.txt'));

View File

@@ -0,0 +1,15 @@
import { RunnablePassthrough, RunnableSequence } from "@langchain/core/runnables";
const fakeRetriever = RunnablePassthrough.from((_q: string) =>
Promise.resolve([{ pageContent: "Hello world." }])
);
const formatDocumentsAsString = (documents: { pageContent: string }[]) =>documents.map((d) => d.pageContent).join("\n\n");
const chain = RunnableSequence.from([
{
context: fakeRetriever.pipe(formatDocumentsAsString),
question: new RunnablePassthrough(),
},
"",
]);

View File

@@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Observable } from 'rxjs';
@Component({
selector: 'minimal-example',
template: `
<div>{{ value$ | async }}</div>
`
})
export class MinimalExampleComponent {
value$: Observable<any>;
constructor(private store: Store<any>) {
this.value$ = this.store.pipe(select('someSlice'));
}
}

View File

@@ -0,0 +1,20 @@
import * as rx from 'rxjs';
import * as ops from 'rxjs/operators';
import { TestScheduler } from 'rxjs/testing';
import { pluck } from "rxjs/operators/pluck";
const { of, from } = rx;
const { map, filter } = ops;
function f(){
of(1, 2, 3).pipe(map(x => x * 2));
someNonStream().pipe(map(x => x * 2));
let testScheduler = new TestScheduler();
testScheduler.run(({x, y, z}) => {
const source = x('', {o: [a, b, c]});
z(source.pipe(null)).toBe(expected,y,);
});
z.option$.pipe(pluck("x"))
}

View File

@@ -0,0 +1,5 @@
import { async } from '@strapi/utils';
const f = async () => {
const permissionsInDB = await async.pipe(strapi.db.query('x').findMany,map('y'))();
}

View File

@@ -0,0 +1,235 @@
function test() {
{
const stream = getStream();
stream.pipe(destination).on("error", e); // $Alert
}
{
const stream = getStream();
stream.pipe(destination);
stream.on('error', handleError);
}
{
const stream = getStream();
stream.on('error', handleError);
stream.pipe(destination);
}
{
const stream = getStream();
const s2 = stream;
s2.pipe(dest).on("error", e); // $Alert
}
{
const stream = getStream();
stream.on('error', handleError);
const s2 = stream;
s2.pipe(dest);
}
{
const stream = getStream();
const s2 = stream;
s2.on('error', handleError);
s2.pipe(dest);
}
{
const s = getStream().on('error', handler);
const d = getDest();
s.pipe(d);
}
{
getStream().on('error', handler).pipe(dest);
}
{
const stream = getStream();
stream.on('error', handleError);
const stream2 = stream.pipe(destination);
stream2.pipe(destination2).on("error", e); // $Alert
}
{
const stream = getStream();
stream.on('error', handleError);
const destination = getDest();
destination.on('error', handleError);
const stream2 = stream.pipe(destination);
const s3 = stream2;
s = s3.pipe(destination2);
}
{
const stream = getStream();
stream.on('error', handleError);
const stream2 = stream.pipe(destination);
stream2.pipe(destination2).on("error", e); // $Alert
}
{ // Error handler on destination instead of source
const stream = getStream();
const dest = getDest();
dest.on('error', handler);
stream.pipe(dest).on("error", e); // $Alert
}
{ // Multiple aliases, error handler on one
const stream = getStream();
const alias1 = stream;
const alias2 = alias1;
alias2.on('error', handleError);
alias1.pipe(dest);
}
{ // Multiple pipes, handler after first pipe
const stream = getStream();
const s2 = stream.pipe(destination1);
stream.on('error', handleError);
s2.pipe(destination2).on("error", e); // $Alert
}
{ // Handler registered via .once
const stream = getStream();
stream.once('error', handleError);
stream.pipe(dest);
}
{ // Handler registered with arrow function
const stream = getStream();
stream.on('error', (err) => handleError(err));
stream.pipe(dest);
}
{ // Handler registered for unrelated event
const stream = getStream();
stream.on('close', handleClose);
stream.pipe(dest).on("error", e); // $Alert
}
{ // Error handler registered after pipe, but before error
const stream = getStream();
stream.pipe(dest);
setTimeout(() => stream.on('error', handleError), 8000); // $MISSING:Alert
}
{ // Pipe in a function, error handler outside
const stream = getStream();
function doPipe(s) { s.pipe(dest); }
stream.on('error', handleError);
doPipe(stream);
}
{ // Pipe in a function, error handler not set
const stream = getStream();
function doPipe(s) {
f = s.pipe(dest); // $Alert
f.on("error", e);
}
doPipe(stream);
}
{ // Dynamic event assignment
const stream = getStream();
const event = 'error';
stream.on(event, handleError);
stream.pipe(dest).on("error", e); // $SPURIOUS:Alert
}
{ // Handler assigned via variable property
const stream = getStream();
const handler = handleError;
stream.on('error', handler);
stream.pipe(dest);
}
{ // Pipe with no intermediate variable, no error handler
getStream().pipe(dest).on("error", e); // $Alert
}
{ // Handler set via .addListener synonym
const stream = getStream();
stream.addListener('error', handleError);
stream.pipe(dest).on("error", e);
}
{ // Handler set via .once after .pipe
const stream = getStream();
stream.pipe(dest);
stream.once('error', handleError);
}
{ // Long chained pipe with error handler
const stream = getStream();
stream.pause().on('error', handleError).setEncoding('utf8').resume().pipe(writable);
}
{ // Long chained pipe without error handler
const stream = getStream();
stream.pause().setEncoding('utf8').resume().pipe(writable).on("error", e); // $Alert
}
{ // Long chained pipe without error handler
const stream = getStream();
stream.pause().setEncoding('utf8').on("error", e).resume().pipe(writable).on("error", e);
}
{ // Non-stream with pipe method that returns subscribable object (Streams do not have subscribe method)
const notStream = getNotAStream();
notStream.pipe(writable).subscribe();
}
{ // Non-stream with pipe method that returns subscribable object (Streams do not have subscribe method)
const notStream = getNotAStream();
const result = notStream.pipe(writable);
const dealWithResult = (result) => { result.subscribe(); };
dealWithResult(result);
}
{ // Non-stream with pipe method that returns subscribable object (Streams do not have subscribe method)
const notStream = getNotAStream();
const pipeIt = (someVariable) => { return someVariable.pipe(something); };
let x = pipeIt(notStream);
x.subscribe();
}
{ // Calling custom pipe method with no arguments
const notStream = getNotAStream();
notStream.pipe();
}
{ // Calling custom pipe method with more then 2 arguments
const notStream = getNotAStream();
notStream.pipe(arg1, arg2, arg3);
}
{ // Member access on a non-stream after pipe
const notStream = getNotAStream();
const val = notStream.pipe(writable).someMember;
}
{ // Member access on a stream after pipe
const notStream = getNotAStream();
const val = notStream.pipe(writable).on("error", e).readable; // $Alert
}
{ // Method access on a non-stream after pipe
const notStream = getNotAStream();
const val = notStream.pipe(writable).someMethod();
}
{ // Pipe on fs readStream
const fs = require('fs');
const stream = fs.createReadStream('file.txt');
const copyStream = stream;
copyStream.pipe(destination).on("error", e); // $Alert
}
{
const notStream = getNotAStream();
const something = notStream.someNotStreamPropertyAccess;
const val = notStream.pipe(writable);
}
{
const notStream = getNotAStream();
const something = notStream.someNotStreamPropertyAccess();
const val = notStream.pipe(writable);
}
{
const notStream = getNotAStream();
notStream.pipe({});
}
{
const notStream = getNotAStream();
notStream.pipe(()=>{});
}
{
const plumber = require('gulp-plumber');
getStream().pipe(plumber()).pipe(dest).pipe(dest).pipe(dest);
}
{
const plumber = require('gulp-plumber');
const p = plumber();
getStream().pipe(p).pipe(dest).pipe(dest).pipe(dest);
}
{
const plumber = require('gulp-plumber');
const p = plumber();
getStream().pipe(p);
}
{
const plumber = require('gulp-plumber');
const p = plumber();
getStream().pipe(p).pipe(dest);
}
{
const notStream = getNotAStream();
notStream.pipe(getStream(),()=>{});
}
}

View File

@@ -0,0 +1,113 @@
const fs = require('fs');
const zlib = require('zlib');
function foo(){
const source = fs.createReadStream('input.txt');
const gzip = zlib.createGzip();
const destination = fs.createWriteStream('output.txt.gz');
source.pipe(gzip).pipe(destination); // $Alert
gzip.on('error', e);
}
class StreamWrapper {
constructor() {
this.outputStream = getStream();
}
}
function zip() {
const zipStream = createWriteStream(zipPath);
let wrapper = new StreamWrapper();
let stream = wrapper.outputStream;
stream.on('error', e);
stream.pipe(zipStream);
zipStream.on('error', e);
}
function zip1() {
const zipStream = createWriteStream(zipPath);
let wrapper = new StreamWrapper();
wrapper.outputStream.pipe(zipStream);
wrapper.outputStream.on('error', e);
zipStream.on('error', e);
}
function zip2() {
const zipStream = createWriteStream(zipPath);
let wrapper = new StreamWrapper();
let outStream = wrapper.outputStream.pipe(zipStream); // $Alert
outStream.on('error', e);
}
function zip3() {
const zipStream = createWriteStream(zipPath);
let wrapper = new StreamWrapper();
wrapper.outputStream.pipe(zipStream); // $Alert
zipStream.on('error', e);
}
function zip3() {
const zipStream = createWriteStream(zipPath);
let wrapper = new StreamWrapper();
let source = getStream();
source.pipe(wrapper.outputStream); // $Alert
wrapper.outputStream.on('error', e);
}
function zip4() {
const zipStream = createWriteStream(zipPath);
let stream = getStream();
let output = stream.pipe(zipStream); // $Alert
output.on('error', e);
}
class StreamWrapper2 {
constructor() {
this.outputStream = getStream();
this.outputStream.on('error', e);
}
}
function zip5() {
const zipStream = createWriteStream(zipPath);
let wrapper = new StreamWrapper2();
wrapper.outputStream.pipe(zipStream);
zipStream.on('error', e);
}
class StreamWrapper3 {
constructor() {
this.stream = getStream();
}
pipeIt(dest) {
return this.stream.pipe(dest);
}
register_error_handler(listener) {
return this.stream.on('error', listener);
}
}
function zip5() {
const zipStream = createWriteStream(zipPath);
let wrapper = new StreamWrapper3();
wrapper.pipeIt(zipStream); // $MISSING:Alert
zipStream.on('error', e);
}
function zip6() {
const zipStream = createWriteStream(zipPath);
let wrapper = new StreamWrapper3();
wrapper.pipeIt(zipStream);
wrapper.register_error_handler(e);
zipStream.on('error', e);
}
function registerErr(stream, listerner) {
stream.on('error', listerner);
}
function zip7() {
const zipStream = createWriteStream(zipPath);
let stream = getStream();
registerErr(stream, e);
stream.pipe(zipStream); // $SPURIOUS:Alert
zipStream.on('error', e);
}

View File

@@ -30,6 +30,9 @@
| serverSide.js:117:20:117:30 | new ws(url) | serverSide.js:115:25:115:35 | request.url | serverSide.js:117:27:117:29 | url | The $@ of this request depends on a $@. | serverSide.js:117:27:117:29 | url | URL | serverSide.js:115:25:115:35 | request.url | user-provided value |
| serverSide.js:125:5:128:6 | axios({ ... \\n }) | serverSide.js:123:29:123:35 | req.url | serverSide.js:127:14:127:20 | tainted | The $@ of this request depends on a $@. | serverSide.js:127:14:127:20 | tainted | URL | serverSide.js:123:29:123:35 | req.url | user-provided value |
| serverSide.js:131:5:131:20 | axios.get(myUrl) | serverSide.js:123:29:123:35 | req.url | serverSide.js:131:15:131:19 | myUrl | The $@ of this request depends on a $@. | serverSide.js:131:15:131:19 | myUrl | URL | serverSide.js:123:29:123:35 | req.url | user-provided value |
| serverSide.js:141:3:141:30 | axios.g ... ring()) | serverSide.js:139:17:139:29 | req.query.url | serverSide.js:141:13:141:29 | target.toString() | The $@ of this request depends on a $@. | serverSide.js:141:13:141:29 | target.toString() | URL | serverSide.js:139:17:139:29 | req.query.url | user-provided value |
| serverSide.js:142:3:142:19 | axios.get(target) | serverSide.js:139:17:139:29 | req.query.url | serverSide.js:142:13:142:18 | target | The $@ of this request depends on a $@. | serverSide.js:142:13:142:18 | target | URL | serverSide.js:139:17:139:29 | req.query.url | user-provided value |
| serverSide.js:143:3:143:24 | axios.g ... t.href) | serverSide.js:139:17:139:29 | req.query.url | serverSide.js:143:13:143:23 | target.href | The $@ of this request depends on a $@. | serverSide.js:143:13:143:23 | target.href | URL | serverSide.js:139:17:139:29 | req.query.url | user-provided value |
edges
| Request/app/api/proxy/route2.serverSide.ts:4:9:4:15 | { url } | Request/app/api/proxy/route2.serverSide.ts:4:9:4:34 | url | provenance | |
| Request/app/api/proxy/route2.serverSide.ts:4:9:4:34 | url | Request/app/api/proxy/route2.serverSide.ts:5:27:5:29 | url | provenance | |
@@ -106,6 +109,15 @@ edges
| serverSide.js:123:29:123:35 | req.url | serverSide.js:123:19:123:42 | url.par ... , true) | provenance | |
| serverSide.js:130:9:130:45 | myUrl | serverSide.js:131:15:131:19 | myUrl | provenance | |
| serverSide.js:130:37:130:43 | tainted | serverSide.js:130:9:130:45 | myUrl | provenance | |
| serverSide.js:139:9:139:29 | input | serverSide.js:140:26:140:30 | input | provenance | |
| serverSide.js:139:17:139:29 | req.query.url | serverSide.js:139:9:139:29 | input | provenance | |
| serverSide.js:140:9:140:31 | target | serverSide.js:141:13:141:18 | target | provenance | |
| serverSide.js:140:9:140:31 | target | serverSide.js:142:13:142:18 | target | provenance | |
| serverSide.js:140:9:140:31 | target | serverSide.js:143:13:143:18 | target | provenance | |
| serverSide.js:140:18:140:31 | new URL(input) | serverSide.js:140:9:140:31 | target | provenance | |
| serverSide.js:140:26:140:30 | input | serverSide.js:140:18:140:31 | new URL(input) | provenance | Config |
| serverSide.js:141:13:141:18 | target | serverSide.js:141:13:141:29 | target.toString() | provenance | |
| serverSide.js:143:13:143:18 | target | serverSide.js:143:13:143:23 | target.href | provenance | |
nodes
| Request/app/api/proxy/route2.serverSide.ts:4:9:4:15 | { url } | semmle.label | { url } |
| Request/app/api/proxy/route2.serverSide.ts:4:9:4:34 | url | semmle.label | url |
@@ -199,4 +211,14 @@ nodes
| serverSide.js:130:9:130:45 | myUrl | semmle.label | myUrl |
| serverSide.js:130:37:130:43 | tainted | semmle.label | tainted |
| serverSide.js:131:15:131:19 | myUrl | semmle.label | myUrl |
| serverSide.js:139:9:139:29 | input | semmle.label | input |
| serverSide.js:139:17:139:29 | req.query.url | semmle.label | req.query.url |
| serverSide.js:140:9:140:31 | target | semmle.label | target |
| serverSide.js:140:18:140:31 | new URL(input) | semmle.label | new URL(input) |
| serverSide.js:140:26:140:30 | input | semmle.label | input |
| serverSide.js:141:13:141:18 | target | semmle.label | target |
| serverSide.js:141:13:141:29 | target.toString() | semmle.label | target.toString() |
| serverSide.js:142:13:142:18 | target | semmle.label | target |
| serverSide.js:143:13:143:18 | target | semmle.label | target |
| serverSide.js:143:13:143:23 | target.href | semmle.label | target.href |
subpaths

View File

@@ -133,3 +133,12 @@ var server2 = http.createServer(function(req, res) {
var myEncodedUrl = `${something}/bla/${encodeURIComponent(tainted)}`;
axios.get(myEncodedUrl);
})
var server2 = http.createServer(function(req, res) {
const { URL } = require('url');
const input = req.query.url; // $Source[js/request-forgery]
const target = new URL(input);
axios.get(target.toString()); // $Alert[js/request-forgery]
axios.get(target); // $Alert[js/request-forgery]
axios.get(target.href); // $Alert[js/request-forgery]
});