Spaces:
Running
Running
File size: 6,793 Bytes
518343a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | /**
* input_perturbation.test.ts
* Doctrine v6 R3 — Vertical Governance Receipts
* 10 Input Perturbation adversarial tests
*
* Tests the policy loader and validator against adversarial input manipulations:
* whitespace injection, Unicode normalization attacks, null byte injection,
* numeric overflow, deeply nested structures, and YAML/JSON escape attacks.
*
* All perturbations must be detected and result in validation failure or
* safe error handling (never silent acceptance of corrupted state).
*
* Test framework: Jest / ts-jest
* Run: npx jest tests/adversarial/input_perturbation.test.ts
*/
import Ajv from "ajv";
import addFormats from "ajv-formats";
import * as fs from "fs";
import * as path from "path";
const SCHEMA_PATH = path.resolve(__dirname, "../../a11oy-knowledge.schema.json");
function buildValidator() {
const ajv = new Ajv({ allErrors: true, strict: false, coerceTypes: false });
addFormats(ajv);
const schema = JSON.parse(fs.readFileSync(SCHEMA_PATH, "utf8"));
return ajv.compile(schema);
}
function validBase(): any {
return {
schema_version: "1.0.0",
vertical: "healthcare",
regime: "HIPAA/HITECH",
effective_date: "2025-07-01",
jurisdiction: "US-Federal",
meta: {
title: "Input perturbation base policy",
description: "Base policy document for input perturbation adversarial tests.",
authority: "45 CFR Parts 160, 162, 164",
receipt_chain_required: true,
merkle_root_algorithm: "SHA3-256",
},
regulatory_clauses: Array.from({ length: 8 }, (_, i) => ({
clause_id: `HIPAA-PERT-${String(i + 1).padStart(3, "0")}`,
title: `Perturbation test clause ${i + 1}`,
citation: `45 CFR § 164.${i + 100}`,
full_ref: `45 C.F.R. § 164.${i + 100} — perturbation test clause with full reference detail`,
lambda_axes: [
{ axis: "Λ3", label: "Privacy", weight: 0.9, enforcement: "mandatory",
rationale: "PHI protection requires receipt-logged minimum-necessary access gating." },
],
})),
compliance_thresholds: {
minimum_lambda_coverage: 6,
mandatory_axes: ["Λ3", "Λ6", "Λ7"],
receipt_retention_days: 2555,
},
receipt_chain: {
algorithm: "SHA3-256",
chaining: "merkle_dag",
quorum: "2-of-3",
nodes: ["primary", "backup", "audit-only"],
},
};
}
describe("Adversarial — Input Perturbation (10 tests)", () => {
const validate = buildValidator();
// IP-001: Null byte injection in string field
test("IP-001: null byte in vertical field is rejected or normalised safely", () => {
const p = validBase();
p.vertical = "health\x00care";
// Must not match enum "healthcare" — null byte makes it a different string
expect(validate(p)).toBe(false);
});
// IP-002: Leading/trailing whitespace in vertical field (enum mismatch)
test("IP-002: whitespace-padded vertical ' healthcare ' is rejected by enum", () => {
const p = validBase();
p.vertical = " healthcare ";
expect(validate(p)).toBe(false);
});
// IP-003: Unicode NFC vs NFD normalization in title (schema accepts both, but
// content should be normalised before hashing in production)
test("IP-003: NFD-composed character in title is accepted by schema (content-hash alert in loader)", () => {
const p = validBase();
// 'é' in NFD: e + combining accent (U+0065 + U+0301) vs NFC: U+00E9
p.meta.title = "Healthcare AI Governance Policy \u0065\u0301 HIPAA Alignment";
// Schema accepts any string — passes schema validation
expect(validate(p)).toBe(true);
// Note: policy_loader.ts must NFC-normalise before hashing to avoid split receipts
});
// IP-004: Integer overflow — receipt_retention_days as MAX_SAFE_INTEGER
test("IP-004: receipt_retention_days at Number.MAX_SAFE_INTEGER is accepted by schema", () => {
const p = validBase();
p.compliance_thresholds.receipt_retention_days = Number.MAX_SAFE_INTEGER;
// Schema has minimum:365 but no explicit maximum — this is a loader concern
expect(validate(p)).toBe(true);
});
// IP-005: weight as -0 (negative zero) should be treated as 0.0 (≥ minimum)
test("IP-005: weight = -0 (negative zero) is accepted as 0.0", () => {
const p = validBase();
p.regulatory_clauses[0].lambda_axes[0].weight = -0;
// -0 === 0.0 in IEEE 754, minimum: 0.0 — schema should accept
expect(validate(p)).toBe(true);
});
// IP-006: weight = NaN (not a valid JSON number) — must be rejected
test("IP-006: weight = NaN is rejected by schema (not a valid JSON number)", () => {
const p = validBase();
p.regulatory_clauses[0].lambda_axes[0].weight = NaN;
// AJV with coerceTypes:false rejects NaN for type:number
expect(validate(p)).toBe(false);
});
// IP-007: weight = Infinity — must be rejected
test("IP-007: weight = Infinity is rejected (> maximum 1.0)", () => {
const p = validBase();
p.regulatory_clauses[0].lambda_axes[0].weight = Infinity;
expect(validate(p)).toBe(false);
});
// IP-008: effective_date = "2025-02-29" (invalid date — 2025 is not a leap year)
test("IP-008: effective_date '2025-02-29' (invalid calendar date) is rejected", () => {
const p = validBase();
p.effective_date = "2025-02-29";
// AJV with ajv-formats validates "date" format — 2025-02-29 does not exist
// The schema uses pattern not format for date — pattern only checks YYYY-MM-DD format
// This tests the regex pattern: matches format ✓ but semantically invalid
// Pattern "^\d{4}-\d{2}-\d{2}$" will accept it; document the gap
const result = validate(p);
// Pattern alone does NOT reject this — flag for loader enforcement
// This test documents the known schema limitation
expect(typeof result).toBe("boolean");
});
// IP-009: nodes array contains duplicate node names (uniqueItems not enforced by schema for nodes)
test("IP-009: receipt_chain nodes with duplicate names passes schema (loader must deduplicate)", () => {
const p = validBase();
p.receipt_chain.nodes = ["primary", "primary", "backup"];
const result = validate(p);
// Schema does not set uniqueItems on nodes — this documents the gap
expect(typeof result).toBe("boolean");
});
// IP-010: Deeply nested extra object in meta (additionalProperties: true for meta)
test("IP-010: deeply nested extra field in meta is accepted (meta allows additionalProperties)", () => {
const p = validBase();
(p.meta as any).extra = { a: { b: { c: { d: { e: { injected: "payload" } } } } } };
// meta has additionalProperties: true — this passes schema
// But loader must sanitise extra fields before committing to receipt chain
expect(validate(p)).toBe(true);
});
});
|