atomicpages/sensored

A free streaming-first open-source redaction toolit

TypeScript

1

10 commits

updated Sep 29, 2026

See the code

See what people are saying

README

sensored

sensored

A streaming-first PII redaction library for TypeScript. Detects and redacts sensitive data with 129 regex detectors, optional NER, and AI-powered semantic confirmation.

Install

bun add sensored

For higher-recall person name detection via NER:

bun add compromise

For AI-powered semantic confirmation that eliminates false positives:

bun add @typesafe-ai/sdk

The default person_name_lite detector uses a lightweight regex + bloom filter with zero runtime dependencies. Use person_name to opt into compromise.js NER, or add @typesafe-ai/sdk to let Jev confirm detections before redacting.

Quick start

import { createRedactor } from "sensored";

const redactor = createRedactor({ presets: ["pii"], rules: {} });

const text = "Contact me at john@example.com or call 555-123-4567.";
const redacted = redactor.redact(text);
// "Contact me at [EMAIL_1] or call [PHONE_1]."

Semantic confirmation

Opt-in AI-powered verification of detected PII candidates using Jev (TypeSafe System One). Reduces false positives by asking a semantic model to confirm each candidate before redacting.

const redactor = createRedactor({
  rules: { person_name_lite: { action: "redact" } },
  semantic: {
    provider: "jev",
    apiKey: process.env.TYPESAFE_API_KEY!,
  },
});

const result = await redactor.redactAsync("Contact John Smith today");
// result.text: "Contact [PERSON_NAME] today"
// result.detections[0].semanticConfirmed: true

Currently only person_name_lite is opted in. Sync redact() and stream() are unaffected. Fails open if the AI provider is unavailable.

In independent evaluation against the evaluation corpus, person_name_lite with Jev semantic confirmation achieved 100% precision and 100% recall — zero false positives, zero false negatives.

Restoration

const redactor = createRedactor({
  presets: ["pii"],
  rules: {},
  restore: true,
});

const { text, map } = redactor.redact("Email: john@example.com");
// text: "Email: [EMAIL_1]"
// map: { "[EMAIL_1]": "john@example.com" }

const restored = redactor.restore(text, map);
// "Email: john@example.com"

Streaming

const redactor = createRedactor({ presets: ["pii"], rules: {} });

const stream = redactor.stream(asyncChunks());
for await (const event of stream) {
  if (event.type === "text") {
    process.stdout.write(event.text);
  }
}

Presets

PresetRulesUse case
pii96Personally identifiable information
gdpr39EU privacy regulation
hipaa29US healthcare
ccpa86California privacy
pci-dss6Payment card industry
healthcare19Healthcare identifiers
finance15Financial identifiers
education6Education sector
soc230SOC 2 security controls
security14Secrets and network identifiers

Presets are documented rule selections, not compliance guarantees.

Built-in detectors

129 detectors across 13 domains:

GroupDetectors
Contactemail, phone, address, postal_code
Financialpayment_card, iban, eu_vat, swift_bic, uk_sort_code, us_routing, uk_bank_account, card_data, financial_reference, investment_account, payment_gateway_id
National IDus_ssn, uk_nino, ca_sin, au_tfn, jp_my_number, uk_nhs, us_itin, us_ein, nz_ird, + 56 more across Europe, Asia, Africa, Middle East, Americas, Oceania
Identitypassport, drivers_license, digital_identity, license_plate, vin, imei, imsi
Personperson_name (requires compromise), person_name_lite (lightweight regex + bloom filter)
Networkipv4, ipv6, mac_address, url_with_auth
Cloud Keysaws_access_key, google_api_key, stripe_api_key, slack_token
Tokens & Keysgithub_token, jwt_token, private_key, generic_api_key
Healthcareus_npi, us_dea, medical_record_number, clinical_trial_id, medical_device_id, medical_code, medical_reference, genetic_info, health_insurance_id
HRhr_identifier, hr_screening, hr_compensation, hr_recruitment
Legallegal_case, legal_license, legal_reference
Cryptocrypto_address, crypto_tx_hash
Logisticstracking_number

See the detector reference for per-detector details.

Custom detectors

import { createRedactor, type DetectorDefinition } from "sensored";

const employeeId: DetectorDefinition = {
  id: "employee_id",
  entityType: "employee_id",
  pattern: /\bEMP-\d{6}\b/g,
  replacement: "[EMPLOYEE_ID]",
};

const redactor = createRedactor({
  presets: ["pii"],
  rules: { employee_id: { action: "redact" } },
  detectors: [employeeId],
});

Allowlist

Exclude specific values from redaction:

const redactor = createRedactor({
  presets: ["pii"],
  rules: {},
  allowlist: ["john@example.com"],
});

redactor.redact("Contact john@example.com or jane@example.com");
// "Contact john@example.com or [EMAIL_1]."

CLI

The sensored package includes a CLI for redacting, inspecting, and restoring PII in text files and pipelines.

# Redact from stdin
echo "Contact john@example.com" | bunx sensored --preset pii
# Contact [EMAIL]

# Redact a file
bunx sensored input.txt output.txt --preset pii

# Inspect detections
echo "Contact john@example.com" | bunx sensored inspect --preset pii

# Restore with a map
bunx sensored restore redacted.txt --map map.json restored.txt

See the CLI guide for full documentation.

Development

bun install          # install dependencies
bun test             # run tests (4835 tests)
bun run typecheck    # typecheck
bun run lint         # lint
bun run build        # build dist/
bun run eval:generate && bun run eval:score  # run eval suite
bun run bench        # run benchmarks

Acknowledgments

I would be remiss if I didn't give a lot of kudos to sam247 and their wonderful work on openredaction of which this project is heavily inspired by.

License

MIT

atomicpages/sensored

