aegis-redact
v0.4.5
Published
Zero-knowledge, client-side PII redaction for PDFs. Documents never leave the browser.
Maintainers
Readme
aegis-redact
Zero-knowledge PII redaction for PDFs. The document never leaves the browser.
No upload, no server call, no telemetry. Give it a PDF, get back an image-only PDF with the sensitive fields destroyed at the pixel level — not covered, destroyed. There is no text layer in the output, so there is nothing to recover.
npm install aegis-redactQuick start
import { createAegis } from 'aegis-redact/browser';
const aegis = await createAegis();
const result = await aegis.redact(file); // a File from <input type="file">
result.redactedPDF; // ArrayBuffer — image-only
result.detected; // what was found, where, by which path, at what confidence
result.redactionCount; // rectangles that ACTUALLY covered pixels
result.warnings; // non-fatal observations worth surfacing to your userThe package ships ESM and CJS only — there is no global/IIFE build, so it needs a
bundler or a native ESM import. A <script src=...> tag pointing at a .global.js will
404.
What it detects
| Document | Fields | | --- | --- | | W-2 | SSN / ITIN | | Form 1040 | SSN / ITIN (taxpayer, spouse, dependents), routing, account, IP PIN | | Bank statement | Account number, member number, routing | | Payslip | SSN / ITIN, routing, account |
EIN, phone and date of birth are additionally available as opt-in field types (see Options).
Document type is auto-detected. Override it when you already know:
await aegis.redact(file, { documentType: 'BANK_STATEMENT' });Options
await aegis.redact(file, {
documentType: 'W2', // skip auto-detection
signal: controller.signal, // cancel a long job
onProgress: (p) => setBar(p), // 0 → 1
redactEin: false, // opt-in, off by default
redactPhone: false, // opt-in, off by default
redactDob: false, // opt-in, off by default
});EIN, phone and date of birth are off by default on purpose. The employer EIN is public information and identifies the employer, not the employee; phone and DOB are what income verification needs for identity matching: income verification needs them for identity matching, and redacting them reduces the document's utility without a corresponding privacy gain. Turn them on for workflows that share documents onward.
Errors
redact() throws AegisError rather than returning a partial result.
| Code | Meaning |
| --- | --- |
| FILE_TOO_LARGE | Over 10 MB or over 50 pages |
| INVALID_PDF | Not a PDF |
| CORRUPTED_PDF | pdf.js could not parse it |
| PASSWORD_PROTECTED | Encrypted |
| UNKNOWN_DOCUMENT_TYPE | Could not classify — pass documentType |
| UNREADABLE_DOCUMENT | Expected targets, found none — see below |
| ALREADY_IMAGE_ONLY | No text layer; possibly already redacted, or a scan |
| ABORTED | Cancelled via signal |
UNREADABLE_DOCUMENT is the important one. When a document type that always contains
a target yields zero detections, the text layer probably failed silently. The SDK refuses
to hand back a document it may not have redacted. Surface it to your user and ask them to
re-upload; do not treat it as a soft failure.
try {
const result = await aegis.redact(file);
} catch (e) {
if (e instanceof AegisError && e.code === 'UNREADABLE_DOCUMENT') {
showRetryPrompt();
}
}Serving the static assets
By default the pdf.js worker and font data resolve from your node_modules and your
bundler handles them. Under a strict CSP, serve them yourself:
const aegis = await createAegis({ assetBaseUrl: '/aegis-assets' });Copy node_modules/pdfjs-dist/build/pdf.worker.min.mjs and
node_modules/pdfjs-dist/standard_fonts/ into that directory.
The font data is not optional. Without it pdf.js cannot build glyph outlines for the base-14 fonts and text extraction degrades silently — which means missed detections.
Content Security Policy
script-src 'self' 'wasm-unsafe-eval';
worker-src 'self' blob:;
img-src 'self' data: blob:;
connect-src 'self';wasm-unsafe-eval is required by pdf.js.
connect-src 'self' is sufficient only for the rules-only configuration. That is the
default and it genuinely makes no outbound request — which is worth proving to your own
security reviewers. But the two optional layers do fetch, and their assets are large:
| Configuration | What it fetches | CSP |
| --- | --- | --- |
| Rules only (default) | nothing | connect-src 'self' |
| + OCR | Tesseract worker, WASM core and eng.traineddata | connect-src 'self' only if you self-host; otherwise Tesseract's CDN |
| + NER | ~14.7MB ONNX model from huggingface.co | connect-src 'self' only if you self-host; otherwise https://huggingface.co https://cdn-lfs.huggingface.co |
Self-hosting both gets you back to connect-src 'self', and that is the recommended
enterprise posture — pass workerPath/corePath/langPath to the OCR engine and serve
the model from your own origin. Neither fetch ever carries document content; they are
downloads of static assets, in the opposite direction. But an allowlist that does not
mention them will fail closed in staging, so plan for it before your AppSec review rather
than during it.
Optional layers
Both are off by default and neither is a dependency of this package — install them only if you want them.
# OCR fallback for scanned or photographed documents
npm install tesseract.jsimport Tesseract from 'tesseract.js';
const aegis = await createAegis({ ocr: { tesseract: Tesseract } });# NER recall layer (adds a ~14.7 MB model download, cached in IndexedDB)
npm install @nationaldesignstudio/rampart @huggingface/transformersconst aegis = await createAegis({ ner: true });Without an OCR engine, a document with no text layer raises ALREADY_IMAGE_ONLY rather
than being returned unredacted.
Browser support
Chrome/Edge 94+, Firefox 105+, Safari 16.4+. The floor is OffscreenCanvas.
Bundle size
~20 KB gzipped for the ESM entry and its chunk; ~59 KB for the full published dist
(ESM + CJS + type declarations). pdf.js is a peer cost on top (~1.1 MB, lazily imported
and separately cacheable), and its worker is a separate file that is not in your entry
chunk.
The trade-off, stated plainly
Output is image-only. It is not searchable and not readable by screen readers. That is inherent to irreversible redaction, not an oversight: a black rectangle drawn over live text leaves the text intact in the content stream, which both the NSA and GCHQ have published warnings about. If redaction is reversible, it is not redaction.
If your workflow needs searchable output, this is the wrong tool.
Scope
Aegis is a best-effort upstream filter, not your last line of defence. A miss restores the status quo — the value reaches your server as it did before Aegis existed — it does not create new exposure. Keep your existing access controls, encryption and retention policies.
Licence
Proprietary. Free 30-day evaluation; production use requires a commercial licence. See LICENSE and THIRD_PARTY_NOTICES.md.
