-
Notifications
You must be signed in to change notification settings - Fork 3.1k
feat(policy): add opt-in allow-all network posture #5092
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Allow-all network posture — opt-in via the "allow-all" policy tier | ||
| # (NEMOCLAW_POLICY_TIER=allow-all) or `shields down --policy allow-all`. | ||
| # | ||
| # A single catch-all network policy (host: "*") permits egress to ANY public | ||
| # host on ports 80/443 with full L7 access. This is the only NemoClaw policy | ||
| # that uses a bare "*" host; the dangerous-host guardrail in | ||
| # scripts/validate-configs.ts exempts files named *-allow-all.yaml for exactly | ||
| # this reason (subdomain wildcards remain the norm everywhere else). | ||
| # | ||
| # WARNING: This disables egress filtering. Every reachable host becomes a | ||
| # potential data-exfiltration path (workspace files, credentials, conversation | ||
| # history). SSRF / private-network blocking (ssrf.ts, private-networks.ts) still | ||
| # applies — localhost, link-local, and RFC1918 ranges remain blocked. Do not use | ||
| # in production. Prefer a specific tier + presets, or operator approval, instead. | ||
| # | ||
| # NOTE: bare host "*" support in the OpenShell L7 proxy must be verified on a | ||
| # live sandbox before relying on this file. If the proxy rejects "*", switch the | ||
| # endpoints below to `enforcement: audit` (log-only, non-blocking). See the | ||
| # allow-all rollout task / docs/reference/network-policies.mdx. | ||
|
|
||
| version: 1 | ||
|
|
||
| filesystem_policy: | ||
| # Mirrors openclaw-sandbox-permissive.yaml. OpenShell rejects include_workdir | ||
| # changes and filesystem path removals on live sandboxes. | ||
| include_workdir: true | ||
| read_only: | ||
| - /usr | ||
| - /lib | ||
| - /proc | ||
| - /dev/urandom | ||
| - /app | ||
| - /etc | ||
| - /var/log | ||
| read_write: | ||
| - /tmp | ||
| - /dev/null | ||
| - /sandbox/.openclaw | ||
| - /sandbox/.nemoclaw | ||
| - /home/linuxbrew | ||
|
|
||
| landlock: | ||
| compatibility: best_effort | ||
|
|
||
| process: | ||
| run_as_user: sandbox | ||
| run_as_group: sandbox | ||
|
|
||
| network_policies: | ||
| allow_all: | ||
| name: allow_all | ||
| endpoints: | ||
| - host: "*" | ||
| port: 443 | ||
| protocol: rest | ||
| enforcement: enforce | ||
| access: full | ||
| - host: "*" | ||
| port: 80 | ||
| protocol: rest | ||
| enforcement: enforce | ||
| access: full | ||
| binaries: | ||
| - { path: "/**" } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,3 +43,14 @@ tiers: | |
| - { name: whatsapp, access: read-write } | ||
| - { name: jira, access: read-write } | ||
| - { name: outlook, access: read-write } | ||
|
|
||
| # DANGER: disables egress filtering. Unlike the other tiers, allow-all does | ||
| # NOT layer presets onto the deny-by-default base — it swaps the base policy | ||
| # for openclaw-sandbox-allow-all.yaml (a catch-all host: "*"), so the agent can | ||
| # reach ANY public host. SSRF / private-network blocking still applies. Has no | ||
| # presets by design; the catch-all makes per-service presets moot. Opt in only | ||
| # for trusted dev/testing. See docs/reference/network-policies.mdx. | ||
| - name: allow-all | ||
| label: Allow All (no egress filtering — dev/testing only) | ||
| description: Catch-all egress to any public host. Swaps the base policy for the allow-all catch-all; presets do not apply. Maximum scope — you accept full responsibility. | ||
| presets: [] | ||
|
Comment on lines
+53
to
+56
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Call out the The selector text reads like unrestricted outbound access, but 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ | |
| // npx tsx scripts/validate-configs.ts # validate all known config files | ||
| // npx tsx scripts/validate-configs.ts --file <config> --schema <schema> # validate one file | ||
|
|
||
| import { existsSync, readFileSync, readdirSync } from "node:fs"; | ||
| import { existsSync, readdirSync, readFileSync } from "node:fs"; | ||
| import { dirname, join, relative } from "node:path"; | ||
| import { fileURLToPath } from "node:url"; | ||
| import Ajv from "ajv/dist/2020.js"; | ||
|
|
@@ -46,6 +46,7 @@ function discoverTargets(): ConfigTarget[] { | |
| files: [ | ||
| "nemoclaw-blueprint/policies/openclaw-sandbox.yaml", | ||
| "nemoclaw-blueprint/policies/openclaw-sandbox-permissive.yaml", | ||
| "nemoclaw-blueprint/policies/openclaw-sandbox-allow-all.yaml", | ||
| ], | ||
| }, | ||
| { | ||
|
|
@@ -209,6 +210,24 @@ interface DangerousHostFinding { | |
| host: string; | ||
| } | ||
|
|
||
| /** | ||
| * Designated allow-all policy files (basename `*-allow-all.yaml`) are the one | ||
| * place a bare catch-all `host: "*"` is intentional — they implement the opt-in | ||
| * allow-all posture. Such files are exempted ONLY for the bare wildcard family | ||
| * (`*` / `*:port`); IP catch-alls (`0.0.0.0/0`, `::/0`, …) stay rejected | ||
| * everywhere so a typo can never silently widen egress to raw address ranges. | ||
| */ | ||
| function isAllowAllPolicyFile(file: string): boolean { | ||
| const base = file.replaceAll("\\", "/").split("/").pop() ?? ""; | ||
| return /-allow-all\.ya?ml$/.test(base); | ||
| } | ||
|
Comment on lines
+220
to
+223
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't key the wildcard exemption off basename alone.
Suggested direction+const ALLOW_ALL_POLICY_FILES = new Set([
+ "nemoclaw-blueprint/policies/openclaw-sandbox-allow-all.yaml",
+]);
+
function isAllowAllPolicyFile(file: string): boolean {
- const base = file.replaceAll("\\", "/").split("/").pop() ?? "";
- return /-allow-all\.ya?ml$/.test(base);
+ return ALLOW_ALL_POLICY_FILES.has(file.replaceAll("\\", "/"));
}If you want this to stay extensible, the important part is scoping the exemption by path/schema kind, not by basename. Also applies to: 363-367 🤖 Prompt for AI Agents |
||
|
|
||
| /** True for the bare wildcard catch-all family ("*" / "*:443"), excluding IP catch-alls. */ | ||
| function isBareWildcardHost(host: string): boolean { | ||
| const trimmed = host.trim(); | ||
| return trimmed === "*" || trimmed.startsWith("*:"); | ||
| } | ||
|
|
||
| const ROUTER_API_BASE_HOST_ALLOWLIST: ReadonlySet<string> = new Set(["integrate.api.nvidia.com"]); | ||
|
|
||
| /** | ||
|
|
@@ -339,7 +358,15 @@ function main(): void { | |
| const schemaErrors = !valid && validate.errors ? validate.errors.length : 0; | ||
| // Semantic check: walk the parsed doc and reject catch-all hosts. | ||
| // Runs regardless of schema outcome so operators see all issues at once. | ||
| const dangerous = [...findDangerousHosts(data), ...findDangerousRouterApiBases(data)]; | ||
| // Designated allow-all files may use the bare wildcard ("*"/"*:port") on | ||
| // purpose; everything else (and all IP catch-alls) stays rejected. | ||
| const allowBareWildcard = isAllowAllPolicyFile(file); | ||
| const dangerous = [ | ||
| ...findDangerousHosts(data).filter( | ||
| (finding) => !(allowBareWildcard && isBareWildcardHost(finding.host)), | ||
| ), | ||
| ...findDangerousRouterApiBases(data), | ||
| ]; | ||
|
|
||
| if (schemaErrors > 0 || dangerous.length > 0) { | ||
| console.error(`FAIL: ${file}`); | ||
|
|
@@ -373,11 +400,13 @@ function main(): void { | |
| // Export for unit tests without re-running main(). | ||
| export { | ||
| DANGEROUS_HOSTS, | ||
| ROUTER_API_BASE_HOST_ALLOWLIST, | ||
| isDangerousHost, | ||
| discoverTargets, | ||
| findDangerousHosts, | ||
| findDangerousRouterApiBases, | ||
| discoverTargets, | ||
| isAllowAllPolicyFile, | ||
| isBareWildcardHost, | ||
| isDangerousHost, | ||
| ROUTER_API_BASE_HOST_ALLOWLIST, | ||
| }; | ||
|
|
||
| // Only run main() when invoked directly (skip on test `import`). | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3364,7 +3364,23 @@ async function createSandbox( | |
| "policies", | ||
| "openclaw-sandbox.yaml", | ||
| ); | ||
| const basePolicyPath = (agent && agentOnboard.getAgentPolicyPath(agent)) || defaultPolicyPath; | ||
| let basePolicyPath = (agent && agentOnboard.getAgentPolicyPath(agent)) || defaultPolicyPath; | ||
| // Allow-all is not a preset bundle — it swaps the base policy for the | ||
| // catch-all (host: "*"). At create time the tier is known only when it comes | ||
| // from the environment (non-interactive) or a recorded recreate; interactive | ||
| // fresh onboards choose the tier post-create and apply it via | ||
| // setupPoliciesWithSelection. Swapping the base here means an env/recreate | ||
| // allow-all sandbox boots permissive from the very first request. | ||
| const recordedTier = registry.getSandbox(sandboxName)?.policyTier ?? null; | ||
| const envTier = (process.env.NEMOCLAW_POLICY_TIER || "").trim().toLowerCase(); | ||
| const allowAllAtCreate = | ||
| envTier === "allow-all" || (recordedTier === "allow-all" && !envTier); | ||
| if (allowAllAtCreate) { | ||
| basePolicyPath = policies.ALLOW_ALL_POLICY_PATH; | ||
|
Comment on lines
+3374
to
+3379
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Read the previous policy tier before the recreate flow clears the registry entry. On the recreate path, Line 3264 already removes the sandbox from the registry before this lookup runs, so Based on PR objectives, recreated 🤖 Prompt for AI Agents |
||
| console.log( | ||
| " Policy tier 'allow-all': booting with catch-all egress (no network filtering).", | ||
| ); | ||
| } | ||
|
Comment on lines
+3367
to
+3383
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win Move the new allow-all onboarding glue behind a helper to get the guardrail green. CI is already failing because As per pipeline failures, Also applies to: 6342-6358 🤖 Prompt for AI AgentsSource: Pipeline failures |
||
| const tokensByEnvKey = Object.fromEntries( | ||
| messagingTokenDefs.map(({ envKey, token }) => [envKey, token]), | ||
| ); | ||
|
|
@@ -6335,6 +6351,7 @@ async function setupPoliciesWithSelection( | |
| syncPresetSelection, | ||
| selectPolicyTier, | ||
| setPolicyTier: (sandbox, tierName) => registry.updateSandbox(sandbox, { policyTier: tierName }), | ||
| applyAllowAllPolicy: (sandbox) => policies.applyAllowAllPolicy(sandbox), | ||
| selectTierPresetsAndAccess, | ||
| parsePolicyPresetEnv, | ||
| env: process.env, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the surrounding tier description so
allow-allis the only documented behavior.This new table row and warning say
allow-allswaps the base policy, but the paragraph above still says the baseline policy is always applied regardless of tier. Please align those statements so the page does not describe two different runtime behaviors.Also applies to: 72-76
🤖 Prompt for AI Agents