Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
27 changes: 22 additions & 5 deletions src/lib/policy/sandbox-policy-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,21 +21,37 @@ let cachedSandboxPolicyValidator: ValidateFunction<unknown> | null = null;
function loadSandboxPolicyValidator(): ValidateFunction<unknown> {
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"),
);
Comment on lines +30 to +32

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.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Keep schema loading behind the adapter boundary.

The newly added fs.readFileSync in src/lib/policy/** couples policy validation directly to the host filesystem. Route both schema reads through an adapter or injected loader so this policy code remains testable with fakes.

As per path instructions, host-boundary work (filesystem/process/network/Docker/OpenShell) must be isolated in src/lib/adapters/** so policy code stays testable with injected fakes.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 30-30: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(NETWORK_POLICY_SCHEMA_PATH, "utf-8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/policy/sandbox-policy-validation.ts` around lines 30 - 32, Move the
NETWORK_POLICY_SCHEMA_PATH filesystem read out of the policy validation code and
into an adapter under src/lib/adapters/**, then inject or call that loader from
the policy validation flow for both schema reads. Update the relevant validation
symbols around parsedNetworkPolicySchema to use the adapter boundary, preserving
existing parsing and validation behavior while allowing tests to provide fakes.

Source: Path instructions

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.",
);
}

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 {
Expand Down
3 changes: 3 additions & 0 deletions test/package-contract/openshell-policy-boundary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Loading