diff --git a/nemoclaw-blueprint/policies/presets/npm.yaml b/nemoclaw-blueprint/policies/presets/npm.yaml index a2ac3dae293..afdb15289b5 100644 --- a/nemoclaw-blueprint/policies/presets/npm.yaml +++ b/nemoclaw-blueprint/policies/presets/npm.yaml @@ -11,10 +11,18 @@ network_policies: endpoints: - host: registry.npmjs.org port: 443 - access: full + protocol: rest + enforcement: enforce + tls: terminate + rules: + - allow: { method: GET, path: "/**" } - host: registry.yarnpkg.com port: 443 - access: full + protocol: rest + enforcement: enforce + tls: terminate + rules: + - allow: { method: GET, path: "/**" } binaries: - { path: /usr/local/bin/npm* } - { path: /usr/local/bin/npx* } diff --git a/nemoclaw-blueprint/policies/presets/pypi.yaml b/nemoclaw-blueprint/policies/presets/pypi.yaml index 9b8cebd5ac3..0bb65d6b523 100644 --- a/nemoclaw-blueprint/policies/presets/pypi.yaml +++ b/nemoclaw-blueprint/policies/presets/pypi.yaml @@ -11,10 +11,20 @@ network_policies: endpoints: - host: pypi.org port: 443 - access: full + protocol: rest + enforcement: enforce + tls: terminate + rules: + - allow: { method: GET, path: "/**" } + - allow: { method: HEAD, path: "/**" } - host: files.pythonhosted.org port: 443 - access: full + protocol: rest + enforcement: enforce + tls: terminate + rules: + - allow: { method: GET, path: "/**" } + - allow: { method: HEAD, path: "/**" } binaries: - { path: /usr/bin/python3* } - { path: /usr/bin/pip* } diff --git a/test/policies.test.js b/test/policies.test.js index d00ea8cbd59..667f7dd8a57 100644 --- a/test/policies.test.js +++ b/test/policies.test.js @@ -563,19 +563,31 @@ describe("policies", () => { } }); - it("package-manager presets use access: full (not tls: terminate)", () => { - // Package managers (pip, npm, yarn) use CONNECT tunneling which breaks - // under tls: terminate. Ensure these presets use access: full like the - // github policy in openclaw-sandbox.yaml. + it("package-manager presets use protocol: rest with read-only rules", () => { + // Package managers only need read access to install packages. + // Using access: full opens a raw CONNECT tunnel that allows + // PUT/POST (publish, exfiltrate). Restrict via rest rules. const packagePresets = ["pypi", "npm"]; for (const name of packagePresets) { const content = policies.loadPreset(name); expect(content).toBeTruthy(); - expect(content.includes("tls: terminate")).toBe(false); - expect(content.includes("access: full")).toBe(true); + expect(content.includes("access: full")).toBe(false); + expect(content.includes("protocol: rest")).toBe(true); + expect(content.includes("method: GET")).toBe(true); + // No write methods allowed + expect(content.includes("method: PUT")).toBe(false); + expect(content.includes("method: POST")).toBe(false); + expect(content.includes("method: DELETE")).toBe(false); } }); + it("pypi preset allows HEAD for pip lazy-wheel metadata checks", () => { + // pip and uv use HEAD requests for lazy wheel downloads and + // range-request support. GET-only would break pip install. + const content = policies.loadPreset("pypi"); + expect(content.includes("method: HEAD")).toBe(true); + }); + it("package-manager presets include binaries section", () => { // Without binaries, the proxy can't match pip/npm traffic to the policy // and returns 403.