Skip to content

Commit b367d65

Browse files
committed
quic: support --allow-net permissions
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode:Opus 4.6
1 parent facd71e commit b367d65

4 files changed

Lines changed: 80 additions & 1 deletion

File tree

doc/api/quic.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,24 @@ When a `QuicError` is passed to [`stream.destroy()`][] or
304304
`STOP_SENDING` frame sent to the peer. Any other error type falls back to
305305
the negotiated protocol's generic internal error code.
306306

307+
### Permission model
308+
309+
When using the [Permission Model][], the `--allow-net` flag must be passed to
310+
allow QUIC network operations. Without it, calling [`quic.connect()`][] or
311+
[`quic.listen()`][] will throw an `ERR_ACCESS_DENIED` error.
312+
313+
```console
314+
$ node --permission --allow-fs-read=* --experimental-quic index.mjs
315+
Error: Access to this API has been restricted. Use --allow-net to manage permissions.
316+
code: 'ERR_ACCESS_DENIED',
317+
permission: 'Net',
318+
}
319+
```
320+
321+
Creating a [`QuicEndpoint`][] instance without connecting or listening
322+
is permitted even without `--allow-net`, since no network I/O occurs until
323+
[`quic.connect()`][] or [`quic.listen()`][] is called.
324+
307325
## `quic.connect(address[, options])`
308326

309327
<!-- YAML
@@ -3853,6 +3871,7 @@ throughput issues caused by flow control.
38533871
[Callback error handling]: #callback-error-handling
38543872
[JSON-SEQ]: https://www.rfc-editor.org/rfc/rfc7464
38553873
[NSS Key Log Format]: https://udn.realityripple.com/docs/Mozilla/Projects/NSS/Key_Log_Format
3874+
[Permission Model]: permissions.md#permission-model
38563875
[RFC 8999]: https://www.rfc-editor.org/rfc/rfc8999
38573876
[RFC 9000]: https://www.rfc-editor.org/rfc/rfc9000
38583877
[RFC 9001]: https://www.rfc-editor.org/rfc/rfc9001
@@ -3872,6 +3891,7 @@ throughput issues caused by flow control.
38723891
[RFC 9443]: https://www.rfc-editor.org/rfc/rfc9443
38733892
[`PerformanceEntry`]: perf_hooks.md#class-performanceentry
38743893
[`PerformanceObserver`]: perf_hooks.md#class-performanceobserver
3894+
[`QuicEndpoint`]: #class-quicendpoint
38753895
[`QuicError`]: #class-quicerror
38763896
[`application.enableConnectProtocol`]: #sessionoptionsapplication
38773897
[`application.enableDatagrams`]: #sessionoptionsapplication

src/quic/endpoint.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <node_external_reference.h>
1212
#include <node_process-inl.h>
1313
#include <node_sockaddr-inl.h>
14+
#include <permission/permission.h>
1415
#include <timer_wrap-inl.h>
1516
#include <util-inl.h>
1617
#include <uv.h>
@@ -1745,6 +1746,11 @@ JS_METHOD_IMPL(Endpoint::DoConnect) {
17451746
SocketAddressBase* address;
17461747
ASSIGN_OR_RETURN_UNWRAP(&address, args[0]);
17471748

1749+
THROW_IF_INSUFFICIENT_PERMISSIONS(
1750+
env,
1751+
permission::PermissionScope::kNet,
1752+
address->address()->ToString());
1753+
17481754
DCHECK(args[1]->IsObject());
17491755
Session::Options options;
17501756
if (!Session::Options::From(env, args[1]).To(&options)) {
@@ -1771,6 +1777,9 @@ JS_METHOD_IMPL(Endpoint::DoListen) {
17711777
ASSIGN_OR_RETURN_UNWRAP(&endpoint, args.This());
17721778
auto env = Environment::GetCurrent(args);
17731779

1780+
THROW_IF_INSUFFICIENT_PERMISSIONS(
1781+
env, permission::PermissionScope::kNet, "");
1782+
17741783
Session::Options options;
17751784
if (Session::Options::From(env, args[0]).To(&options)) {
17761785
endpoint->Listen(options);

test/cctest/test_sockaddr.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ TEST(SocketAddress, SocketAddress) {
4444
}
4545

4646
TEST(SocketAddress, IpHashAndIpEqual) {
47-
sockaddr_storage s1, s2, s3, s4;
47+
sockaddr_storage s1, s2, s3;
4848
// Same IP, different ports.
4949
SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 443, &s1);
5050
SocketAddress::ToSockAddr(AF_INET, "10.0.0.1", 8080, &s2);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Flags: --permission --allow-fs-read=* --experimental-quic --no-warnings
2+
import { hasQuic, skip, mustNotCall } from '../common/index.mjs';
3+
import assert from 'node:assert';
4+
import * as fixtures from '../common/fixtures.mjs';
5+
6+
if (!hasQuic) {
7+
skip('QUIC is not enabled');
8+
}
9+
10+
const { createPrivateKey } = await import('node:crypto');
11+
const { connect, listen, QuicEndpoint } = await import('node:quic');
12+
13+
// Verify that the permission system correctly reports no net access.
14+
assert.ok(!process.permission.has('net'));
15+
16+
const key = createPrivateKey(fixtures.readKey('agent1-key.pem'));
17+
const cert = fixtures.readKey('agent1-cert.pem');
18+
19+
// Test: connect() should reject with ERR_ACCESS_DENIED
20+
{
21+
await assert.rejects(
22+
connect('127.0.0.1:12345', { alpn: 'h3' }),
23+
{
24+
code: 'ERR_ACCESS_DENIED',
25+
permission: 'Net',
26+
},
27+
);
28+
}
29+
30+
// Test: listen() should throw ERR_ACCESS_DENIED
31+
{
32+
await assert.rejects(
33+
listen(mustNotCall('onsession should not be called'), {
34+
alpn: ['h3'],
35+
sni: { '*': { keys: [key], certs: [cert] } },
36+
}),
37+
{
38+
code: 'ERR_ACCESS_DENIED',
39+
permission: 'Net',
40+
},
41+
);
42+
}
43+
44+
// Test: Creating a QuicEndpoint without connect/listen is allowed
45+
// since no network I/O occurs at construction time.
46+
{
47+
const endpoint = new QuicEndpoint();
48+
// The endpoint exists but has not performed any network operations.
49+
await endpoint.close();
50+
}

0 commit comments

Comments
 (0)