A free streaming-first open-source redaction toolit

TypeScript

1

10 commits

updated Sep 29, 2026

See the code

See what people are saying

README

sensored

sensored

A streaming-first PII redaction library for TypeScript. Detects and redacts sensitive data with 129 regex detectors, optional NER, and AI-powered semantic confirmation.

Install

bun add sensored

For higher-recall person name detection via NER:

bun add compromise

For AI-powered semantic confirmation that eliminates false positives:

bun add @typesafe-ai/sdk

The default person_name_lite detector uses a lightweight regex + bloom filter with zero runtime dependencies. Use person_name to opt into compromise.js NER, or add @typesafe-ai/sdk to let Jev confirm detections before redacting.

Quick start

import { createRedactor } from "sensored";

const redactor = createRedactor({ presets: ["pii"], rules: {} });

const text = "Contact me at john@example.com or call 555-123-4567.";
const redacted = redactor.redact(text);
// "Contact me at [EMAIL_1] or call [PHONE_1]."

Semantic confirmation

Opt-in AI-powered verification of detected PII candidates using Jev (TypeSafe System One). Reduces false positives by asking a semantic model to confirm each candidate before redacting.

const redactor = createRedactor({
  rules: { person_name_lite: { action: "redact" } },
  semantic: {
    provider: "jev",
    apiKey: process.env.TYPESAFE_API_KEY!,
  },
});

const result = await redactor.redactAsync("Contact John Smith today");
// result.text: "Contact [PERSON_NAME] today"
// result.detections[0].semanticConfirmed: true

Currently only person_name_lite is opted in. Sync redact() and stream() are unaffected. Fails open if the AI provider is unavailable.

In independent evaluation against the evaluation corpus, person_name_lite with Jev semantic confirmation achieved 100% precision and 100% recall — zero false positives, zero false negatives.

Restoration

const redactor = createRedactor({
  presets: ["pii"],
  rules: {},
  restore: true,
});

const { text, map } = redactor.redact("Email: john@example.com");
// text: "Email: [EMAIL_1]"
// map: { "[EMAIL_1]": "john@example.com" }

const restored = redactor.restore(text, map);
// "Email: john@example.com"

Streaming

const redactor = createRedactor({ presets: ["pii"], rules: {} });

const stream = redactor.stream(asyncChunks());
for await (const event of stream) {
  if (event.type === "text") {
    process.stdout.write(event.text);
  }
}

Presets

PresetRulesUse case
pii96Personally identifiable information
gdpr39EU privacy regulation
hipaa29US healthcare
ccpa86California privacy
pci-dss6Payment card industry
healthcare19Healthcare identifiers
finance15Financial identifiers
education6Education sector
soc230SOC 2 security controls
security14Secrets and network identifiers

Presets are documented rule selections, not compliance guarantees.

Built-in detectors

129 detectors across 13 domains:

GroupDetectors
Contactemail, phone, address, postal_code
Financialpayment_card, iban, eu_vat, swift_bic, uk_sort_code, us_routing, uk_bank_account, card_data, financial_reference, investment_account, payment_gateway_id
National IDus_ssn, uk_nino, ca_sin, au_tfn, jp_my_number, uk_nhs, us_itin, us_ein, nz_ird, + 56 more across Europe, Asia, Africa, Middle East, Americas, Oceania
Identitypassport, drivers_license, digital_identity, license_plate, vin, imei, imsi
Personperson_name (requires compromise), person_name_lite (lightweight regex + bloom filter)
Networkipv4, ipv6, mac_address, url_with_auth
Cloud Keysaws_access_key, google_api_key, stripe_api_key, slack_token
Tokens & Keysgithub_token, jwt_token, private_key, generic_api_key
Healthcareus_npi, us_dea, medical_record_number, clinical_trial_id, medical_device_id, medical_code, medical_reference, genetic_info, health_insurance_id
HRhr_identifier, hr_screening, hr_compensation, hr_recruitment
Legallegal_case, legal_license, legal_reference
Cryptocrypto_address, crypto_tx_hash
Logisticstracking_number

See the detector reference for per-detector details.

Custom detectors

import { createRedactor, type DetectorDefinition } from "sensored";

const employeeId: DetectorDefinition = {
  id: "employee_id",
  entityType: "employee_id",
  pattern: /\bEMP-\d{6}\b/g,
  replacement: "[EMPLOYEE_ID]",
};

const redactor = createRedactor({
  presets: ["pii"],
  rules: { employee_id: { action: "redact" } },
  detectors: [employeeId],
});

Allowlist

Exclude specific values from redaction:

const redactor = createRedactor({
  presets: ["pii"],
  rules: {},
  allowlist: ["john@example.com"],
});

redactor.redact("Contact john@example.com or jane@example.com");
// "Contact john@example.com or [EMAIL_1]."

CLI

The sensored package includes a CLI for redacting, inspecting, and restoring PII in text files and pipelines.

# Redact from stdin
echo "Contact john@example.com" | bunx sensored --preset pii
# Contact [EMAIL]

# Redact a file
bunx sensored input.txt output.txt --preset pii

# Inspect detections
echo "Contact john@example.com" | bunx sensored inspect --preset pii

# Restore with a map
bunx sensored restore redacted.txt --map map.json restored.txt

See the CLI guide for full documentation.

Development

bun install          # install dependencies
bun test             # run tests (4835 tests)
bun run typecheck    # typecheck
bun run lint         # lint
bun run build        # build dist/
bun run eval:generate && bun run eval:score  # run eval suite
bun run bench        # run benchmarks

Acknowledgments

I would be remiss if I didn't give a lot of kudos to sam247 and their wonderful work on openredaction of which this project is heavily inspired by.

License

MIT

Languages

TypeScript

100.0%