Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is **secure-json-parse**, a drop-in replacement for `JSON.parse()` that protects against prototype poisoning attacks. The library detects and handles dangerous `__proto__` and `constructor` properties in JSON that could lead to prototype pollution vulnerabilities.

## Architecture

- **Single-file module**: `index.js` contains the core functionality
- **Three main functions**:
- `parse()`: Main JSON parsing with security checks (supports reviver and options)
- `scan()` (exported as `filter`): Scans existing objects for dangerous properties
- `safeParse()`: Returns undefined instead of throwing errors
- **Security approach**: Uses regex pre-scanning for performance, then deep object traversal for thorough cleanup
- **Enhanced security**: Now specifically targets `constructor.prototype` patterns rather than any `constructor` property
- **Action modes**: `error` (default), `remove`, or `ignore` for both `protoAction` and `constructorAction`
- **TypeScript support**: Includes TypeScript definitions in `types/` directory

## Commands

### Testing
```bash
npm test # Run linting, unit tests, and TypeScript tests
npm run test:unit # Run unit tests only (tape)
npm run test:typescript # Run TypeScript definition tests (tsd)
npm run test:browser # Run tests in browsers using airtap
```

### Linting
```bash
npm run lint # Run ESLint with neostandard config
npm run lint:fix # Auto-fix linting issues where possible
```

### Benchmarking
```bash
npm run benchmark # Run performance benchmarks against standard JSON.parse
```

### Code Standards
- Uses **neostandard** with **ESLint 9** for linting
- **Tape** for testing framework
- **nyc** for test coverage
- **tsd** for TypeScript definition testing
- Test files located in `test/` directory (not root)

### Git Workflow
- Use `git commit -s` to add Developer Certificate of Origin signoff
- Create feature branches for changes
- All commits should include proper signoff for contribution tracking

## Testing Notes

- Tests cover all action combinations (`error`/`remove`/`ignore`)
- Unicode escape sequence handling is thoroughly tested
- Buffer and BOM (Byte Order Mark) support included
- Tests verify behavior with overwritten `hasOwnProperty`
- Constructor null handling is tested to prevent TypeError
- Enhanced security tests for `constructor.prototype` patterns specifically
- TypeScript definitions are validated with test files in `types/`
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ function filter (obj, { protoAction = 'error', constructorAction = 'error', safe

if (constructorAction !== 'ignore' &&
Object.prototype.hasOwnProperty.call(node, 'constructor') &&
node.constructor !== null &&
typeof node.constructor === 'object' &&
Object.prototype.hasOwnProperty.call(node.constructor, 'prototype')) { // Avoid calling node.hasOwnProperty directly
if (safe === true) {
return null
Expand Down
21 changes: 21 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,27 @@ test('parse', t => {
t.end()
})

t.test('handles constructor null safely', t => {
// Test that constructor: null doesn't trigger prototype pollution checks
t.deepEqual(
j.parse('{"constructor": null}', { constructorAction: 'remove' }),
{ constructor: null }
)

// Test that constructor: null doesn't throw error when using error action
t.deepEqual(
j.parse('{"constructor": null}', { constructorAction: 'error' }),
{ constructor: null }
)

// Test that constructor: null is preserved when using ignore action
t.deepEqual(
j.parse('{"constructor": null}', { constructorAction: 'ignore' }),
{ constructor: null }
)
t.end()
})

t.end()
})

Expand Down