Skip to content
This repository was archived by the owner on Mar 9, 2026. It is now read-only.
Merged
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
9 changes: 4 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ Before implementing, review:

safe-formdata is a **boundary-focused FormData parser**.

Its sole responsibility is to establish a strict, security-oriented boundary
between untrusted FormData input and application logic.
Its sole responsibility is maintaining a strict parsing boundary between untrusted FormData and application logic.

This document defines the non-negotiable rules for implementation and review.

Expand Down Expand Up @@ -97,7 +96,7 @@ This is non-optional and part of the boundary definition.

## Key validation criteria

To maintain a predictable boundary, keys must meet the following criteria. Failure to do so results in an `invalid_key` issue.
Keys failing any of the following result in an `invalid_key` issue:

- **Non-empty**: A key must have a length > 0.
- Note: Keys consisting only of whitespace characters are considered valid as they satisfy the length requirement and preserve the "opaque strings" principle.
Expand All @@ -122,7 +121,7 @@ No additional IssueCode may be introduced without a major version bump.
- `key` must be the original FormData key that caused the issue, reported as-is without interpretation.
- Issues are informational, not exceptions.

Note: In v1.0+, additional fields such as `message: string` and `meta?: Record<string, unknown>` may be considered for enhanced error reporting.
Note: In v1.0+, additional fields (e.g., `message`, `meta`) may be added for richer error reporting.

---

Expand Down Expand Up @@ -181,7 +180,7 @@ export type IssueCode = "invalid_key" | "forbidden_key" | "duplicate_key";

**Type documentation:**

All type definitions include comprehensive JSDoc comments for IDE integration. See:
All public types include JSDoc comments for IDE integration. See:

- `src/types/ParseResult.ts` - Discriminated union with type narrowing examples
- `src/types/ParseIssue.ts` - Issue structure and property explanations
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Review this code against boundary-validator rules

## Usage Examples

The `examples/` directory contains comprehensive usage examples:
The `examples/` directory contains usage examples:

- `00-basic.ts` - Basic parsing and type narrowing
- `01-file-upload.ts` - File handling with type guards
Expand Down
10 changes: 2 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

Thank you for your interest in contributing to safe-formdata!

This document provides guidelines for contributing to this boundary-focused FormData parser.

---

## Before You Start

**Please read these documents carefully.**

