From c6a005a6dc3f6b75b540ce89ca30d8e9b2dabafd Mon Sep 17 00:00:00 2001 From: Abdul Wasay Date: Tue, 25 Aug 2026 00:35:34 +0500 Subject: [PATCH] docs(permissions): correct the network pattern form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Network permission resources are formatted as URIs — tcp://host:port for fetch/http/listen and dns://hostname for dns — and patterns are matched against that whole string by a path glob whose single * does not cross /. The docs described a pattern as "a host (or host:port)" and the shipped allow-one-host example used a bare host, so the documented policy matched nothing: an allowlist denied everything and a blocklist permitted every host. Documents the URI form, fixes the example, and pins the semantics with a kernel test so the documented shape has coverage. --- crates/kernel/tests/permissions.rs | 31 ++++++++++++++++++++++++++++++ docs/content/docs/permissions.mdx | 4 +++- examples/permissions/server.ts | 11 +++++++++-- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/crates/kernel/tests/permissions.rs b/crates/kernel/tests/permissions.rs index 6876bcaaac..48283072da 100644 --- a/crates/kernel/tests/permissions.rs +++ b/crates/kernel/tests/permissions.rs @@ -536,6 +536,37 @@ fn permission_glob_double_star_still_matches_nested_paths() { assert!(permission_glob_matches("tcp://**", "tcp://127.0.0.1:43111")); } +#[test] +fn network_permission_patterns_match_the_resource_uri_not_a_bare_host() { + // Network resources are formatted as URIs (`format_tcp_resource` / + // `format_dns_resource`), so a pattern is matched against the whole URI. + assert!(permission_glob_matches( + "tcp://api.example.com:443", + "tcp://api.example.com:443", + )); + assert!(permission_glob_matches( + "tcp://api.example.com:*", + "tcp://api.example.com:443", + )); + assert!(permission_glob_matches( + "dns://api.example.com", + "dns://api.example.com", + )); + + // A bare host or `host:port` never matches, and a single `*` cannot reach + // past the `//`, so neither form can be used to allow or deny a host. + assert!(!permission_glob_matches( + "api.example.com", + "tcp://api.example.com:443", + )); + assert!(!permission_glob_matches( + "api.example.com:443", + "tcp://api.example.com:443", + )); + assert!(!permission_glob_matches("*", "tcp://api.example.com:443")); + assert!(!permission_glob_matches("*.example.com", "dns://api.example.com")); +} + #[test] fn kernel_vm_config_defaults_to_deny_all_permissions() { let mut kernel = KernelVm::new(MemoryFileSystem::new(), KernelVmConfig::new("vm-defaults")); diff --git a/docs/content/docs/permissions.mdx b/docs/content/docs/permissions.mdx index 5adeec23d4..481538fb7c 100644 --- a/docs/content/docs/permissions.mdx +++ b/docs/content/docs/permissions.mdx @@ -77,7 +77,9 @@ To invert it, flip `default` to `"deny"` and allow just one subtree: ## Allow only specific network hosts -Every non-`fs` scope matches by `patterns` instead of `paths`. For `network`, a pattern is a host (or `host:port`), and the operations are `fetch`, `http`, `dns`, and `listen`. +Every non-`fs` scope matches by `patterns` instead of `paths`. The operations for `network` are `fetch`, `http`, `dns`, and `listen`. + +A `network` pattern is matched against the URI form of the connection, not a bare host: `tcp://:` for `fetch`, `http`, and `listen`, and `dns://` for `dns`. Patterns are globs in which a single `*` does not cross `/`, so use `tcp://api.example.com:*` to allow every port on a host, and `tcp://**` to allow every host. A bare `api.example.com` matches nothing. diff --git a/examples/permissions/server.ts b/examples/permissions/server.ts index f429690da0..df52018425 100644 --- a/examples/permissions/server.ts +++ b/examples/permissions/server.ts @@ -15,11 +15,18 @@ const denyVault = { } satisfies Permissions; // docs:start allow-one-host -// Deny the network by default, allow only api.example.com. +// Deny the network by default, allow only api.example.com (any port). +// Patterns match the URI form of the connection, so a bare host never matches. const allowOneHost = { network: { default: "deny", - rules: [{ mode: "allow", operations: ["*"], patterns: ["api.example.com"] }], + rules: [ + { + mode: "allow", + operations: ["*"], + patterns: ["tcp://api.example.com:*", "dns://api.example.com"], + }, + ], }, } satisfies Permissions; // docs:end allow-one-host