Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
# eval-safe
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides `serializeEvalSafe` and `deserializeEvalSafe` as an explicit opt-in adapter for scenarios where the standard tagged JSON or MessagePack codecs cannot be used — for example, when the payload must include JavaScript `Function` values, `RegExp` literals with complex syntax, or other types that cannot be expressed in a JSON-safe intermediate. The adapter wraps the `serialize-javascript` library, which is purpose-built for this use case (e.g. server-side Redux initial state injection into HTML).
This module lives under the `./unsafe` subpath export and must **never** appear in the main `@suaveplan/serialize` bundle. Importing it is an explicit acknowledgment of the security trade-off.
## Features
- `serializeEvalSafe` — produces a JavaScript expression string via `serialize-javascript` that, when eval'd, reconstructs the original value including Functions and RegExps
- `deserializeEvalSafe` — evaluates the produced string via `(0, eval)(...)` (indirect eval) to reconstruct the value
- Security warning emitted on every call unless `{ ignoreSecurityWarning: true }` is passed
- `EvalSafeOptions` interface for suppressing warnings in controlled environments
## Usage: Basic Example
```typescript
import { serializeEvalSafe, deserializeEvalSafe } from "@suaveplan/serialize/unsafe";
// Suppress warnings in a trusted server-side context
const str = serializeEvalSafe({ fn: () => 42 }, { ignoreSecurityWarning: true });
const restored = deserializeEvalSafe(str, { ignoreSecurityWarning: true });
// (restored as any).fn() === 42
```
## Usage: Advanced Example
```typescript
import { serializeEvalSafe } from "@suaveplan/serialize/unsafe";
// Inject Redux initial state into an HTML page server-side.
// This is the canonical use case for serialize-javascript.
const html = `
<script>
window.__INITIAL_STATE__ = ${serializeEvalSafe(reduxState, { ignoreSecurityWarning: true })};
</script>
`;
```
## API Reference
### `serializeEvalSafe(value: unknown, opts?: EvalSafeOptions): string`
Serialize `value` using `serialize-javascript`. The output is a JavaScript expression string, not JSON.
**Parameters:**
- `value` — any JavaScript value, including Functions and RegExps
- `opts.ignoreSecurityWarning` — pass `true` to suppress the `console.warn`
**Returns:** a JavaScript expression string.
### `deserializeEvalSafe(json: string, opts?: EvalSafeOptions): unknown`
Evaluate `json` as a JavaScript expression and return the result.
**Parameters:**
- `json` — a string produced by `serializeEvalSafe`; MUST be from a trusted source
- `opts.ignoreSecurityWarning` — pass `true` to suppress the `console.warn`
**Returns:** the reconstructed value.
**Security:** Uses indirect `eval` (`(0, eval)(...)`), which runs in the global scope rather than the local closure scope, slightly reducing the attack surface compared to direct `eval`.
### `EvalSafeOptions`
```typescript
interface EvalSafeOptions {
ignoreSecurityWarning?: boolean;
}
```
## Implementation Notes
- The `(0, eval)(...)` pattern (indirect eval) is used intentionally: it evaluates in the global scope rather than capturing local variables, which slightly reduces the blast radius if a malicious string is accidentally evaluated.
- `serialize-javascript` is listed as a regular dependency (not a dev dependency) because it is shipped as part of the `/unsafe` subpath. The main bundle's Rollup config marks it as `external`, so it is never bundled into `dist/index.js`.
- The biome `lint/security/noEval` rule is suppressed with a comment at the call site to document that the eval usage is intentional, reviewed, and isolated to this specific adapter.