@privacyscrubber/sdk 2.3.7 โ 2.4.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 +86 -28
- package/benchmark.js +347 -0
- package/browser.js +2 -2
- package/cli.js +54 -2
- package/examples/python-bridge/MANIFEST.in +5 -0
- package/examples/python-bridge/README.md +265 -0
- package/examples/python-bridge/bridge_daemon.cjs +241 -0
- package/examples/python-bridge/dist/privacyscrubber-2.3.9-py3-none-any.whl +0 -0
- package/examples/python-bridge/dist/privacyscrubber-2.3.9.tar.gz +0 -0
- package/examples/python-bridge/fastapi_example.py +206 -0
- package/examples/python-bridge/langchain_adapter.py +20 -0
- package/examples/python-bridge/langchain_example.py +126 -0
- package/examples/python-bridge/llamaindex_example.py +103 -0
- package/examples/python-bridge/privacyscrubber/__init__.py +42 -0
- package/examples/python-bridge/privacyscrubber/bridge_daemon.cjs +241 -0
- package/examples/python-bridge/privacyscrubber/cli.py +138 -0
- package/examples/python-bridge/privacyscrubber/client.py +542 -0
- package/examples/python-bridge/privacyscrubber/exceptions.py +26 -0
- package/examples/python-bridge/privacyscrubber/langchain.py +161 -0
- package/examples/python-bridge/privacyscrubber/types.py +52 -0
- package/examples/python-bridge/privacyscrubber.egg-info/PKG-INFO +311 -0
- package/examples/python-bridge/privacyscrubber.egg-info/SOURCES.txt +18 -0
- package/examples/python-bridge/privacyscrubber.egg-info/dependency_links.txt +1 -0
- package/examples/python-bridge/privacyscrubber.egg-info/entry_points.txt +3 -0
- package/examples/python-bridge/privacyscrubber.egg-info/requires.txt +18 -0
- package/examples/python-bridge/privacyscrubber.egg-info/top_level.txt +1 -0
- package/examples/python-bridge/privacyscrubber.py +36 -0
- package/examples/python-bridge/pyproject.toml +62 -0
- package/examples/python-bridge/setup.py +66 -0
- package/examples/python-bridge/test_python_bridge.py +320 -0
- package/examples/vercel-ai-stream.js +195 -0
- package/fastmcp.cjs +291 -2
- package/fastmcp.d.ts +18 -0
- package/fastmcp.js +4 -0
- package/index.cjs +211 -134
- package/index.js +211 -134
- package/package.json +15 -3
- package/ps-license-manager.js +1 -1
- package/ps-pii-engine.cjs +1 -1
- package/scrubber-core.cjs +1 -1
- package/vercel-ai.cjs +400 -0
- package/vercel-ai.d.ts +156 -0
- package/vercel-ai.js +36 -0
- package/vision.cjs +13 -5
- package/vision.js +13 -5
package/index.js
CHANGED
|
@@ -38,16 +38,33 @@ if (scrubberPkg && typeof scrubberPkg.init === 'function') {
|
|
|
38
38
|
* Built-in patterns for DevOps & Cloud Secrets detection.
|
|
39
39
|
*/
|
|
40
40
|
export const DEVOPS_SECRETS_DETECTOR = [
|
|
41
|
-
{ name: 'AWS Credentials', regex: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA)[A-Z0-9]{
|
|
41
|
+
{ name: 'AWS Credentials', regex: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA)[A-Z0-9]{12,28}\b/g, label: 'AWS_KEY' },
|
|
42
|
+
{ name: 'Google Cloud / Gemini API Key', regex: /\bAIza[0-9A-Za-z_-]{35}\b/g, label: 'API_TOKEN' },
|
|
43
|
+
{ name: 'Anthropic API Key', regex: /\bsk-ant-(?:api\d{2}|admin\d{2}|sid\d{2})-[a-zA-Z0-9_-]{16,128}\b/g, label: 'API_TOKEN' },
|
|
44
|
+
{ name: 'HuggingFace / Perplexity API Key', regex: /\b(?:hf_[a-zA-Z0-9]{20,64}|pplx-[a-zA-Z0-9]{24,64})\b/g, label: 'API_TOKEN' },
|
|
45
|
+
{ name: 'SendGrid API Key', regex: /\bSG\.[a-zA-Z0-9_-]{16,32}\.[a-zA-Z0-9_-]{32,64}\b/g, label: 'API_TOKEN' },
|
|
46
|
+
{ name: 'Postman API Key', regex: /\bPMAK-[a-fA-F0-9]{24}-[a-fA-F0-9]{34}\b/g, label: 'API_TOKEN' },
|
|
47
|
+
{ name: 'GitHub Fine-Grained PAT', regex: /\bgithub_pat_[a-zA-Z0-9]{20,30}_[a-zA-Z0-9]{40,80}\b/g, label: 'API_TOKEN' },
|
|
48
|
+
{ name: 'Slack Enterprise / Refresh Token', regex: /\bxoxe\.xox[baprs]-\d-[a-zA-Z0-9-]{10,}\b|\bxox[eapbrs]-[a-zA-Z0-9-]{10,}\b/g, label: 'API_TOKEN' },
|
|
49
|
+
{ name: 'Telegram Bot API Token', regex: /\b\d{8,10}:AA[A-Za-z0-9_-]{33}\b/g, label: 'API_TOKEN' },
|
|
50
|
+
{ name: 'Age Encryption Secret Key', regex: /\bAGE-SECRET-KEY-1[A-Z0-9]{58}\b/g, label: 'SECRET' },
|
|
51
|
+
{ name: 'Modern Cloud & AI Platform Key', regex: /\b(?:sbp_[a-fA-F0-9]{40}|dop_v1_[a-fA-F0-9]{64}|doo_v1_[a-fA-F0-9]{64}|dp\.(?:st|pt|ct|sa)\.[a-zA-Z0-9_-]{20,}|lin_api_[a-zA-Z0-9]{32,64}|ntn_[a-zA-Z0-9]{32,64}|secret_[a-zA-Z0-9]{32,64}|gsk_[a-zA-Z0-9]{32,64}|r8_[a-zA-Z0-9]{24,64}|tvly-[a-zA-Z0-9_-]{20,64}|fc-[a-fA-F0-9]{32}|dapi[a-fA-F0-9]{32}(?:-\d)?|re_[a-zA-Z0-9_]{20,64}|napi_[a-zA-Z0-9]{32,64}|[a-zA-Z0-9]{14}\.atlasv1\.[a-zA-Z0-9]{50,80}|pypi-[A-Za-z0-9_-]{16,128}|whsec_[A-Za-z0-9+/=_-]{24,128}|shpat_[a-fA-F0-9]{32}|shpss_[a-fA-F0-9]{32}|shpca_[a-fA-F0-9]{32}|shppa_[a-fA-F0-9]{32}|hvs\.[A-Za-z0-9_-]{24,128}|hvb\.[A-Za-z0-9_-]{24,128}|glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8}|glc_[A-Za-z0-9+/=_-]{20,128}|pscale_tkn_[A-Za-z0-9_.-]{20,64}|pscale_pw_[A-Za-z0-9_.-]{20,64}|pat[A-Za-z0-9]{14}\.[a-fA-F0-9]{64}|pat-(?:na1|eu1)-[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}|sq0atp-[A-Za-z0-9_-]{22,64}|sq0csp-[A-Za-z0-9_-]{32,64}|NRAK-[A-Z0-9]{27}|v1\.0-[a-fA-F0-9]{24}-[a-fA-F0-9]{16,160}|ATATT3xFfGF0[A-Za-z0-9+/=_-]{24,128}|tskey-(?:auth|api|client)-[A-Za-z0-9_-]{20,96}|sntry[su]_[A-Za-z0-9+/=_-]{32,160}|(?:pk|sk)\.eyJ1[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{16,}|key-[a-f0-9]{32}|[a-f0-9]{32}-us\d{1,2})\b/g, label: 'API_TOKEN' },
|
|
52
|
+
{ name: 'Twilio Account SID / API Key', regex: /\b(?:AC|SK)[a-fA-F0-9]{32}\b/g, label: 'API_TOKEN' },
|
|
53
|
+
{ name: 'Bcrypt / Argon2 Password Hash', regex: /(?<![A-Za-z0-9])(?:\$2[aby]\$\d{2}\$[./A-Za-z0-9=]{20,60}|\$argon2(?:id|i|d)\$[^\s"']{15,120})/g, label: 'SECRET' },
|
|
42
54
|
{ name: 'JSON Web Token (JWT)', regex: /\beyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/g, label: 'JWT_TOKEN' },
|
|
43
|
-
{ name: 'API Token/Key (GitHub/Slack/NPM)', regex: /\b(?:ghp|gho|ghu|ghs|ghr|glpat|npm|xox[baprs])[-_][A-Za-z0-9_]{10,}\b/g, label: 'API_TOKEN' },
|
|
44
|
-
{ name: 'Stripe API Key', regex: /\b(?:[rs]k)_(?:test|live)_[a-zA-Z0-9]{
|
|
55
|
+
{ name: 'API Token/Key (GitHub/Slack/NPM)', regex: /\b(?:ghp|gho|ghu|ghs|ghr|glpat|npm|xox[baprs])[-_][A-Za-z0-9_-]{10,}\b/g, label: 'API_TOKEN' },
|
|
56
|
+
{ name: 'Stripe API Key', regex: /\b(?:[rs]k)_(?:test|live)_[a-zA-Z0-9]{10,}\b/g, label: 'STRIPE_KEY' },
|
|
45
57
|
{ name: 'OpenAI Project API Key', regex: /\b(?:sk|pk)-(?:proj-)?[a-zA-Z0-9_-]{16,}\b/gi, label: 'OPENAI_KEY' },
|
|
58
|
+
{ name: 'Slack / Discord Incoming Webhook URL', regex: /\bhttps:\/\/(?:hooks\.slack\.com\/services\/T[A-Z0-9_]+\/B[A-Z0-9_]+\/[A-Za-z0-9_-]+|(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/\d{17,20}\/[A-Za-z0-9_-]{60,70})\b/gi, label: 'SECRET' },
|
|
59
|
+
{ name: 'Azure Connection String / SAS Token', regex: /(?:DefaultEndpointsProtocol=https?;AccountName=[^;\s"']+;AccountKey=[A-Za-z0-9+/=]{20,}(?:;EndpointSuffix=[^;\s"']+)?|Endpoint=sb:\/\/[^;\s"']+;SharedAccessKeyName=[^;\s"']+;SharedAccessKey=[A-Za-z0-9+/=]{16,}|\bSharedAccessSignature=sr=[^\s"']+&sig=[A-Za-z0-9%/+=]{16,}|\bAccountKey=[A-Za-z0-9+/=]{40,88}==)/gi, label: 'SECRET' },
|
|
46
60
|
{ name: 'Database Connection URI', regex: /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^\s"']+/gi, label: 'DB_URI' },
|
|
47
|
-
{ name: 'Embedded Credentials URI', regex: /\b(?:https?|s?ftp):\/\/[a-zA-Z0-9_.%+-]+:[^\s/@:"']+@[a-zA-Z0-9_.-]+(?::\d+)?(?:\/[^\s"']*)?/gi, label: 'SECRET' },
|
|
61
|
+
{ name: 'Embedded Credentials URI', regex: /\b(?:https?|hxxps?|s?ftp|fxp|wss?):\/\/[a-zA-Z0-9_.%+-]+:[^\s/@:"']+@[a-zA-Z0-9_.-]+(?:\[\.\][a-zA-Z0-9_.-]+|\(\.\)[a-zA-Z0-9_.-]+)*(?::\d+)?(?:\/[^\s"']*)?/gi, label: 'SECRET' },
|
|
48
62
|
{ name: 'URL Query Parameter Secret', regex: /(?<=[?&](?:api_key|apikey|access_token|client_secret|auth_token|token|secret|password|private_key)=)[^\s"'\x26#)>]{8,}/gi, label: 'SECRET' },
|
|
63
|
+
{ name: 'CLI Credential Flag / Curl User Auth', regex: /(?:\bcurl\b[^\r\n]*?\s(?:-u|--user)\s+["']?[A-Za-z0-9_.+-]+:[^\s"']+|\b(?:--password|--api-key|--apikey|--access-token|--auth-token|--client-secret|--secret-key|--private-key)(?:=|\s+)["']?[^\s"']{6,}["']?)/gi, label: 'SECRET' },
|
|
64
|
+
{ name: 'HTTP API Key Header', regex: /\b(?:X-API-Key|X-Amz-Security-Token|X-Functions-Key|X-Shopify-Access-Token|X-Auth-Token|Api-Key)\s*:\s*["']?[A-Za-z0-9_.\-~+/=]{12,}["']?/gi, label: 'SECRET' },
|
|
65
|
+
{ name: 'Docker Registry Auth / K8s Secret Base64', regex: /(?<=["']?(?:auth|identitytoken|registrytoken|\.dockerconfigjson)["']?\s*:\s*["'])[A-Za-z0-9+/=]{16,}(?=["'])/gi, label: 'SECRET' },
|
|
49
66
|
{ name: 'Bearer / Auth Header Token', regex: /(?<=\b(?:Bearer|Token)\s+)[a-zA-Z0-9_\-\.~+/=]{16,}\b/gi, label: 'SECRET' },
|
|
50
|
-
{ name: 'Database/API Secret', regex: /\b(DB|POSTGRES|REDIS|MYSQL|AWS|SECRET|PASSWORD|TOKEN|API|KEY)[A-Z0-9_]*\s*[:=]\s*[^ \t\r\n"']{8,}
|
|
67
|
+
{ name: 'Database/API Secret', regex: /\b(DB|POSTGRES|REDIS|MYSQL|AWS|SECRET|PASSWORD|TOKEN|API|KEY)[A-Z0-9_]*\s*[:=]\s*[^ \t\r\n"']{8,}/gi, label: 'SECRET' },
|
|
51
68
|
{ name: 'Private Cryptographic Key (PEM Block)', regex: /-----BEGIN (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----[\s\S]{10,8192}?-----END (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----/g, label: 'SECRET' },
|
|
52
69
|
{ name: 'Private Cryptographic Key (Header)', regex: /-----BEGIN (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----/g, label: 'SECRET' }
|
|
53
70
|
];
|
|
@@ -143,8 +160,8 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
143
160
|
frameworksSet.add('CCPA/CPRA');
|
|
144
161
|
}
|
|
145
162
|
if (types.includes('ID') || types.includes('SSN') || types.includes('PASSPORT') || types.includes('NATIONAL_ID')) {
|
|
146
|
-
frameworksSet.add('SOC 2
|
|
147
|
-
frameworksSet.add('ISO 27001
|
|
163
|
+
frameworksSet.add('SOC 2 controls');
|
|
164
|
+
frameworksSet.add('ISO 27001 A.8.11 controls');
|
|
148
165
|
}
|
|
149
166
|
if (types.some(t => ['CREDIT_CARD', 'BANK', 'IBAN', 'FINANCIAL', 'CARD', 'STRIPE_KEY', 'TAX_ID', 'EIN', 'W2', '1099', 'SALARY', 'WAGE'].includes(t))) {
|
|
150
167
|
frameworksSet.add('PCI DSS v4.0');
|
|
@@ -233,7 +250,7 @@ export function formatAuditReceipt(telemetry) {
|
|
|
233
250
|
|
|
234
251
|
return `> ๐ก๏ธ **PrivacyScrubber Audit Receipt**\n` +
|
|
235
252
|
`> * **Risk Level:** ${icon} ${telemetry.riskLevel}\n` +
|
|
236
|
-
`> * **
|
|
253
|
+
`> * **Framework References (informational):** ${telemetry.frameworksList.join(', ')}\n` +
|
|
237
254
|
`> * **Tokens Masked:** ${telemetry.totalCount} (${entitiesList})\n` +
|
|
238
255
|
`> * โญ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) | **SDK & Enterprise:** [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing)\n`;
|
|
239
256
|
}
|
|
@@ -1468,13 +1485,13 @@ export function buildStreamReplacer(tokenMap = {}, options = {}) {
|
|
|
1468
1485
|
|
|
1469
1486
|
for (const k of sortedKeys) {
|
|
1470
1487
|
const val = tokenMap[k];
|
|
1471
|
-
const inner = k.replace(/^\[|<|\{\{
|
|
1488
|
+
const inner = k.replace(/^\[|<|\{\{?|__|\]|>|\}\}?|__/g, '');
|
|
1472
1489
|
const match = inner.match(/^([A-Za-z_0-9]+?)[-_ ]?(\d+)$/);
|
|
1473
1490
|
|
|
1474
1491
|
if (match) {
|
|
1475
|
-
const label = escapeRegex(match[1]);
|
|
1492
|
+
const label = escapeRegex(match[1]).replace(/[_-]/g, '\\\\?[_-]');
|
|
1476
1493
|
const idx = match[2];
|
|
1477
|
-
const patternStr = '(?:\\[\\s*' + label + '[-_
|
|
1494
|
+
const patternStr = '(?:(?:\\[|\\\\u005[Bb]|%5[Bb]|&(?:amp;)?#0*91;|&(?:amp;)?#[xX]0*5[Bb];|&(?:amp;)?lbrack;|ใ|๏ผป|ใ|ใ|ใ)\\s*[*`~]*' + label + '[-_\\\\\\s]*0*' + idx + '[*`~]*[.,;:!?]?\\s*(?:\\]|\\\\u005[Dd]|%5[Dd]|&(?:amp;)?#0*93;|&(?:amp;)?#[xX]0*5[Dd];|&(?:amp;)?rbrack;|ใ|๏ผฝ|ใ|ใ|ใ)|<\\s*' + label + '[-_\\\\\\s]*0*' + idx + '[.,;:!?]?\\s*>|\\{\\{?\\s*' + label + '[-_\\\\\\s]*0*' + idx + '[.,;:!?]?\\s*\\}\\}?|__\\s*' + label + '[-_\\\\\\s]*0*' + idx + '\\s*__|\\b' + label + '[-_\\s]+0*' + idx + '\\b)';
|
|
1478
1495
|
patterns.push(patternStr);
|
|
1479
1496
|
const normKey = (match[1] + '_' + parseInt(idx, 10)).toUpperCase();
|
|
1480
1497
|
lookup.set(normKey, val);
|
|
@@ -1503,10 +1520,14 @@ export function buildStreamReplacer(tokenMap = {}, options = {}) {
|
|
|
1503
1520
|
} else if (lookup.has(coreMatch.toUpperCase())) {
|
|
1504
1521
|
restored = lookup.get(coreMatch.toUpperCase());
|
|
1505
1522
|
} else {
|
|
1506
|
-
const inner = coreMatch
|
|
1507
|
-
|
|
1523
|
+
const inner = coreMatch
|
|
1524
|
+
.replace(/^(?:\[|\\u005[Bb]|%5[Bb]|&(?:amp;)?#0*91;|&(?:amp;)?#[xX]0*5[Bb];|&(?:amp;)?lbrack;|ใ|๏ผป|ใ|ใ|ใ|<|\{\{?|__)|(?:\]|\\u005[Dd]|%5[Dd]|&(?:amp;)?#0*93;|&(?:amp;)?#[xX]0*5[Dd];|&(?:amp;)?rbrack;|ใ|๏ผฝ|ใ|ใ|ใ|>|\}\}?|__)$/gi, '')
|
|
1525
|
+
.replace(/^[*`~\s]+|[.,;:!?*`~\s]+$/g, '')
|
|
1526
|
+
.trim();
|
|
1527
|
+
const m = inner.match(/^([A-Za-z_0-9]+?)[-_\\\s]*(\d+)$/);
|
|
1508
1528
|
if (m) {
|
|
1509
|
-
const
|
|
1529
|
+
const cleanLabel = m[1].replace(/\\([_-])/g, '$1');
|
|
1530
|
+
const normKey = (cleanLabel + '_' + parseInt(m[2], 10)).toUpperCase();
|
|
1510
1531
|
if (lookup.has(normKey)) {
|
|
1511
1532
|
restored = lookup.get(normKey);
|
|
1512
1533
|
}
|
|
@@ -1612,18 +1633,17 @@ export class StreamingDetokenizer {
|
|
|
1612
1633
|
this.pendingBuffer += text;
|
|
1613
1634
|
|
|
1614
1635
|
// Candidate unclosed token at tail of buffer (inspect tail window up to 160 chars)
|
|
1615
|
-
// Matches incomplete tokens starting with [, <, {
|
|
1636
|
+
// Matches incomplete tokens starting with [, ใ, ๏ผป, <, {, __, %5B, \u005b, or HTML entity [ / [
|
|
1616
1637
|
const tailLen = 160;
|
|
1617
1638
|
const tail = this.pendingBuffer.length > tailLen ? this.pendingBuffer.slice(-tailLen) : this.pendingBuffer;
|
|
1618
1639
|
const offset = this.pendingBuffer.length > tailLen ? this.pendingBuffer.length - tailLen : 0;
|
|
1619
|
-
const m = tail.match(/(?:\[
|
|
1640
|
+
const m = tail.match(/(?:(?:\[|ใ|๏ผป|ใ|ใ|ใ|<|\{\{?|__?|%5[Bb]|\\u005[Bb]|&(?:amp;)?(?:#0*91;|#[xX]0*5[Bb];|lbrack;))[A-Za-z0-9_\-ะ-ะฏะฐ-ัะั *`~.\\]{0,128}(?:%5?|&(?:a(?:m(?:p(?:;)?)?)?)?(?:#(?:0*9?3?|[xX]0*5?[Dd]?)?|r(?:b(?:r(?:a(?:c(?:k)?)?)?)?)?)?)?|%[0-9A-Fa-f]?|\\(?:u(?:0(?:0(?:5)?)?)?)?|&(?:a(?:m(?:p(?:;)?)?)?)?(?:#(?:0*9?1?|[xX]0*5?)?|l(?:b(?:r(?:a(?:c(?:k)?)?)?)?)?)?)$/);
|
|
1620
1641
|
|
|
1621
1642
|
if (m) {
|
|
1622
1643
|
const candidate = m[0];
|
|
1623
|
-
//
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
if (isClosedUnderscoreToken && this.replacer(candidate) !== candidate) {
|
|
1644
|
+
// Closed double-underscore (__NAME_1__) or JSON Unicode (\u005bNAME_1\u005d) tokens match character class and can be emitted immediately.
|
|
1645
|
+
const isClosedUnderscoreOrUnicode = (candidate.length >= 4 && candidate.startsWith('__') && candidate.endsWith('__')) || /\\u005[Dd]/.test(candidate);
|
|
1646
|
+
if (isClosedUnderscoreOrUnicode && this.replacer(candidate) !== candidate) {
|
|
1627
1647
|
const toEmit = this.pendingBuffer;
|
|
1628
1648
|
this.pendingBuffer = '';
|
|
1629
1649
|
return this.replacer(toEmit);
|
|
@@ -1845,13 +1865,8 @@ export function withPrivacyScrubber(handler, options = {}) {
|
|
|
1845
1865
|
tokenMap = res.tokenMap;
|
|
1846
1866
|
telemetry = res.telemetry;
|
|
1847
1867
|
} else if (rawBody && typeof rawBody === 'object') {
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
try {
|
|
1851
|
-
scrubbedBody = JSON.parse(res.scrubbedText);
|
|
1852
|
-
} catch (_) {
|
|
1853
|
-
scrubbedBody = rawBody;
|
|
1854
|
-
}
|
|
1868
|
+
const res = sanitizeObject(rawBody, options);
|
|
1869
|
+
scrubbedBody = res.sanitized;
|
|
1855
1870
|
tokenMap = res.tokenMap;
|
|
1856
1871
|
telemetry = res.telemetry;
|
|
1857
1872
|
}
|
|
@@ -1862,7 +1877,8 @@ export function withPrivacyScrubber(handler, options = {}) {
|
|
|
1862
1877
|
scrubbedBody,
|
|
1863
1878
|
tokenMap,
|
|
1864
1879
|
telemetry,
|
|
1865
|
-
restore: (text) => restore(text, tokenMap),
|
|
1880
|
+
restore: (text) => (text && typeof text === 'object') ? restoreObject(text, tokenMap) : restore(text, tokenMap),
|
|
1881
|
+
restoreObject: (obj) => restoreObject(obj, tokenMap),
|
|
1866
1882
|
createStreamRestorer: () => createStreamTokenRestorer(tokenMap)
|
|
1867
1883
|
};
|
|
1868
1884
|
|
|
@@ -1880,15 +1896,13 @@ export function withPrivacyScrubber(handler, options = {}) {
|
|
|
1880
1896
|
export function createExpressMiddleware(options = {}) {
|
|
1881
1897
|
return function privacyScrubberExpress(req, res, next) {
|
|
1882
1898
|
if (req && req.body && typeof req.body === 'object') {
|
|
1883
|
-
const
|
|
1884
|
-
const { scrubbedText, tokenMap, telemetry } = sanitize(serialized, options);
|
|
1899
|
+
const { sanitized, tokenMap, telemetry } = sanitizeObject(req.body, options);
|
|
1885
1900
|
req.rawBody = req.body;
|
|
1886
|
-
|
|
1887
|
-
req.body = JSON.parse(scrubbedText);
|
|
1888
|
-
} catch (_) {}
|
|
1901
|
+
req.body = sanitized;
|
|
1889
1902
|
req.psTokenMap = tokenMap;
|
|
1890
1903
|
req.psTelemetry = telemetry;
|
|
1891
|
-
req.restore = (aiResponse) => restore(aiResponse, tokenMap);
|
|
1904
|
+
req.restore = (aiResponse) => (aiResponse && typeof aiResponse === 'object') ? restoreObject(aiResponse, tokenMap) : restore(aiResponse, tokenMap);
|
|
1905
|
+
req.restoreObject = (obj) => restoreObject(obj, tokenMap);
|
|
1892
1906
|
req.createStreamRestorer = () => createStreamTokenRestorer(tokenMap);
|
|
1893
1907
|
}
|
|
1894
1908
|
if (typeof next === 'function') next();
|
|
@@ -1905,7 +1919,7 @@ export function formatCefEvent(eventData = {}) {
|
|
|
1905
1919
|
const {
|
|
1906
1920
|
vendor = 'PrivacyScrubber',
|
|
1907
1921
|
product = 'ZTDS',
|
|
1908
|
-
version = '2.
|
|
1922
|
+
version = '2.4.0',
|
|
1909
1923
|
eventClassId = 'PII_REDACTION',
|
|
1910
1924
|
eventName = 'Zero-Trust PII Sanitization Executed',
|
|
1911
1925
|
riskLevel = 'LOW EXPOSURE',
|
|
@@ -1974,7 +1988,7 @@ export function formatSyslogEvent(eventData = {}) {
|
|
|
1974
1988
|
export function formatJsonlEvent(eventData = {}) {
|
|
1975
1989
|
const {
|
|
1976
1990
|
timestamp = new Date().toISOString(),
|
|
1977
|
-
version = '2.
|
|
1991
|
+
version = '2.4.0',
|
|
1978
1992
|
protocol = 'ZTDS',
|
|
1979
1993
|
event = 'sanitization',
|
|
1980
1994
|
riskLevel = 'LOW EXPOSURE',
|
|
@@ -1999,7 +2013,7 @@ export function formatJsonlEvent(eventData = {}) {
|
|
|
1999
2013
|
frameworks,
|
|
2000
2014
|
session_hash: sessionHash,
|
|
2001
2015
|
zero_egress: true,
|
|
2002
|
-
|
|
2016
|
+
processing_mode: "local in-memory (by design, not measured)",
|
|
2003
2017
|
...(organization && { organization }),
|
|
2004
2018
|
...(department && { department }),
|
|
2005
2019
|
...(executionMs && { execution_ms: executionMs }),
|
|
@@ -2091,13 +2105,7 @@ export function createAuditLogger(options = {}) {
|
|
|
2091
2105
|
telemetry = buildAuditTelemetry(tokenMap);
|
|
2092
2106
|
}
|
|
2093
2107
|
|
|
2094
|
-
|
|
2095
|
-
try {
|
|
2096
|
-
const cryptoMod = require('crypto');
|
|
2097
|
-
sessionHash = cryptoMod.createHash('sha256').update(JSON.stringify(tokenMap) + Date.now()).digest('hex');
|
|
2098
|
-
} catch {
|
|
2099
|
-
sessionHash = 'ztds_' + Math.random().toString(16).substring(2, 10);
|
|
2100
|
-
}
|
|
2108
|
+
const sessionHash = buildPsr2Receipt(tokenMap).receiptSha256;
|
|
2101
2109
|
|
|
2102
2110
|
const eventData = {
|
|
2103
2111
|
event: 'pii_sanitization',
|
|
@@ -2143,108 +2151,138 @@ export function createAuditLogger(options = {}) {
|
|
|
2143
2151
|
};
|
|
2144
2152
|
}
|
|
2145
2153
|
|
|
2154
|
+
// โโ PSR-2 Receipt Integrity (real SHA-256; parity with src/modules/receipt-integrity.js) โโ
|
|
2155
|
+
const PSR2_VERSION = 'PSR-2';
|
|
2156
|
+
const PSR2_STATEMENTS = {
|
|
2157
|
+
measured: [
|
|
2158
|
+
'Token Manifest SHA-256: digest of the sorted token labels held in this session (original values are never hashed or exported).',
|
|
2159
|
+
'Entity counts per type.',
|
|
2160
|
+
'Receipt SHA-256: digest of the canonical field string, so edits to this receipt are detectable.'
|
|
2161
|
+
],
|
|
2162
|
+
byDesign: [
|
|
2163
|
+
'The sanitization engine runs locally in process memory and does not transmit prompt content. This is an architectural property and is not measured by this receipt.',
|
|
2164
|
+
'Token-to-original mappings are held in volatile memory for the session. This receipt does not record their contents.'
|
|
2165
|
+
],
|
|
2166
|
+
limits: [
|
|
2167
|
+
'This receipt is not a digital signature and does not prove who generated it or when.',
|
|
2168
|
+
'No final output digest is recorded by this export (Output SHA-256: not recorded).',
|
|
2169
|
+
'Detection is pattern-based. Free-text narrative can contain indirect identifiers that remain after sanitization; review output before sharing.',
|
|
2170
|
+
'Regulatory references are informational technical-safeguard mappings, not a legal determination of compliance.'
|
|
2171
|
+
]
|
|
2172
|
+
};
|
|
2173
|
+
function psr2Sha256(input) {
|
|
2174
|
+
return require('crypto').createHash('sha256').update(String(input == null ? '' : input), 'utf8').digest('hex');
|
|
2175
|
+
}
|
|
2176
|
+
function buildPsr2Receipt(tokenMap, timestamp = new Date().toISOString()) {
|
|
2177
|
+
const labels = (tokenMap && typeof tokenMap === 'object' ? Object.keys(tokenMap) : []).slice().sort();
|
|
2178
|
+
const counts = {};
|
|
2179
|
+
for (const t of labels) {
|
|
2180
|
+
const m = t.match(/\[([A-Z_]+)_\d+\]/);
|
|
2181
|
+
const type = m ? m[1] : 'CUSTOM';
|
|
2182
|
+
counts[type] = (counts[type] || 0) + 1;
|
|
2183
|
+
}
|
|
2184
|
+
const entityCounts = Object.keys(counts).sort().map(k => `${k}=${counts[k]}`).join(',') || 'none';
|
|
2185
|
+
const manifestSha256 = psr2Sha256(labels.join('\n'));
|
|
2186
|
+
const canonical = [PSR2_VERSION, timestamp, 'none', manifestSha256, entityCounts, 'na'].join('|');
|
|
2187
|
+
return { version: PSR2_VERSION, timestamp, outputSha256: null, manifestSha256, entityCounts, tokensTotal: labels.length, tokensPresentInOutput: null, canonical, receiptSha256: psr2Sha256(canonical) };
|
|
2188
|
+
}
|
|
2189
|
+
function psr2Lines(r) {
|
|
2190
|
+
return [
|
|
2191
|
+
`Receipt Version: ${r.version}`,
|
|
2192
|
+
`Timestamp (UTC): ${r.timestamp}`,
|
|
2193
|
+
`Output SHA-256: not recorded`,
|
|
2194
|
+
`Token Manifest SHA-256: ${r.manifestSha256}`,
|
|
2195
|
+
`Entity Counts: ${r.entityCounts}`,
|
|
2196
|
+
`Tokens In Final Output: not recorded`,
|
|
2197
|
+
`Canonical: ${r.canonical}`,
|
|
2198
|
+
`Receipt SHA-256: ${r.receiptSha256}`
|
|
2199
|
+
];
|
|
2200
|
+
}
|
|
2201
|
+
function formatPsr2Report({ format, receipt, telemetry, company_name, department, utmMedium }) {
|
|
2202
|
+
const entitySummary = Object.entries(telemetry.entities).map(([t, count]) => `[${t}]: ${count}`).join(', ') || 'None (Clean)';
|
|
2203
|
+
const teamsUrl = `https://privacyscrubber.com/teams?tier=teams&utm_source=sdk_audit_report&utm_medium=${utmMedium}&utm_campaign=teams_upgrade`;
|
|
2204
|
+
const sdkUrl = `https://privacyscrubber.com/sdk/?tier=sdk&utm_source=sdk_audit_report&utm_medium=${utmMedium}&utm_campaign=sdk_pipeline`;
|
|
2205
|
+
if (format === 'json') {
|
|
2206
|
+
return JSON.stringify({
|
|
2207
|
+
protocol: 'Zero-Trust Data Sanitization (ZTDS)',
|
|
2208
|
+
receipt_version: receipt.version,
|
|
2209
|
+
company: company_name,
|
|
2210
|
+
department,
|
|
2211
|
+
timestamp: receipt.timestamp,
|
|
2212
|
+
output_sha256: null,
|
|
2213
|
+
token_manifest_sha256: receipt.manifestSha256,
|
|
2214
|
+
entity_counts: receipt.entityCounts,
|
|
2215
|
+
canonical: receipt.canonical,
|
|
2216
|
+
receipt_sha256: receipt.receiptSha256,
|
|
2217
|
+
risk_level: telemetry.riskLevel,
|
|
2218
|
+
frameworks_referenced: telemetry.frameworksList,
|
|
2219
|
+
total_masked_tokens: telemetry.totalCount,
|
|
2220
|
+
entities_breakdown: telemetry.entities,
|
|
2221
|
+
scope: { measured: PSR2_STATEMENTS.measured, by_design_not_measured: PSR2_STATEMENTS.byDesign, limits: PSR2_STATEMENTS.limits },
|
|
2222
|
+
verification_url: 'https://privacyscrubber.com/features/audit-receipt/#verify',
|
|
2223
|
+
licensing_and_governance: { teams_governance_url: teamsUrl, sdk_pipeline_url: sdkUrl }
|
|
2224
|
+
}, null, 2);
|
|
2225
|
+
}
|
|
2226
|
+
if (format === 'summary') {
|
|
2227
|
+
return `[PrivacyScrubber SHA-256 Receipt] ${receipt.version} | Receipt SHA-256: ${receipt.receiptSha256.substring(0, 16)}... | Organization: ${company_name} | Masked Tokens: ${telemetry.totalCount} | Risk: ${telemetry.riskLevel} | Frameworks (informational): ${telemetry.frameworksList.join(', ')}`;
|
|
2228
|
+
}
|
|
2229
|
+
return `# Zero-Trust Data Sanitization Receipt
|
|
2230
|
+
**Organization:** ${company_name} (${department})
|
|
2231
|
+
**Timestamp:** ${receipt.timestamp}
|
|
2232
|
+
**Receipt Format:** ${receipt.version} (SHA-256 digests; not a digital signature)
|
|
2233
|
+
|
|
2234
|
+
---
|
|
2235
|
+
|
|
2236
|
+
### Sanitization Metrics & Risk Assessment
|
|
2237
|
+
* **Overall Risk Rating:** **${telemetry.riskLevel}**
|
|
2238
|
+
* **Total Sensitive Entities Masked:** \`${telemetry.totalCount}\`
|
|
2239
|
+
* **Entity Breakdown:** ${entitySummary}
|
|
2240
|
+
|
|
2241
|
+
### Informational Framework References
|
|
2242
|
+
${telemetry.frameworksList.map(f => `- ${f}`).join('\n')}
|
|
2243
|
+
|
|
2244
|
+
These are technical-safeguard mappings, not a legal determination of compliance.
|
|
2245
|
+
|
|
2246
|
+
### Receipt Integrity (${receipt.version})
|
|
2247
|
+
\`\`\`text
|
|
2248
|
+
${psr2Lines(receipt).join('\n')}
|
|
2249
|
+
\`\`\`
|
|
2250
|
+
|
|
2251
|
+
### Scope of This Receipt
|
|
2252
|
+
**Measured:** ${PSR2_STATEMENTS.measured.join(' ')}
|
|
2253
|
+
|
|
2254
|
+
**Stated by design, not measured:** ${PSR2_STATEMENTS.byDesign.join(' ')}
|
|
2255
|
+
|
|
2256
|
+
**Limits:** ${PSR2_STATEMENTS.limits.join(' ')}
|
|
2257
|
+
|
|
2258
|
+
### Enterprise & Team Governance
|
|
2259
|
+
* **Team Governance & Encrypted Handoff:** Centrally deploy redaction rules and encrypted session handoff with **PrivacyScrubber TEAMS** ($99/mo flat): [privacyscrubber.com/teams](${teamsUrl})
|
|
2260
|
+
* **Backend Microservices & RAG Pipelines:** Integrate in-memory sanitization (<1ms) into your Node.js/Python infrastructure with **Developer SDK** ($299/mo): [privacyscrubber.com/sdk](${sdkUrl})
|
|
2261
|
+
|
|
2262
|
+
*Generated locally by PrivacyScrubber Core SDK*
|
|
2263
|
+
*Verify locally (paste this receipt): https://privacyscrubber.com/features/audit-receipt/#verify*`;
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2146
2266
|
export function generateAuditReport(sessionMap = {}, options = {}) {
|
|
2147
2267
|
const { companyName = 'PrivacyScrubber Client', department = 'SecOps / Compliance', format = 'markdown' } = options;
|
|
2148
2268
|
const telemetry = buildAuditTelemetry(sessionMap);
|
|
2149
|
-
|
|
2150
|
-
let sessionHash = '';
|
|
2151
|
-
try {
|
|
2152
|
-
const cryptoMod = require('crypto');
|
|
2153
|
-
sessionHash = cryptoMod.createHash('sha256').update(JSON.stringify(sessionMap) + Date.now().toString()).digest('hex');
|
|
2154
|
-
} catch {
|
|
2155
|
-
sessionHash = Array.from(crypto.getRandomValues(new Uint8Array(16))).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
2269
|
const timestamp = new Date().toISOString();
|
|
2159
|
-
const
|
|
2270
|
+
const receipt = buildPsr2Receipt(sessionMap, timestamp);
|
|
2271
|
+
const sessionHash = receipt.receiptSha256;
|
|
2160
2272
|
|
|
2161
2273
|
if (format === 'cef') {
|
|
2162
|
-
return formatCefEvent({
|
|
2163
|
-
organization: companyName,
|
|
2164
|
-
department: department,
|
|
2165
|
-
timestamp,
|
|
2166
|
-
sessionHash,
|
|
2167
|
-
riskLevel: telemetry.riskLevel,
|
|
2168
|
-
tokensMasked: telemetry.totalCount,
|
|
2169
|
-
entities: telemetry.entities,
|
|
2170
|
-
frameworks: telemetry.frameworksList
|
|
2171
|
-
});
|
|
2274
|
+
return formatCefEvent({ organization: companyName, department, timestamp, sessionHash, riskLevel: telemetry.riskLevel, tokensMasked: telemetry.totalCount, entities: telemetry.entities, frameworks: telemetry.frameworksList });
|
|
2172
2275
|
}
|
|
2173
|
-
|
|
2174
2276
|
if (format === 'syslog') {
|
|
2175
|
-
return formatSyslogEvent({
|
|
2176
|
-
organization: companyName,
|
|
2177
|
-
department: department,
|
|
2178
|
-
timestamp,
|
|
2179
|
-
sessionHash,
|
|
2180
|
-
riskLevel: telemetry.riskLevel,
|
|
2181
|
-
tokensMasked: telemetry.totalCount,
|
|
2182
|
-
entities: telemetry.entities,
|
|
2183
|
-
frameworks: telemetry.frameworksList
|
|
2184
|
-
});
|
|
2277
|
+
return formatSyslogEvent({ organization: companyName, department, timestamp, sessionHash, riskLevel: telemetry.riskLevel, tokensMasked: telemetry.totalCount, entities: telemetry.entities, frameworks: telemetry.frameworksList });
|
|
2185
2278
|
}
|
|
2186
|
-
|
|
2187
2279
|
if (format === 'jsonl') {
|
|
2188
|
-
return formatJsonlEvent({
|
|
2189
|
-
organization: companyName,
|
|
2190
|
-
department: department,
|
|
2191
|
-
timestamp,
|
|
2192
|
-
sessionHash,
|
|
2193
|
-
riskLevel: telemetry.riskLevel,
|
|
2194
|
-
tokensMasked: telemetry.totalCount,
|
|
2195
|
-
entities: telemetry.entities,
|
|
2196
|
-
frameworks: telemetry.frameworksList
|
|
2197
|
-
});
|
|
2280
|
+
return formatJsonlEvent({ organization: companyName, department, timestamp, sessionHash, riskLevel: telemetry.riskLevel, tokensMasked: telemetry.totalCount, entities: telemetry.entities, frameworks: telemetry.frameworksList });
|
|
2198
2281
|
}
|
|
2199
|
-
|
|
2200
2282
|
if (format === 'json') {
|
|
2201
|
-
return {
|
|
2202
|
-
protocol: "Zero-Trust Data Sanitization (ZTDS)",
|
|
2203
|
-
certificateId: `ZTDS-CERT-${sessionHash.substring(0, 16).toUpperCase()}`,
|
|
2204
|
-
organization: companyName,
|
|
2205
|
-
department: department,
|
|
2206
|
-
timestamp: timestamp,
|
|
2207
|
-
sessionHash: sessionHash,
|
|
2208
|
-
verificationMode: "100% Offline (In-Memory RAM)",
|
|
2209
|
-
complianceStatus: "VERIFIED PASS",
|
|
2210
|
-
riskLevel: telemetry.riskLevel,
|
|
2211
|
-
frameworksEnforced: telemetry.frameworksList,
|
|
2212
|
-
totalMaskedTokens: telemetry.totalCount,
|
|
2213
|
-
entitiesBreakdown: telemetry.entities,
|
|
2214
|
-
zeroEgressVerified: true,
|
|
2215
|
-
verificationUrl: `https://privacyscrubber.com/features/audit-receipt/#verify?hash=${sessionHash.substring(0, 16)}`
|
|
2216
|
-
};
|
|
2283
|
+
return JSON.parse(formatPsr2Report({ format: 'json', receipt, telemetry, company_name: companyName, department, utmMedium: 'json' }));
|
|
2217
2284
|
}
|
|
2218
|
-
|
|
2219
|
-
if (format === 'summary') {
|
|
2220
|
-
return `[PrivacyScrubber Compliance Certificate] ID: ZTDS-${sessionHash.substring(0, 8).toUpperCase()} | Organization: ${companyName} | Masked Tokens: ${telemetry.totalCount} | Risk: ${telemetry.riskLevel} | Frameworks: ${telemetry.frameworksList.join(', ')} | Status: PASS (100% Air-Gapped)`;
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
|
-
return `# ๐ก๏ธ Zero-Trust Data Sanitization Compliance Certificate
|
|
2224
|
-
**Certificate ID:** \`ZTDS-CERT-${sessionHash.substring(0, 16).toUpperCase()}\`
|
|
2225
|
-
**Organization:** ${companyName} (${department})
|
|
2226
|
-
**Timestamp:** ${timestamp}
|
|
2227
|
-
**Verification Mode:** 100% Local In-Memory Processing (Air-Gapped)
|
|
2228
|
-
**Status:** **VERIFIED PASS** (Zero Network Egress)
|
|
2229
|
-
|
|
2230
|
-
---
|
|
2231
|
-
|
|
2232
|
-
### ๐ Sanitization Metrics & Risk Assessment
|
|
2233
|
-
* **Overall Risk Rating:** **${telemetry.riskLevel}**
|
|
2234
|
-
* **Total Sensitive Entities Masked:** \`${telemetry.totalCount}\`
|
|
2235
|
-
* **Entity Breakdown:** ${entitySummary}
|
|
2236
|
-
* **Network Data Transmitted:** \`0.00 KB (Zero-Trust Local RAM)\`
|
|
2237
|
-
|
|
2238
|
-
### ๐ Regulatory Frameworks Enforced
|
|
2239
|
-
${telemetry.frameworksList.map(f => `- **${f}**`).join('\n')}
|
|
2240
|
-
|
|
2241
|
-
### ๐ CISO Compliance Declaration
|
|
2242
|
-
1. **EU AI Act (Art. 50) & GDPR (Art. 25 & 32):** Data minimization and local pseudonymization enforced prior to model interaction.
|
|
2243
|
-
2. **HIPAA Safe Harbor (ยง164.514) / SOC 2 Type II:** All direct and indirect identifiers sanitized locally without cloud processor liability.
|
|
2244
|
-
3. **Cryptographic Verification:** Tamper-evident session verification hash: \`${sessionHash}\`
|
|
2245
|
-
|
|
2246
|
-
*Certified Offline by PrivacyScrubber Core SDK*
|
|
2247
|
-
*Verify at: https://privacyscrubber.com/features/audit-receipt/*`;
|
|
2285
|
+
return formatPsr2Report({ format: format === 'summary' ? 'summary' : 'markdown', receipt, telemetry, company_name: companyName, department, utmMedium: 'markdown' });
|
|
2248
2286
|
}
|
|
2249
2287
|
|
|
2250
2288
|
/**
|
|
@@ -2486,7 +2524,46 @@ export function createGuardedTools(engineOrOptions = {}, toolConfig = {}) {
|
|
|
2486
2524
|
throw new Error(`[guard_read_file] Path is a directory, not a file: ${resolved}`);
|
|
2487
2525
|
}
|
|
2488
2526
|
|
|
2489
|
-
const
|
|
2527
|
+
const rawBuf = fs.readFileSync(resolved);
|
|
2528
|
+
const checkLen = Math.min(rawBuf.length, 8000);
|
|
2529
|
+
for (let i = 0; i < checkLen; i++) {
|
|
2530
|
+
if (rawBuf[i] === 0) {
|
|
2531
|
+
throw new Error(`[guard_read_file] Binary file detected (${path.basename(resolved)}). Only text, code, and MIME files are supported.`);
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
let raw = rawBuf.toString('utf8');
|
|
2535
|
+
const ext = path.extname(resolved).toLowerCase();
|
|
2536
|
+
if (ext === '.eml') {
|
|
2537
|
+
const decodeQpUtf8 = (str) =>
|
|
2538
|
+
str.replace(/=\r?\n/g, '').replace(/(?:=[0-9A-Fa-f]{2})+/g, (seq) => {
|
|
2539
|
+
try {
|
|
2540
|
+
const bytes = Buffer.alloc(seq.length / 3);
|
|
2541
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2542
|
+
bytes[i] = parseInt(seq.slice(i * 3 + 1, i * 3 + 3), 16);
|
|
2543
|
+
}
|
|
2544
|
+
return bytes.toString('utf8');
|
|
2545
|
+
} catch (_) { return seq; }
|
|
2546
|
+
});
|
|
2547
|
+
raw = raw.replace(/=\?([^?]+)\?([BQbq])\?([^?]+)\?=/g, (full, _cs, enc, payload) => {
|
|
2548
|
+
try {
|
|
2549
|
+
return enc.toUpperCase() === 'B'
|
|
2550
|
+
? Buffer.from(payload.replace(/\s+/g, ''), 'base64').toString('utf8')
|
|
2551
|
+
: decodeQpUtf8(payload.replace(/_/g, ' '));
|
|
2552
|
+
} catch (_) { return full; }
|
|
2553
|
+
});
|
|
2554
|
+
raw = decodeQpUtf8(raw);
|
|
2555
|
+
} else if (ext === '.rtf') {
|
|
2556
|
+
raw = raw
|
|
2557
|
+
.replace(/\\par\b|\\line\b/gi, '\n')
|
|
2558
|
+
.replace(/\\'([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
2559
|
+
.replace(/\\u(-?\d+)\??/g, (_, code) => {
|
|
2560
|
+
const n = parseInt(code, 10);
|
|
2561
|
+
return String.fromCharCode(n < 0 ? n + 65536 : n);
|
|
2562
|
+
})
|
|
2563
|
+
.replace(/\\[a-zA-Z]+(?:-?\d+)?[ ]?/g, ' ')
|
|
2564
|
+
.replace(/[{}]/g, ' ')
|
|
2565
|
+
.replace(/[ \t]+/g, ' ');
|
|
2566
|
+
}
|
|
2490
2567
|
const lines = raw.split(/\r?\n/);
|
|
2491
2568
|
const capped = lines.slice(0, maxLines).join('\n');
|
|
2492
2569
|
const wasTruncated = lines.length > maxLines;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@privacyscrubber/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "In-process consumer privacy defense and pre-emptive PII interception SDK for Node.js, Next.js, and AI pipelines. Zero data loss, zero third parties, sub-millisecond in-memory execution.",
|
|
5
5
|
"main": "index.cjs",
|
|
6
6
|
"module": "index.js",
|
|
@@ -25,6 +25,12 @@
|
|
|
25
25
|
"import": "./fastmcp.js",
|
|
26
26
|
"require": "./fastmcp.cjs",
|
|
27
27
|
"default": "./fastmcp.js"
|
|
28
|
+
},
|
|
29
|
+
"./vercel-ai": {
|
|
30
|
+
"types": "./vercel-ai.d.ts",
|
|
31
|
+
"import": "./vercel-ai.js",
|
|
32
|
+
"require": "./vercel-ai.cjs",
|
|
33
|
+
"default": "./vercel-ai.js"
|
|
28
34
|
}
|
|
29
35
|
},
|
|
30
36
|
"publishConfig": {
|
|
@@ -40,8 +46,12 @@
|
|
|
40
46
|
"fastmcp.js",
|
|
41
47
|
"fastmcp.cjs",
|
|
42
48
|
"fastmcp.d.ts",
|
|
49
|
+
"vercel-ai.js",
|
|
50
|
+
"vercel-ai.cjs",
|
|
51
|
+
"vercel-ai.d.ts",
|
|
43
52
|
"index.d.ts",
|
|
44
53
|
"cli.js",
|
|
54
|
+
"benchmark.js",
|
|
45
55
|
"polyfill.js",
|
|
46
56
|
"ps-license-manager.js",
|
|
47
57
|
"ps-pii-engine.cjs",
|
|
@@ -58,8 +68,10 @@
|
|
|
58
68
|
"ps-git-guard": "cli.js"
|
|
59
69
|
},
|
|
60
70
|
"scripts": {
|
|
61
|
-
"build": "node ../scripts/build-sdk.js",
|
|
62
|
-
"test": "node test-sdk.js",
|
|
71
|
+
"build": "node ../scripts/build-sdk.js --keep-obfuscated",
|
|
72
|
+
"test": "node test-sdk.js && node test-vercel-ai.js",
|
|
73
|
+
"test:python": "python3 examples/python-bridge/test_python_bridge.py",
|
|
74
|
+
"benchmark": "node benchmark.js",
|
|
63
75
|
"prepublishOnly": "npm run build && npm test"
|
|
64
76
|
},
|
|
65
77
|
"keywords": [
|