1. **README.md**: Understand the design principles and what safe-formdata is (and isn't)
2. **AGENTS.md**: Review the non-negotiable implementation rules
3. **Design decisions (Why not?)**: Understand why certain features are intentionally excluded
Expand Down Expand Up @@ -47,8 +43,6 @@ bun run test:watch # Watch mode for testing

## Project Philosophy

safe-formdata establishes a **strict boundary** between untrusted FormData input and application logic.

### Core Principles (from README.md)

- 🧱 **Keys are opaque**: Never interpret key naming conventions
Expand Down Expand Up @@ -135,7 +129,7 @@ Before submitting a PR:

### PR Description

Include the following.
Include the following:

1. **Problem**: What issue does this solve?
2. **Solution**: How does this maintain the boundary?
Expand All @@ -144,7 +138,7 @@ Include the following.

### Review Process

PRs will be evaluated against the following.
PRs are evaluated against:

1. **Alignment with design principles** (README.md)
2. **Compliance with technical rules** (AGENTS.md)
Expand Down
20 changes: 8 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

**The strict trust boundary for FormData.**

safe-formdata is a **security-focused** FormData parser.
It enforces rules on keys and forbids structural inference by design.

[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/roottool/safe-formdata)
[![npm version](https://img.shields.io/npm/v/safe-formdata)](https://www.npmjs.com/package/safe-formdata)
[![CI](https://github.com/roottool/safe-formdata/actions/workflows/ci.yml/badge.svg)](https://github.com/roottool/safe-formdata/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/roottool/safe-formdata/graph/badge.svg)](https://codecov.io/gh/roottool/safe-formdata)

safe-formdata is a **security-focused** parser that establishes a predictable boundary between untrusted input and application logic.
It enforces strict rules on keys and forbids structural inference by design.

## Table of Contents

- [safe-formdata](#safe-formdata)
Expand Down Expand Up @@ -76,10 +76,7 @@ Anything beyond this boundary — including value validation, schema enforcement
framework conventions, authentication, or denial-of-service protection —
is **out of scope** and must be handled by the application.

📘 **Authoritative security guarantees, assumptions, and reporting policy:**
See [SECURITY.md](./SECURITY.md)

Security decisions and issue triage are based on the definitions in SECURITY.md.
Security guarantees, assumptions, and reporting policy: [SECURITY.md](./SECURITY.md)

---

Expand Down Expand Up @@ -128,7 +125,7 @@ their interpretation necessarily implies structure

Defining or inferring such structure is outside the scope of safe-formdata.

safe-formdata establishes a strict, non-inferential boundary:
safe-formdata enforces a strict rule:
each key must map to exactly one value (`string` or `File`),
or the input is rejected.

Expand Down Expand Up @@ -156,7 +153,7 @@ Validation and typing belong beyond it.

## Installation

Install safe-formdata using your preferred package manager:
Install:

```bash
# npm
Expand Down Expand Up @@ -201,9 +198,8 @@ if (result.data !== null) {

- All values are `string | File` - no automatic type conversion
- Use `data !== null` to check for success and narrow the type
- Security boundaries are enforced from the start

For complete examples including file uploads and validation patterns, see the [examples/](./examples) directory.
See [examples/](./examples) for file upload handling and more.

---

Expand Down Expand Up @@ -254,7 +250,7 @@ No inference or convenience features will be added within v0.x.

## Contributing

Contributions are welcome! Please see:
See:

- [CONTRIBUTING.md](CONTRIBUTING.md) - Contributor guide
- [docs/PUBLISHING.md](docs/PUBLISHING.md) - Publishing guide (for maintainers)
Expand Down
17 changes: 6 additions & 11 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
# Security Policy

safe-formdata is a **boundary-focused FormData parser** designed with security as a core principle.

This document defines:

- How to report security issues responsibly
- What is considered **in scope** vs **out of scope**
- The security guarantees and assumptions of this library
- How security-related issues are evaluated and handled

This policy serves as the **authoritative reference** for security-related decisions,
including issue triage and closure.
This policy governs security issue triage and closure decisions.

---

## Supported Versions

**Only the latest release receives security updates.**
Only the latest release receives security updates.

Security fixes are not backported.
Users must upgrade to the latest version to receive security patches.
Expand All @@ -30,7 +27,7 @@ and **public security discussion**.

### Private (Security Advisory)

Use **GitHub Security Advisories** for the following.
Use **GitHub Security Advisories** for:

- Reproducible vulnerabilities
- Exploit techniques or payloads
Expand All @@ -42,7 +39,7 @@ Use **GitHub Security Advisories** for the following.

### Public Issue

Public issues are appropriate only for the following.
Public issues are appropriate for:

- **Design-level security questions**
- **Non-sensitive security concerns**
Expand Down Expand Up @@ -102,9 +99,7 @@ The following are **not** considered security vulnerabilities:
- **API misuse**
- Incorrect usage by consumers (e.g., ignoring `issues`)

If an issue falls under these categories,
it will be **closed without action**, even if it has security implications
at the application level.
If an issue falls under these categories, it will be **closed without action**, even if it has security implications at the application level.

---

Expand Down Expand Up @@ -149,7 +144,7 @@ safe-formdata **assumes**:

## Disclosure Policy

Security reports are handled as follows.
Security reports are handled as follows:

1. **Review**
- Issues are evaluated against the security scope defined above
Expand Down
2 changes: 1 addition & 1 deletion skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This directory contains **Agent Skills** specific to the safe-formdata project.

## What are Agent Skills?

Agent Skills are structured knowledge packages that agents like Claude Code can efficiently reference. Based on the implementation rules in AGENTS.md, they provide the following.
Agent Skills are structured knowledge packages for agents like Claude Code. Based on AGENTS.md, they provide:

- **Automatic triggering**: Activates automatically during PR creation and code review
- **Progressive disclosure**: Loads only necessary information incrementally
Expand Down
13 changes: 1 addition & 12 deletions src/issues/createIssue.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
import type { IssueCode } from "#types/IssueCode";
import type { ParseIssue } from "#types/ParseIssue";

/**
* Creates a ParseIssue with the specified code and key.
*
* This is an internal utility function for generating structured issue reports
* during FormData parsing.
*
* @param code - The type of issue (invalid_key, forbidden_key, or duplicate_key)
* @param key - The FormData key that caused the issue
* @returns A ParseIssue object ready to be added to the issues array
*
* @internal
*/
/** @internal */
export function createIssue(code: IssueCode, key: string): ParseIssue {
return { code, key };
}
4 changes: 0 additions & 4 deletions src/issues/forbiddenKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@
* These keys are reserved properties on `Object.prototype` and must never
* be allowed in parsed FormData, regardless of their values or context.
*
* The forbidden keys are the following.
* - `__proto__`: Legacy prototype accessor
* - `prototype`: Function prototype property
* - `constructor`: Object constructor reference
*
* Any FormData entry containing these keys will trigger a `forbidden_key` issue,
* causing the parse operation to fail with `data: null`.
*
* @see {@link https://github.com/roottool/safe-formdata/blob/main/AGENTS.md#prototype-safety AGENTS.md > Security rules > Prototype safety}
*/
export const FORBIDDEN_KEYS: ReadonlySet<string> = new Set([
Expand Down
12 changes: 5 additions & 7 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@ import type { ParseIssue } from "#types/ParseIssue";
import type { ParseResult } from "#types/ParseResult";

/**
* Parses FormData into a flat JavaScript object with strict boundary enforcement.
* Parses FormData into a flat JavaScript object.
*
* This function establishes a security-focused boundary between untrusted FormData input
* and application logic by:
* - Detecting duplicate, forbidden, and invalid keys
* - Treating keys as opaque strings (no structural inference)
* - Returning null data if any issues are detected (no partial success)
* Fails completely if any entry violates the rules below — no partial success:
* - Duplicate, forbidden, and invalid keys are rejected
* - Keys are treated as opaque strings (no structural inference)
*
* @param formData - The FormData instance to parse
* @returns ParseResult containing either parsed data or issues (never both)
* @returns ParseResult containing either parsed data or issues, never both
*
* @example
* ```ts
Expand Down
2 changes: 0 additions & 2 deletions src/types/IssueCode.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
/**
* Type of validation issue detected during FormData parsing.
*
* All issue codes represent security boundaries enforced by safe-formdata:
*
* - **`invalid_key`**: Key is empty or not a string.
* Prevents ambiguous or unrepresentable keys from entering application logic.
*
Expand Down
6 changes: 2 additions & 4 deletions src/types/ParseResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ParseIssue } from "#types/ParseIssue";
/**
* Result of parsing FormData with {@link parse}.
*
* This is a discriminated union type. Use `data !== null` to narrow the type:
* Use `data !== null` to narrow the type:
* - `data !== null` → Success: parsed data available, issues is empty array
* - `data === null` → Failure: validation issues occurred
*
Expand Down Expand Up @@ -51,7 +51,7 @@ export type ParseResult =
data: Record<string, string | File>;

/**
* Empty array when parsing succeeds.
* Always `[]` on success — kept for consistent destructuring alongside the failure branch.
*/
issues: [];
}
Expand All @@ -66,8 +66,6 @@ export type ParseResult =
/**
* Non-empty array of validation issues that prevented successful parsing.
*
* Always contains at least one issue when `data` is `null`.
*
* Possible issue codes:
* - `invalid_key`: Key is empty or not a string
* - `forbidden_key`: Key is a forbidden prototype property
Expand Down