openredaction 1.0.10 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -6
- package/dist/index.cli.cjs +79 -152
- package/dist/index.d.mts +29 -320
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +29 -320
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +161 -1148
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +156 -1137
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +3 -14
- package/dist/react.d.mts.map +1 -1
- package/dist/react.d.ts +3 -14
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +79 -152
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +79 -152
- package/dist/react.mjs.map +1 -1
- package/dist/server.d.mts +1809 -0
- package/dist/server.d.mts.map +1 -0
- package/dist/server.d.ts +1809 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +18124 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +18109 -0
- package/dist/server.mjs.map +1 -0
- package/dist/workers/worker.cjs +17240 -0
- package/package.json +24 -4
package/dist/react.mjs
CHANGED
|
@@ -11069,6 +11069,38 @@ const transportLogisticsPreset = {
|
|
|
11069
11069
|
]
|
|
11070
11070
|
};
|
|
11071
11071
|
/**
|
|
11072
|
+
* PCI-DSS oriented preset — cardholder data and common payment identifiers
|
|
11073
|
+
*/
|
|
11074
|
+
const pciDssPreset = {
|
|
11075
|
+
includeNames: true,
|
|
11076
|
+
includeEmails: true,
|
|
11077
|
+
includePhones: true,
|
|
11078
|
+
includeAddresses: true,
|
|
11079
|
+
categories: [
|
|
11080
|
+
"personal",
|
|
11081
|
+
"contact",
|
|
11082
|
+
"financial",
|
|
11083
|
+
"network"
|
|
11084
|
+
]
|
|
11085
|
+
};
|
|
11086
|
+
/**
|
|
11087
|
+
* SOC 2 oriented preset — broad PII and credentials for trust services contexts
|
|
11088
|
+
*/
|
|
11089
|
+
const soc2Preset = {
|
|
11090
|
+
includeNames: true,
|
|
11091
|
+
includeEmails: true,
|
|
11092
|
+
includePhones: true,
|
|
11093
|
+
includeAddresses: true,
|
|
11094
|
+
categories: [
|
|
11095
|
+
"personal",
|
|
11096
|
+
"contact",
|
|
11097
|
+
"financial",
|
|
11098
|
+
"government",
|
|
11099
|
+
"network",
|
|
11100
|
+
"digital-identity"
|
|
11101
|
+
]
|
|
11102
|
+
};
|
|
11103
|
+
/**
|
|
11072
11104
|
* Get preset configuration by name
|
|
11073
11105
|
*/
|
|
11074
11106
|
function getPreset(name) {
|
|
@@ -11085,6 +11117,10 @@ function getPreset(name) {
|
|
|
11085
11117
|
case "transport-logistics":
|
|
11086
11118
|
case "transportation":
|
|
11087
11119
|
case "logistics": return transportLogisticsPreset;
|
|
11120
|
+
case "pci-dss":
|
|
11121
|
+
case "pci_dss": return pciDssPreset;
|
|
11122
|
+
case "soc2":
|
|
11123
|
+
case "soc-2": return soc2Preset;
|
|
11088
11124
|
default: return {};
|
|
11089
11125
|
}
|
|
11090
11126
|
}
|
|
@@ -11603,7 +11639,7 @@ var ConfigLoader = class {
|
|
|
11603
11639
|
static createDefaultConfig(outputPath = ".openredaction.config.js") {
|
|
11604
11640
|
fs.writeFileSync(outputPath, `/**
|
|
11605
11641
|
* OpenRedaction Configuration
|
|
11606
|
-
* @see https://github.com/
|
|
11642
|
+
* @see https://github.com/sam247/openredaction
|
|
11607
11643
|
*/
|
|
11608
11644
|
export default {
|
|
11609
11645
|
// Extend built-in presets
|
|
@@ -14270,133 +14306,6 @@ function validatePattern(pattern) {
|
|
|
14270
14306
|
}
|
|
14271
14307
|
}
|
|
14272
14308
|
|
|
14273
|
-
//#endregion
|
|
14274
|
-
//#region src/utils/ai-assist.ts
|
|
14275
|
-
/**
|
|
14276
|
-
* Get the AI endpoint URL from options or environment
|
|
14277
|
-
*/
|
|
14278
|
-
function getAIEndpoint(aiOptions) {
|
|
14279
|
-
if (!aiOptions?.enabled) return null;
|
|
14280
|
-
if (aiOptions.endpoint) return aiOptions.endpoint;
|
|
14281
|
-
if (typeof process !== "undefined" && process.env) {
|
|
14282
|
-
const envEndpoint = process.env.OPENREDACTION_AI_ENDPOINT;
|
|
14283
|
-
if (envEndpoint) return envEndpoint;
|
|
14284
|
-
}
|
|
14285
|
-
return null;
|
|
14286
|
-
}
|
|
14287
|
-
/**
|
|
14288
|
-
* Check if fetch is available in the current environment
|
|
14289
|
-
*/
|
|
14290
|
-
function isFetchAvailable() {
|
|
14291
|
-
return typeof fetch !== "undefined";
|
|
14292
|
-
}
|
|
14293
|
-
/**
|
|
14294
|
-
* Call the AI endpoint to get additional PII entities
|
|
14295
|
-
* Returns null if AI is disabled, endpoint unavailable, or on error
|
|
14296
|
-
*/
|
|
14297
|
-
async function callAIDetect(text, endpoint, debug) {
|
|
14298
|
-
if (!isFetchAvailable()) {
|
|
14299
|
-
if (debug) console.warn("[OpenRedaction] AI assist requires fetch API. Not available in this environment.");
|
|
14300
|
-
return null;
|
|
14301
|
-
}
|
|
14302
|
-
try {
|
|
14303
|
-
const url = endpoint.endsWith("/ai-detect") ? endpoint : `${endpoint}/ai-detect`;
|
|
14304
|
-
if (debug) console.log(`[OpenRedaction] Calling AI endpoint: ${url}`);
|
|
14305
|
-
const response = await fetch(url, {
|
|
14306
|
-
method: "POST",
|
|
14307
|
-
headers: { "Content-Type": "application/json" },
|
|
14308
|
-
body: JSON.stringify({ text })
|
|
14309
|
-
});
|
|
14310
|
-
if (!response.ok) {
|
|
14311
|
-
if (debug) {
|
|
14312
|
-
const statusText = response.status === 429 ? "Rate limit exceeded (429)" : `${response.status}: ${response.statusText}`;
|
|
14313
|
-
console.warn(`[OpenRedaction] AI endpoint returned ${statusText}`);
|
|
14314
|
-
}
|
|
14315
|
-
return null;
|
|
14316
|
-
}
|
|
14317
|
-
const data = await response.json();
|
|
14318
|
-
if (!data.entities || !Array.isArray(data.entities)) {
|
|
14319
|
-
if (debug) console.warn("[OpenRedaction] Invalid AI response format: missing entities array");
|
|
14320
|
-
return null;
|
|
14321
|
-
}
|
|
14322
|
-
return data.entities;
|
|
14323
|
-
} catch (error) {
|
|
14324
|
-
if (debug) console.warn(`[OpenRedaction] AI endpoint error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
14325
|
-
return null;
|
|
14326
|
-
}
|
|
14327
|
-
}
|
|
14328
|
-
/**
|
|
14329
|
-
* Validate an AI entity
|
|
14330
|
-
*/
|
|
14331
|
-
function validateAIEntity(entity, textLength) {
|
|
14332
|
-
if (!entity.type || !entity.value || typeof entity.start !== "number" || typeof entity.end !== "number") return false;
|
|
14333
|
-
if (entity.start < 0 || entity.end < 0 || entity.start >= entity.end) return false;
|
|
14334
|
-
if (entity.start >= textLength || entity.end > textLength) return false;
|
|
14335
|
-
if (entity.value.length !== entity.end - entity.start) return false;
|
|
14336
|
-
return true;
|
|
14337
|
-
}
|
|
14338
|
-
/**
|
|
14339
|
-
* Check if two detections overlap significantly
|
|
14340
|
-
* Returns true if they overlap by more than 50% of the shorter detection
|
|
14341
|
-
*/
|
|
14342
|
-
function detectionsOverlap(det1, det2) {
|
|
14343
|
-
const [start1, end1] = det1.position;
|
|
14344
|
-
const [start2, end2] = det2.position;
|
|
14345
|
-
const overlapStart = Math.max(start1, start2);
|
|
14346
|
-
const overlapEnd = Math.min(end1, end2);
|
|
14347
|
-
if (overlapStart >= overlapEnd) return false;
|
|
14348
|
-
const overlapLength = overlapEnd - overlapStart;
|
|
14349
|
-
const length1 = end1 - start1;
|
|
14350
|
-
const length2 = end2 - start2;
|
|
14351
|
-
return overlapLength > Math.min(length1, length2) * .5;
|
|
14352
|
-
}
|
|
14353
|
-
/**
|
|
14354
|
-
* Convert AI entity to PIIDetection format
|
|
14355
|
-
*/
|
|
14356
|
-
function convertAIEntityToDetection(entity, text) {
|
|
14357
|
-
if (!validateAIEntity(entity, text.length)) return null;
|
|
14358
|
-
const actualValue = text.substring(entity.start, entity.end);
|
|
14359
|
-
let type = entity.type.toUpperCase();
|
|
14360
|
-
if (type.includes("EMAIL") || type === "EMAIL_ADDRESS") type = "EMAIL";
|
|
14361
|
-
else if (type.includes("PHONE") || type === "PHONE_NUMBER") type = "PHONE_US";
|
|
14362
|
-
else if (type.includes("NAME") || type === "PERSON") type = "NAME";
|
|
14363
|
-
else if (type.includes("SSN") || type === "SOCIAL_SECURITY_NUMBER") type = "SSN";
|
|
14364
|
-
else if (type.includes("ADDRESS")) type = "ADDRESS_STREET";
|
|
14365
|
-
let severity = "medium";
|
|
14366
|
-
if (type === "SSN" || type === "CREDIT_CARD") severity = "critical";
|
|
14367
|
-
else if (type === "EMAIL" || type === "PHONE_US" || type === "NAME") severity = "high";
|
|
14368
|
-
return {
|
|
14369
|
-
type,
|
|
14370
|
-
value: actualValue,
|
|
14371
|
-
placeholder: `[${type}_${Math.random().toString(36).substring(2, 9)}]`,
|
|
14372
|
-
position: [entity.start, entity.end],
|
|
14373
|
-
severity,
|
|
14374
|
-
confidence: entity.confidence ?? .7
|
|
14375
|
-
};
|
|
14376
|
-
}
|
|
14377
|
-
/**
|
|
14378
|
-
* Merge AI entities with regex detections
|
|
14379
|
-
* Prefers regex detections on conflicts
|
|
14380
|
-
*/
|
|
14381
|
-
function mergeAIEntities(regexDetections, aiEntities, text) {
|
|
14382
|
-
const merged = [...regexDetections];
|
|
14383
|
-
const processedRanges = regexDetections.map((d) => d.position);
|
|
14384
|
-
for (const aiEntity of aiEntities) {
|
|
14385
|
-
const detection = convertAIEntityToDetection(aiEntity, text);
|
|
14386
|
-
if (!detection) continue;
|
|
14387
|
-
let hasOverlap = false;
|
|
14388
|
-
for (const regexDet of regexDetections) if (detectionsOverlap(regexDet, detection)) {
|
|
14389
|
-
hasOverlap = true;
|
|
14390
|
-
break;
|
|
14391
|
-
}
|
|
14392
|
-
if (!hasOverlap) {
|
|
14393
|
-
merged.push(detection);
|
|
14394
|
-
processedRanges.push(detection.position);
|
|
14395
|
-
}
|
|
14396
|
-
}
|
|
14397
|
-
return merged;
|
|
14398
|
-
}
|
|
14399
|
-
|
|
14400
14309
|
//#endregion
|
|
14401
14310
|
//#region src/config/ConfigExporter.ts
|
|
14402
14311
|
var ConfigExporter_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -16541,7 +16450,7 @@ var OpenRedaction = class OpenRedaction {
|
|
|
16541
16450
|
redactionMode: "placeholder",
|
|
16542
16451
|
enableContextAnalysis: true,
|
|
16543
16452
|
confidenceThreshold: .5,
|
|
16544
|
-
enableFalsePositiveFilter:
|
|
16453
|
+
enableFalsePositiveFilter: true,
|
|
16545
16454
|
falsePositiveThreshold: .7,
|
|
16546
16455
|
enableMultiPass: false,
|
|
16547
16456
|
multiPassCount: 3,
|
|
@@ -16770,8 +16679,9 @@ var OpenRedaction = class OpenRedaction {
|
|
|
16770
16679
|
throw error;
|
|
16771
16680
|
}
|
|
16772
16681
|
}
|
|
16773
|
-
if (this.nerDetector &&
|
|
16774
|
-
const
|
|
16682
|
+
if (this.nerDetector && this.nerDetector.isAvailable()) {
|
|
16683
|
+
const nerMatches = this.nerDetector.detect(text);
|
|
16684
|
+
let piiMatches = detections.map((det) => ({
|
|
16775
16685
|
type: det.type,
|
|
16776
16686
|
value: det.value,
|
|
16777
16687
|
start: det.position[0],
|
|
@@ -16782,11 +16692,43 @@ var OpenRedaction = class OpenRedaction {
|
|
|
16782
16692
|
after: text.substring(det.position[1], Math.min(text.length, det.position[1] + 50))
|
|
16783
16693
|
}
|
|
16784
16694
|
}));
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16789
|
-
|
|
16695
|
+
if (detections.length > 0) {
|
|
16696
|
+
const hybridMatches = this.nerDetector.hybridDetection(piiMatches, text);
|
|
16697
|
+
detections = detections.map((det, index) => ({
|
|
16698
|
+
...det,
|
|
16699
|
+
confidence: hybridMatches[index].confidence
|
|
16700
|
+
}));
|
|
16701
|
+
piiMatches = detections.map((det) => ({
|
|
16702
|
+
type: det.type,
|
|
16703
|
+
value: det.value,
|
|
16704
|
+
start: det.position[0],
|
|
16705
|
+
end: det.position[1],
|
|
16706
|
+
confidence: det.confidence || 1,
|
|
16707
|
+
context: {
|
|
16708
|
+
before: text.substring(Math.max(0, det.position[0] - 50), det.position[0]),
|
|
16709
|
+
after: text.substring(det.position[1], Math.min(text.length, det.position[1] + 50))
|
|
16710
|
+
}
|
|
16711
|
+
}));
|
|
16712
|
+
}
|
|
16713
|
+
const nerOnly = this.nerDetector.extractNEROnly(nerMatches, piiMatches);
|
|
16714
|
+
for (const ner of nerOnly) {
|
|
16715
|
+
const syntheticPattern = {
|
|
16716
|
+
type: `NER_${ner.type}`,
|
|
16717
|
+
regex: /.^/,
|
|
16718
|
+
priority: 1,
|
|
16719
|
+
placeholder: `[NER_${ner.type}_{n}]`,
|
|
16720
|
+
severity: "medium"
|
|
16721
|
+
};
|
|
16722
|
+
const placeholder = this.generatePlaceholder(ner.text, syntheticPattern);
|
|
16723
|
+
detections.push({
|
|
16724
|
+
type: syntheticPattern.type,
|
|
16725
|
+
value: ner.text,
|
|
16726
|
+
placeholder,
|
|
16727
|
+
position: [ner.start, ner.end],
|
|
16728
|
+
severity: "medium",
|
|
16729
|
+
confidence: ner.confidence
|
|
16730
|
+
});
|
|
16731
|
+
}
|
|
16790
16732
|
}
|
|
16791
16733
|
if (this.contextRulesEngine && detections.length > 0) {
|
|
16792
16734
|
const piiMatches = detections.map((det) => ({
|
|
@@ -16810,7 +16752,7 @@ var OpenRedaction = class OpenRedaction {
|
|
|
16810
16752
|
}
|
|
16811
16753
|
/**
|
|
16812
16754
|
* Detect PII in text
|
|
16813
|
-
*
|
|
16755
|
+
* Async API for detection pipeline (NER, multi-pass, etc.)
|
|
16814
16756
|
*/
|
|
16815
16757
|
async detect(text) {
|
|
16816
16758
|
if (this.rbacManager && !this.rbacManager.hasPermission("detection:detect")) throw new Error("[OpenRedaction] Permission denied: detection:detect required");
|
|
@@ -16850,21 +16792,6 @@ var OpenRedaction = class OpenRedaction {
|
|
|
16850
16792
|
}
|
|
16851
16793
|
detections = mergePassDetections(passDetections, this.multiPassConfig);
|
|
16852
16794
|
} else detections = this.processPatterns(text, this.patterns, processedRanges);
|
|
16853
|
-
if (this.options.ai?.enabled) {
|
|
16854
|
-
const aiEndpoint = getAIEndpoint(this.options.ai);
|
|
16855
|
-
if (aiEndpoint) try {
|
|
16856
|
-
if (this.options.debug) console.log("[OpenRedaction] AI assist enabled, calling AI endpoint...");
|
|
16857
|
-
const aiEntities = await callAIDetect(text, aiEndpoint, this.options.debug);
|
|
16858
|
-
if (aiEntities && aiEntities.length > 0) {
|
|
16859
|
-
if (this.options.debug) console.log(`[OpenRedaction] AI returned ${aiEntities.length} additional entities`);
|
|
16860
|
-
detections = mergeAIEntities(detections, aiEntities, text);
|
|
16861
|
-
if (this.options.debug) console.log(`[OpenRedaction] After AI merge: ${detections.length} total detections`);
|
|
16862
|
-
} else if (this.options.debug) console.log("[OpenRedaction] AI endpoint returned no additional entities");
|
|
16863
|
-
} catch (error) {
|
|
16864
|
-
if (this.options.debug) console.warn(`[OpenRedaction] AI assist failed, using regex-only: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
16865
|
-
}
|
|
16866
|
-
else if (this.options.debug) console.warn("[OpenRedaction] AI assist enabled but no endpoint configured. Set ai.endpoint or OPENREDACTION_AI_ENDPOINT env var.");
|
|
16867
|
-
}
|
|
16868
16795
|
detections.sort((a, b) => b.position[0] - a.position[0]);
|
|
16869
16796
|
let redacted = text;
|
|
16870
16797
|
const redactionMap = {};
|