Skip to content

Fix critical null-pointer and semantic union vulnerabilities in validation#2

Open
Iradukunda-Fils wants to merge 3 commits into
bufferpunk:mainfrom
Iradukunda-Fils:fix/null-safety-guards
Open

Fix critical null-pointer and semantic union vulnerabilities in validation#2
Iradukunda-Fils wants to merge 3 commits into
bufferpunk:mainfrom
Iradukunda-Fils:fix/null-safety-guards

Conversation

@Iradukunda-Fils

@Iradukunda-Fils Iradukunda-Fils commented Jul 17, 2026

Copy link
Copy Markdown

This pull request addresses several critical null-safety vulnerabilities, raw TypeError crashes, and semantic edge cases in the validation pipeline of @bufferpunk/modelcore. These patches ensure production-grade stability under degenerate inputs (such as missing field types, null-prototype objects, and invalid type matching in union schemas).

Changes Implemented

1. Robust Config/Schema Normalization (with Constructor Checks)

  • Normalized Config Safety: Resolved raw TypeError crashes on .prototype access inside normalizeConf. Added type check to reject { type: undefined }, { type: null }, and invalid string/primitive types like { type: "string" } by ensuring the type is always a valid constructor function (typeof conf.type === "function"), raising a structured SchemaDefinitionError.
  • Missing Schema Guard: Enforced that subclasses extending Base must define a static schema properties object, raising a structured SchemaDefinitionError instead of producing silently empty instances.

2. Defensive Object and Type Verification

  • Null-Prototype Safety: Placed guards before checking prototype properties like .constructor inside isOfType() and the error message formatter. This prevents crashes when validating objects derived from Object.create(null) or exotic deserialized primitives, raising a structured TypeValidationError with code INVALID_TYPE.
  • Strict Primitive Numeric Bounds: Cleaned up the overly-permissive !isNaN(value) type fallback in isOfType(). This prevents numeric string values like "42" from sneaking through Number validation checks without actual coercion when coerce: true is not active.

3. Union Schema Correctness (Skipping Structural Check for Scalar Values)

  • Union Class/String Iteration Fix: Fixed character-splitting bugs on Union(Array, String) fields. Structured branches (like Array or Set validation loops) now inspect the actual runtime value's type (e.g. Array.isArray(value)), preventing normal strings from being validation-mapped into character arrays (['h','e','l','l','o']).
  • Union Object/Map Type Preservations: Fixed recursive property crashes on Union(Object, String) (or Map unions) under nested keys schemas. Nested validations for structural keys now strictly verify the runtime value type before executing (isObject and isMap), ensuring primitive values mapped inside broad union keys bypass structural key validation cleanly.

Code & Usage Examples

###1. Invalid/Undefined Types in Schema
Before (Crashed with Raw TypeError on initialization):

class Product extends Base {
  static schema = { name: { type: undefined } }; 
}
new Product({ name: "Phone" }); // Throws TypeError: Cannot read properties of undefined (reading 'prototype')

After (Throws clear SchemaDefinitionError):

new Product({ name: "Phone" }); // Throws SchemaDefinitionError: Schema field 'name' type must be a constructor function

2. Null-Prototype Inputs (Object.create(null))

Before (Crashed with Raw TypeError on constructor lookup):

class User extends Base {
  static schema = { info: String };
}
new User({ info: Object.create(null) }); // Throws TypeError: Cannot read properties of undefined (reading 'name')

After (Throws TypeValidationError):

new User({ info: Object.create(null) }); // Throws TypeValidationError: Invalid type at 'info', expected String, got null-prototype object

3. Union Array vs. String Iteration

Before (Silently corrupted strings into character arrays):

class Info extends Base {
  static schema = { val: { type: Union(Array, String), values: String } };
}
const info = new Info({ val: "hello" });
console.log(info.val); // Output: ['h', 'e', 'l', 'l', 'o']

After (Preserves string structures cleanly):

const info = new Info({ val: "hello" });
console.log(info.val); // Output: "hello"

4. Union Nested Object Key Checks

Before (Crashed trying to check internal properties on scalar matches):

class Box extends Base {
  static schema = {
    val: {
      type: Union(Object, String),
      keys: { prop: { type: String, required: true } }
    }
  };
}
new Box({ val: "hello" }); // Throws RequiredError: Missing required property at 'val.prop'

After (Correctly bypasses Object checks for string primitives):

const box = new Box({ val: "hello" }); 
console.log(box.val); // Output: "hello"

Verification & Regression Testing

  • Added 9 regression tests covering all the above scenarios.
  • Ran test suite:
    npm test
    Outcome: 62/62 tests passing cleanly (0 regressions).
  • Compiled base.ts successfully to base.js and base.d.ts via TypeScript compiler (tsc).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens the Base validation pipeline in @bufferpunk/modelcore to avoid raw runtime crashes and incorrect behavior under degenerate inputs (invalid schema type, null-prototype values, and unions mixing structural/scalar types), while adding regression coverage and documenting the guarantees.

Changes:

  • Strengthened schema normalization and schema-presence checks to throw structured SchemaDefinitionErrors.
  • Refined union “structural” validation branching (Array/Set/Object/Map) to depend on the runtime value type, preventing incorrect traversal (e.g. string char-splitting).
  • Added regression tests and updated README/CHANGELOG; bumped package version.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
base.ts Adds schema/type guards, refines union structural branching, improves invalid-type error reporting.
base.js Mirrors the base.ts runtime changes in the built output.
test/base.test.js Adds regression tests for invalid schema types, null-prototype inputs, unions, and numeric coercion.
README.md Documents new defensive guarantees and clarifies error triggers.
CHANGELOG.md Adds a 1.6.2 entry describing the fixes and tests.
package.json Bumps package version to 1.6.2.
package-lock.json Updates lockfile metadata (currently mismatched vs package.json).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread base.ts
Comment on lines +606 to +608
// Guard against null-prototype objects (e.g. Object.create(null)) which have no .constructor
if (value != null && !value.constructor)
throw buildError(TypeValidationError, `Invalid type at '${path}', expected ${conf.type.name}, got null-prototype object`, this.constructor.name, path, conf.type, value, "INVALID_TYPE");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with copilot on this one. That also means checking of the prototype is unnecessary. The object will be invalid as long as it doesn't have the necessary properties that your schema expects.

Comment thread CHANGELOG.md

### Added

- **8 regression tests**: Covering `{ type: undefined }`, `{ type: null }`, null-prototype objects, `Union(Array, String)` with string/array inputs, `Number` strict type rejection, `Number` coercion interop, and missing schema detection.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants