@editora/pii-redaction
v1.0.1
Published
Native PII detection and redaction plugin for Editora
Maintainers
Readme
@editora/pii-redaction
[!IMPORTANT] Live Website: https://editora-ecosystem.netlify.app/
Storybook: https://editora-ecosystem-storybook.netlify.app/
@editora/pii-redaction detects sensitive data patterns (PII/secrets) and helps redact them before documents are shared, exported, or approved.
Features
- Native framework-agnostic plugin (no framework dependency)
- Works in React (Vite/CRA) and Web Components without modification
- Built-in detectors: email, phone, SSN, credit card, IPv4, API key, JWT
- Realtime debounced scanning for performance-sensitive typing
- Redact one finding or redact all findings in one command
- Accessible panel (
role="dialog", keyboard navigation,aria-liveupdates) - Light/dark theme support
- Multi-instance safe state (findings, options, panel, timers)
Install
npm install @editora/pii-redactionBasic Usage (React)
import { EditoraEditor } from '@editora/react';
import { HistoryPlugin, PIIRedactionPlugin } from '@editora/plugins';
const plugins = [
HistoryPlugin(),
PIIRedactionPlugin({
enableRealtime: true,
redactionMode: 'token',
redactionToken: 'REDACTED',
maxFindings: 120,
}),
];
export default function App() {
return <EditoraEditor plugins={plugins} />;
}Basic Usage (Web Component)
<editora-editor id="editor"></editora-editor>
<script>
const editor = document.getElementById('editor');
editor.setConfig({
plugins: 'history pii-redaction',
toolbar: {
items: 'undo redo | piiRedaction piiScan piiRedactAll piiRealtime',
},
});
</script>Accepted aliases: piiRedaction, pii-redaction, piiredaction.
Practical Scenario
Scenario: Pre-release compliance pass for a customer incident memo
A content team prepares an external incident memo. The draft accidentally includes:
- direct customer email addresses
- internal support phone numbers
- a pasted API key from a debugging note
Before legal approval, the team must detect and redact all sensitive values.
Suggested flow
- Open panel:
Ctrl/Cmd + Alt + Shift + I. - Run scan:
Ctrl/Cmd + Alt + Shift + U. - Review findings list (type, severity, detected value, masked preview).
- Use
Locateto confirm context. - Redact single sensitive values where needed.
- Run
Redact Allto clear remaining findings. - Re-run scan and confirm zero findings before export.
Why this is useful
- Prevents accidental leakage in docs moving across teams.
- Gives editors a repeatable pre-share safety checkpoint.
- Keeps remediation in-editor without external scripts/tools.
Step-by-Step Test Scenario (Manual QA)
Use this exact flow to validate behavior quickly in Storybook or your app.
1) Seed test content
Paste the content below into an editor instance:
<h2>Incident Draft</h2>
<p>Owner email: [email protected]</p>
<p>Escalation phone: +1 (415) 555-0136</p>
<p>Temporary key: sk-ABCDEF1234567890ABCDEF1234567890AB</p>
<p>Session token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Kf7f0Xf7abcABC123zzYY77abcABC123zzYY77</p>2) Run the first scan
- Open panel:
Ctrl/Cmd + Alt + Shift + I - Run scan:
Ctrl/Cmd + Alt + Shift + U - Verify findings appear (email, phone, API key, JWT)
Expected result: findings list and severity summary are visible.
3) Validate per-item actions
- Click
Locateon one finding - Confirm selection jumps to matched content
- Click
Redacton that finding
Expected result: one finding is removed/updated, editor content changes, redaction event is emitted.
4) Validate redact-all
- Press
Ctrl/Cmd + Alt + Shift + M - Run scan again
Expected result: zero findings after full redaction.
5) Validate realtime toggle
- Toggle realtime off:
Ctrl/Cmd + Alt + Shift + Y - Type a new email like
[email protected] - Confirm panel does not auto-refresh immediately
- Toggle realtime on and type another sensitive value
Expected result: detection resumes only when realtime is on.
6) Validate readonly behavior
- Put editor in readonly mode
- Attempt
RedactandRedact All
Expected result: redaction actions are blocked and readonly message is shown.
7) Validate multi-instance isolation
- Open two editors (A and B), each with different sensitive content
- Run scan in A only
- Redact in A only
Expected result: A state/buttons/findings change; B remains unchanged.
Toolbar Commands
togglePIIRedactionPanel-> open/close PII panelrunPIIScan-> run immediate scan and refresh findingsredactAllPII-> redact all currently detectable findingsredactPIIFinding-> redact one finding by idtogglePIIRealtime-> enable/disable realtime scanninggetPIIRedactionFindings-> emiteditora:pii-findings, cache findings oneditor.__piiRedactionFindings, optional callbacksetPIIRedactionOptions-> update detector/options at runtime
Keyboard Shortcuts
Ctrl/Cmd + Alt + Shift + I-> open/close PII panelCtrl/Cmd + Alt + Shift + U-> run PII scanCtrl/Cmd + Alt + Shift + M-> redact all findingsCtrl/Cmd + Alt + Shift + Y-> toggle realtime scanEsc-> close panel
Advanced Usage
Restrict detectors and severity tuning
PIIRedactionPlugin({
detectors: {
email: { enabled: true, severity: 'medium' },
'api-key': { enabled: true, severity: 'high' },
jwt: { enabled: true, severity: 'high' },
ipv4: false, // disable low-risk detector for this doc type
},
});Multi-instance React usage (isolated command execution)
import { useRef } from 'react';
import { EditoraEditor } from '@editora/react';
import { HistoryPlugin, PIIRedactionPlugin } from '@editora/plugins';
export default function DualEditors() {
const editorA = useRef<any>(null);
const editorB = useRef<any>(null);
return (
<>
<EditoraEditor
plugins={[HistoryPlugin(), PIIRedactionPlugin({ enableRealtime: true })]}
onInit={(api) => (editorA.current = api)}
/>
<EditoraEditor
plugins={[HistoryPlugin(), PIIRedactionPlugin({ enableRealtime: false, redactionMode: 'mask' })]}
onInit={(api) => (editorB.current = api)}
/>
<button onClick={() => editorA.current?.execCommand('runPIIScan')}>Scan A</button>
<button onClick={() => editorB.current?.execCommand('runPIIScan')}>Scan B</button>
</>
);
}Runtime option update
(window as any).executeEditorCommand?.('setPIIRedactionOptions', {
enableRealtime: false,
redactionMode: 'mask',
maxFindings: 200,
});
(window as any).executeEditorCommand?.('runPIIScan');Release gate before export/share
const exec = (window as any).executeEditorCommand;
exec?.('runPIIScan');
exec?.('getPIIRedactionFindings', (findings, stats) => {
if (stats.total > 0) {
throw new Error(`Blocked export: ${stats.total} PII findings still present.`);
}
// safeExportDocument();
});Profile switching by document phase
const exec = (window as any).executeEditorCommand;
// Authoring profile
exec?.('setPIIRedactionOptions', {
enableRealtime: true,
redactionMode: 'mask',
detectors: { ipv4: false },
});
// Pre-release profile
exec?.('setPIIRedactionOptions', {
enableRealtime: false,
redactionMode: 'token',
maxFindings: 300,
detectors: { ipv4: true, jwt: true, 'api-key': true },
});
exec?.('runPIIScan');Read findings programmatically
const exec = (window as any).executeEditorCommand;
exec?.('getPIIRedactionFindings', (findings, stats) => {
console.log('findings', findings);
console.log('stats', stats);
});
document.addEventListener('editora:pii-findings', (event) => {
const detail = (event as CustomEvent).detail;
console.log('event findings', detail?.findings || []);
});Edge Cases Covered
- Multi-instance isolation for findings/options/panels/timers
- Debounced realtime scans for large docs
- Snapshot dedupe avoids redundant full rescans
- Credit-card detector uses Luhn validation to reduce false positives
- Mask mode output remains re-scan safe (idempotent redaction behavior)
- Redaction actions are blocked in readonly mode and announced in panel
- Detached editors are cleaned up deterministically (panel/timers/listeners state)
- Panel + listeners are detached cleanly when plugin is destroyed
