#!/usr/bin/env bash set -euo pipefail # CASAN H2 tool-input validation. # Validates a tool-call input JSON against a JSON-Schema-style spec # (required fields + property types + additionalProperties:false). # Stdlib-only — supports type, required, properties, additionalProperties, # enum. NOT a full JSON Schema engine (no $ref, no nested object recursion # beyond one level); scoped deliberately and labeled as such. # # Usage: validate-tool-input.sh # Exit: 0 valid, 2 invalid, 64 usage error. SCHEMA="${1:-}" INPUT="${2:-}" if [[ -z "$SCHEMA" || -z "$INPUT" || ! -f "$SCHEMA" || ! -f "$INPUT" ]]; then echo "Usage: validate-tool-input.sh " >&2 exit 64 fi python - "$SCHEMA" "$INPUT" <<'PY' import json, sys schema = json.load(open(sys.argv[1], encoding="utf-8")) data = json.load(open(sys.argv[2], encoding="utf-8")) TYPES = { "string": str, "integer": int, "number": (int, float), "boolean": bool, "object": dict, "array": list, } errors = [] # SEC-15: validate RECURSIVELY. Previously only the top level was checked, so a # nested object could smuggle wrong types / unexpected fields past the gate. def validate(schema, data, path): expected = schema.get("type") py = TYPES.get(expected) if py and (not isinstance(data, py) or (expected in ("integer", "number") and isinstance(data, bool))): errors.append(f"{path or 'root'}: expected {expected}") return if "enum" in schema and data not in schema["enum"]: errors.append(f"{path or 'root'}: not in enum {schema['enum']}") if expected == "object" and isinstance(data, dict): props = schema.get("properties", {}) for field in schema.get("required", []): if field not in data: errors.append(f"{path or 'root'}: missing required field: {field}") if schema.get("additionalProperties") is False: for key in data: if key not in props: errors.append(f"{path or 'root'}: unexpected field: {key}") for key, spec in props.items(): if key in data: validate(spec, data[key], f"{path}.{key}" if path else key) elif expected == "array" and isinstance(data, list): item_spec = schema.get("items") if isinstance(item_spec, dict): for i, item in enumerate(data): validate(item_spec, item, f"{path}[{i}]") validate(schema, data, "") if errors: sys.stderr.write("TOOL_INPUT_INVALID " + "; ".join(errors) + "\n") raise SystemExit(2) print("TOOL_INPUT_VALID") PY