Skip to content
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
1 change: 1 addition & 0 deletions .agents/skills/e2e-tests/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Cassettes mock provider HTTP responses (OpenAI, Anthropic, ...) so scenarios tha
Then run again in `BRAINTRUST_E2E_CASSETTE_MODE=replay` with no provider keys to confirm the cassette is sufficient.

- Volatile fields in request bodies (e.g. AI-SDK `experimental_generateMessageId`) need a per-scenario filter. Add the scenario name and a `FilterSpec` to `e2e/helpers/cassette-filters.mjs`. The cassette layer is backed by `@braintrust/seinfeld` (`dev-packages/seinfeld`); the preload entry point is `e2e/helpers/cassette-preload.mjs`.
- **After recording, always inspect the cassette for leaked API keys before committing.** The recorder redacts common patterns (`paranoid` preset), but confirm that no real keys appear in request headers, response bodies, or any URL query parameters. If a key slips through, remove the cassette, add a custom `redact` rule, and re-record.

## Preferred Patterns

Expand Down
21 changes: 0 additions & 21 deletions dev-packages/seinfeld/LICENSE

This file was deleted.

12 changes: 2 additions & 10 deletions dev-packages/seinfeld/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,16 @@ Generic VCR/cassette library for Node.js, built on [MSW](https://mswjs.io). Reco
## Features

- **Normalizers** (always-on, lossy) transform requests before matching. They strip volatile fields like `Authorization` headers, dynamic IDs (`experimental_generateMessageId`), or query nonces so two structurally-identical requests still match across runs. Their output is internal — never serialized.
- **Redactors** (opt-in) transform what gets persisted to disk. They mask credentials before the cassette hits version control. Disabled by default; cassettes contain the real on-the-wire bytes unless you opt in.
- **Redactors** transform what gets persisted to disk. They mask credentials before the cassette hits version control. The `'paranoid'` preset is applied by default; pass `redact: []` to disable.

## Security note

> **Cassettes contain real request and response bytes by default, including `Authorization` headers.** This is the safer default for fidelity (downstream consumers see real responses) but it means you must either (a) enable redaction, (b) write a custom `RedactionConfig`, or (c) add cassette files to `.gitignore` if they may contain credentials.
Three body-redaction gaps are worth knowing:
Three body-redaction gaps are worth knowing even with the default `'paranoid'` preset:

1. **Non-canonical content-type** — some servers return JSON with `Content-Type: text/plain`. `redactBodyFields` covers this because seinfeld attempts to parse `text` bodies as JSON before masking.
2. **SSE event data** — streaming endpoints (OpenAI, Anthropic) emit JSON in `data:` lines. `redactBodyFields` applies to parseable `data:` lines; `redactBodyText` handles non-JSON SSE content.
3. **Plain-text credentials** — form-encoded bodies, XML, or log-like text are opaque to field-path rules. Use `redactBodyText` with a regex.

For cassettes committed to version control, use the `'paranoid'` preset, which covers all three paths:

```ts
createCassette({ name: "demo", redact: "paranoid" });
```

`'paranoid'` redacts credential headers, common credential field names at any JSON depth (`apiKey`, `token`, `secret`, `password`, `authorization`), and Bearer / `sk-` style tokens in text bodies.

To detect misconfigurations at record time, add `strict: true`:
Expand Down
300 changes: 0 additions & 300 deletions dev-packages/seinfeld/scripts/migrate-from-legacy.mjs

This file was deleted.

7 changes: 5 additions & 2 deletions dev-packages/seinfeld/src/format/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { z } from "zod";
/**
* Zod schema for cassette format version 1.
*
* Validates cassettes at load time so corrupt files fail loudly rather than
* mysteriously silent at match time.
* Cassette files carry a `version` field so that: (a) load-time validation
* fails loudly on corrupt files rather than silently at match time, and
* (b) a future breaking change can introduce v2 while the library still reads
* older cassettes without ambiguity. See `format/index.ts` for the dispatch
* logic and the rule for when to bump the version.
*/

export const CURRENT_FORMAT_VERSION = 1 as const;
Expand Down
4 changes: 2 additions & 2 deletions dev-packages/seinfeld/src/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ export interface CassetteOptions {
store?: CassetteStore;
/** Filter spec (matching-only normalization). */
filters?: FilterSpec;
/** Redaction spec (applied to persisted bytes). Defaults to none. */
/** Redaction spec (applied to persisted bytes). Defaults to `'paranoid'`. Pass `[]` to disable. */
redact?: RedactionSpec;
/** Hosts to intercept. Other hosts pass through. Defaults to all hosts. */
hosts?: Array<string | RegExp>;
Expand Down Expand Up @@ -403,7 +403,7 @@ export function createCassette(options: CassetteOptions): Cassette {
options.store ?? createJsonFileStore({ rootDir: DEFAULT_CASSETTE_DIR }),
matcher: options.matcher ?? createDefaultMatcher(),
filters: options.filters,
redact: options.redact,
redact: options.redact ?? "paranoid",
hosts: options.hosts,
passthroughHosts: options.passthroughHosts,
threshold: resolveThreshold(options.externalBlobThreshold),
Expand Down
Loading
Loading