diff --git a/docs/reference/network-policies.md b/docs/reference/network-policies.md index bfbe74e2015..9a2b0ef7ae4 100644 --- a/docs/reference/network-policies.md +++ b/docs/reference/network-policies.md @@ -123,6 +123,24 @@ $ ./scripts/walkthrough.sh This opens a split tmux session with the TUI on the left and the agent on the right. +## Policy Presets + +Presets extend the baseline network policy with additional endpoints for common services. +Apply a preset with `nemoclaw policy-add` and list available presets with `nemoclaw policy-list`. + +| Preset | Endpoints | Use case | +|---|---|---| +| `discord` | `discord.com`, `discordapp.com` | Discord bot and webhook access | +| `docker` | `registry-1.docker.io`, `auth.docker.io`, `index.docker.io` | Docker Hub image pulls | +| `github` | `api.github.com`, `github.com`, `raw.githubusercontent.com`, `uploads.github.com` | GitHub REST API, raw file content, and asset uploads | +| `huggingface` | `huggingface.co`, `cdn-lfs.huggingface.co`, `api-inference.huggingface.co` | Hugging Face Hub and Inference API | +| `jira` | `*.atlassian.net` | Jira issue tracking | +| `npm` | `registry.npmjs.org` | npm package registry | +| `outlook` | `graph.microsoft.com`, `login.microsoftonline.com`, `outlook.office365.com`, `outlook.office.com` | Microsoft Graph and Outlook | +| `pypi` | `pypi.org`, `files.pythonhosted.org` | Python package index | +| `slack` | `slack.com`, `api.slack.com`, `hooks.slack.com` | Slack API and webhooks | +| `telegram` | `api.telegram.org` | Telegram Bot API | + ## Modifying the Policy ### Static Changes diff --git a/nemoclaw-blueprint/policies/presets/github.yaml b/nemoclaw-blueprint/policies/presets/github.yaml new file mode 100644 index 00000000000..9fef520930c --- /dev/null +++ b/nemoclaw-blueprint/policies/presets/github.yaml @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +preset: + name: github + description: "GitHub REST API, raw content, and asset upload access" + +network_policies: + github: + name: github + endpoints: + - host: api.github.com + port: 443 + protocol: rest + enforcement: enforce + tls: terminate + rules: + - allow: { method: GET, path: "/**" } + - allow: { method: POST, path: "/**" } + - allow: { method: PATCH, path: "/**" } + - allow: { method: PUT, path: "/**" } + - allow: { method: DELETE, path: "/**" } + - host: github.com + port: 443 + protocol: rest + enforcement: enforce + tls: terminate + rules: + - allow: { method: GET, path: "/**" } + - allow: { method: POST, path: "/**" } + - host: raw.githubusercontent.com + port: 443 + protocol: rest + enforcement: enforce + tls: terminate + rules: + - allow: { method: GET, path: "/**" } + - host: uploads.github.com + port: 443 + protocol: rest + enforcement: enforce + tls: terminate + rules: + - allow: { method: POST, path: "/**" } diff --git a/test/policies.test.js b/test/policies.test.js index e1b1de0dfb7..9c803b9f22e 100644 --- a/test/policies.test.js +++ b/test/policies.test.js @@ -7,9 +7,9 @@ import policies from "../bin/lib/policies"; describe("policies", () => { describe("listPresets", () => { - it("returns all 9 presets", () => { + it("returns all 10 presets", () => { const presets = policies.listPresets(); - expect(presets.length).toBe(9); + assert.equal(presets.length, 10); }); it("each preset has name and description", () => { @@ -21,8 +21,8 @@ describe("policies", () => { it("returns expected preset names", () => { const names = policies.listPresets().map((p) => p.name).sort(); - const expected = ["discord", "docker", "huggingface", "jira", "npm", "outlook", "pypi", "slack", "telegram"]; - expect(names).toEqual(expected); + const expected = ["discord", "docker", "github", "huggingface", "jira", "npm", "outlook", "pypi", "slack", "telegram"]; + assert.deepEqual(names, expected); }); }); @@ -59,6 +59,15 @@ describe("policies", () => { expect(hosts).toEqual(["api.telegram.org"]); }); + it("extracts hosts from github preset", () => { + const content = policies.loadPreset("github"); + const hosts = policies.getPresetEndpoints(content); + assert.ok(hosts.includes("api.github.com")); + assert.ok(hosts.includes("github.com")); + assert.ok(hosts.includes("raw.githubusercontent.com")); + assert.ok(hosts.includes("uploads.github.com")); + }); + it("every preset has at least one endpoint", () => { for (const p of policies.listPresets()) { const content = policies.loadPreset(p.name);