Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support Node.js 20 #16

Merged
merged 14 commits into from
Oct 21, 2023
Merged

fix: support Node.js 20 #16

merged 14 commits into from
Oct 21, 2023

Conversation

azu
Copy link
Owner

@azu azu commented Oct 20, 2023

TODO:

  • Cleanup
  • major updates? or patch updates?
    • delete applyRequestFilter

Bug Fixes

v2.0.0 fixes following error on Node.js 20+

Uncaught Error: This is caused by either a bug in Node.js or incorrect usage of Node.js internals.
Please open an issue with this stack trace at https://github.com/nodejs/node/issues

BREAKING CHANGE

Version Node.js 12 Node.js 14 Node.js 16 Node.js 18 Node.js 20
v1.x.x Support Support Support Support Not Support
v2.0.0 No Support No Support No Support Support Support
  • Drop Node.js 12, 14, 16
  • Remove applyRequestFilter(agent) function
    • It is incompatible way for Node.js 20+
    • So, It is just dropped
  • Remove stopPortScanningByUrlRedirection option

}
}
});

it("should allow http://127.0.0.1, but other private ip is disallowed", async () => {
const agent = new RequestFilteringHttpAgent({
allowIPAddressList: ["127.0.0.1"],
allowIPAddressList: ["127.0.0.1", "::1"],
Copy link
Owner Author

@azu azu Oct 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localhost will be ::1 in some env.

@azu azu added the Type: Bug Bug or Bug fixes label Oct 20, 2023
Comment on lines 372 to 379
socket.end(() => {
console.log("end 2");
if (socket.destroyed) {
return;
}
console.log("end 3 ", socket.destroyed);
console.log("end errr", validationError?.message);
socket.destroy(error);
Copy link
Owner Author

@azu azu Oct 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid Node.js ERR_INTERNAL_ASSERTION: end -> destory.

Just call destory and Node.js throws ERR_INTERNAL_ASSERTION like #15

@azu azu added the Type: Breaking Change Includes breaking changes label Oct 21, 2023
const privateIPs = [
`http://0.0.0.0:${TEST_PORT}`, // 0.0.0.0 is special
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move it to IPv4: should not request because it is meta/unspecified IP

Comment on lines 167 to 199
// override http.Agent#createConnection
// https://nodejs.org/api/http.html#http_agent_createconnection_options_callback
// https://nodejs.org/api/net.html#net_net_createconnection_options_connectlistener
createConnection(options: TcpNetConnectOpts, connectionListener?: (error: Error | null, socket: Socket) => void) {
const { host } = options;
if (host !== undefined) {
// Direct ip address request without dns-lookup
// Example: http://127.0.0.1
// https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener
const validationError = validateIPAddress({ address: host }, this.requestFilterOptions);
if (validationError) {
throw validationError;
}
}
// https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener
// @ts-expect-error - @types/node does not defined createConnection
const socket: Socket = super.createConnection(options, connectionListener);
// Request with domain name
// Example: http://127.0.0.1.nip.io/
const onLookup = (err: Error, address: string, family: string | number, host: string): void => {
if (err) {
return;
}
const validationError = validateIPAddress({ address, family, host }, this.requestFilterOptions);
if (validationError) {
socket.removeListener("lookup", onLookup);
socket.end(() => {
socket.destroy(validationError);
});
}
};
socket.addListener("lookup", onLookup);
return socket;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implement validation as Agent subclass.
Overriding instance(applyRequestFilter) cause ERR_INTERNAL_ASSERTION on Node.js

We need to avoid this error.

Comment on lines 190 to 196
const validationError = validateIPAddress({ address, family, host }, this.requestFilterOptions);
if (validationError) {
socket.removeListener("lookup", onLookup);
socket.end(() => {
socket.destroy(validationError);
});
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to call end before destory.

When just call destroy without end, Node.js 20 throws following error.

socket.destroy(validationError);
Error: This is caused by either a bug in Node.js or incorrect usage of Node.js internals.
Please open an issue with this stack trace at https://github.com/nodejs/node/issues

    at new NodeError (node:internal/errors:406:5)
    at assert (node:internal/assert:14:11)
    at internalConnect (node:net:1037:3)
    at defaultTriggerAsyncIdScope (node:internal/async_hooks:464:18)
    at GetAddrInfoReqWrap.emitLookup [as callback] (node:net:1481:9)
    at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:130:8)

Comment on lines -28 to -31
// prevent url redirection attack
// connection not made to private IP adresses where the port is closed
// Default: false
stopPortScanningByUrlRedirection?: boolean;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allowMetaIPAddress and allowPrivateIPAddress has same logics.

@azu azu marked this pull request as ready for review October 21, 2023 05:48
@azu azu merged commit dbdeeb5 into master Oct 21, 2023
@azu azu deleted the node20 branch October 21, 2023 05:51
Comment on lines -246 to -273
it("should fail to make request when stopPortScanningByUrlRedirection option is set to true", async () => {
const closedPort = TEST_PORT + 1;
const privateIPs = [
`http://0.0.0.0:${closedPort}`, // 0.0.0.0 is special
`http://127.0.0.1:${closedPort}`,
`http://[email protected]:${closedPort}`
];
const agent = new RequestFilteringHttpAgent({
stopPortScanningByUrlRedirection: true
});
for (const ipAddress of privateIPs) {
try {
await fetch(ipAddress, {
agent,
timeout: 2000
});
throw new ReferenceError("SHOULD NOT BE CALLED");
} catch (error) {
if (error instanceof ReferenceError) {
assert.fail(error);
}
assert.match(
error.message,
/^DNS lookup (0\.0\.0\.0|127\.0\.0\.1|undefined)\(family:undefined, host:(0\.0\.0\.0|127\.0\.0\.1|undefined)\) is not allowed. Because, It is private IP address.$/g
);
}
}
});
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case is coverred by other tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Breaking Change Includes breaking changes Type: Bug Bug or Bug fixes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Error when running tests on node 20
1 participant