diff --git a/package.json b/package.json index 371aa600cf..fa4c37d2ca 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "nemoclaw/openclaw.plugin.json", "nemoclaw/package.json", "nemoclaw-blueprint/", + "schemas/network-policy.schema.json", "schemas/sandbox-policy.schema.json", "scripts/", "docs/resources/local-credential-form.html", diff --git a/src/lib/policy/sandbox-policy-validation.ts b/src/lib/policy/sandbox-policy-validation.ts index 22301d341f..b1a05a93eb 100644 --- a/src/lib/policy/sandbox-policy-validation.ts +++ b/src/lib/policy/sandbox-policy-validation.ts @@ -11,6 +11,7 @@ import { parseOpenShellPolicy } from "./merge"; const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", ".."); const SANDBOX_POLICY_SCHEMA_PATH = path.join(PACKAGE_ROOT, "schemas", "sandbox-policy.schema.json"); +const NETWORK_POLICY_SCHEMA_PATH = path.join(PACKAGE_ROOT, "schemas", "network-policy.schema.json"); const MAX_SCHEMA_ERRORS = 3; const MAX_SCHEMA_ERROR_MESSAGE_CHARS = 120; const MAX_SCHEMA_ERROR_SUMMARY_CHARS = 500; @@ -20,13 +21,27 @@ let cachedSandboxPolicyValidator: ValidateFunction | null = null; function loadSandboxPolicyValidator(): ValidateFunction { if (cachedSandboxPolicyValidator) return cachedSandboxPolicyValidator; - let schema: AnySchemaObject; + let sandboxPolicySchema: AnySchemaObject; + let networkPolicySchema: AnySchemaObject; try { - const parsed: unknown = JSON.parse(fs.readFileSync(SANDBOX_POLICY_SCHEMA_PATH, "utf-8")); - if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + const parsedSandboxPolicySchema: unknown = JSON.parse( + fs.readFileSync(SANDBOX_POLICY_SCHEMA_PATH, "utf-8"), + ); + const parsedNetworkPolicySchema: unknown = JSON.parse( + fs.readFileSync(NETWORK_POLICY_SCHEMA_PATH, "utf-8"), + ); + if ( + !parsedSandboxPolicySchema || + typeof parsedSandboxPolicySchema !== "object" || + Array.isArray(parsedSandboxPolicySchema) || + !parsedNetworkPolicySchema || + typeof parsedNetworkPolicySchema !== "object" || + Array.isArray(parsedNetworkPolicySchema) + ) { throw new Error("schema root is not an object"); } - schema = parsed as AnySchemaObject; + sandboxPolicySchema = parsedSandboxPolicySchema as AnySchemaObject; + networkPolicySchema = parsedNetworkPolicySchema as AnySchemaObject; } catch { throw new Error( "Sandbox policy validation schema is unavailable from this NemoClaw installation.", @@ -34,7 +49,9 @@ function loadSandboxPolicyValidator(): ValidateFunction { } try { - const compiled = new Ajv({ allErrors: true, strict: false, $data: true }).compile(schema); + const ajv = new Ajv({ allErrors: true, strict: false, $data: true }); + ajv.addSchema(networkPolicySchema); + const compiled = ajv.compile(sandboxPolicySchema); cachedSandboxPolicyValidator = compiled; return compiled; } catch { diff --git a/test/package-contract/openshell-policy-boundary.test.ts b/test/package-contract/openshell-policy-boundary.test.ts index 639f7508a7..797088dbd3 100644 --- a/test/package-contract/openshell-policy-boundary.test.ts +++ b/test/package-contract/openshell-policy-boundary.test.ts @@ -214,6 +214,9 @@ describe("OpenShell policy boundary package contract", () => { expect(fs.existsSync(path.join(installedRoot, "schemas", "sandbox-policy.schema.json"))).toBe( true, ); + expect(fs.existsSync(path.join(installedRoot, "schemas", "network-policy.schema.json"))).toBe( + true, + ); expect( fs.existsSync( path.join(installedRoot, "dist", "lib", "policy", "sandbox-policy-validation.js"),