@privacyscrubber/sdk 2.4.3 → 2.4.5
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/cli.js +1 -1
- package/examples/python-bridge/bridge_daemon.cjs +1 -1
- package/examples/python-bridge/fastapi_example.py +1 -1
- package/examples/python-bridge/privacyscrubber/__init__.py +1 -1
- package/examples/python-bridge/privacyscrubber/bridge_daemon.cjs +1 -1
- package/examples/python-bridge/privacyscrubber/cli.py +1 -1
- package/examples/python-bridge/pyproject.toml +1 -1
- package/examples/python-bridge/setup.py +1 -1
- package/examples/python-bridge/test_python_bridge.py +1 -1
- package/index.cjs +43 -7
- package/index.js +43 -7
- package/package.json +1 -1
- package/ps-license-manager.js +1 -1
- package/ps-pii-engine.cjs +1 -1
- package/sarif.cjs +2 -2
- package/scrubber-core.cjs +1 -1
package/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "privacyscrubber"
|
|
7
|
-
version = "2.4.
|
|
7
|
+
version = "2.4.5"
|
|
8
8
|
description = "Zero-Trust Data Sanitization & In-Memory PII Shield for Python AI Pipelines (ZTDS RFC v1.0)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -13,7 +13,7 @@ with open(os.path.join(here, "README.md"), encoding="utf-8") as f:
|
|
|
13
13
|
|
|
14
14
|
setup(
|
|
15
15
|
name="privacyscrubber",
|
|
16
|
-
version="2.4.
|
|
16
|
+
version="2.4.5",
|
|
17
17
|
description="Zero-Trust Data Sanitization & In-Memory PII Shield for Python AI Pipelines (ZTDS RFC v1.0)",
|
|
18
18
|
long_description=long_description,
|
|
19
19
|
long_description_content_type="text/markdown",
|
package/index.cjs
CHANGED
|
@@ -540,8 +540,17 @@ function sanitize(text, options = {}) {
|
|
|
540
540
|
wasTruncated = true;
|
|
541
541
|
}
|
|
542
542
|
|
|
543
|
+
// Filter user-supplied custom rules against catastrophic backtracking (PS-VDP-2026-10-002 parity)
|
|
544
|
+
const nestedQuantifierRegex = /\([^)]*(\+|\*|\{\d+,?\d*\})\)[+*]|\([^)]*(\+|\*|\{\d+,?\d*\})\)\{\d+,?\d*\}|\([^)]*\|[^)]*\)(?:[+*]|\{\d+,?\d*\})/;
|
|
545
|
+
const safeCustomRules = (Array.isArray(customRules) ? customRules : []).filter(r => {
|
|
546
|
+
const pat = typeof r === 'string' ? r : (r && (r.pattern || (r.regex && r.regex.source)));
|
|
547
|
+
if (!pat || typeof pat !== 'string' || pat.length > 500) return false;
|
|
548
|
+
if (nestedQuantifierRegex.test(pat)) return false;
|
|
549
|
+
return true;
|
|
550
|
+
});
|
|
551
|
+
|
|
543
552
|
// Auto-inject DevOps Secrets rules when in Dev profile or when detectSecrets is requested
|
|
544
|
-
const activeRules = [...
|
|
553
|
+
const activeRules = [...safeCustomRules];
|
|
545
554
|
if (profile.toLowerCase() === 'dev' || detectSecrets === true) {
|
|
546
555
|
DEVOPS_SECRETS_DETECTOR.forEach(rule => {
|
|
547
556
|
activeRules.push({
|
|
@@ -1952,7 +1961,7 @@ function formatCefEvent(eventData = {}) {
|
|
|
1952
1961
|
const {
|
|
1953
1962
|
vendor = 'PrivacyScrubber',
|
|
1954
1963
|
product = 'ZTDS',
|
|
1955
|
-
version = '2.4.
|
|
1964
|
+
version = '2.4.5',
|
|
1956
1965
|
eventClassId = 'PII_REDACTION',
|
|
1957
1966
|
eventName = 'Zero-Trust PII Sanitization Executed',
|
|
1958
1967
|
riskLevel = 'LOW EXPOSURE',
|
|
@@ -2021,7 +2030,7 @@ function formatSyslogEvent(eventData = {}) {
|
|
|
2021
2030
|
function formatJsonlEvent(eventData = {}) {
|
|
2022
2031
|
const {
|
|
2023
2032
|
timestamp = new Date().toISOString(),
|
|
2024
|
-
version = '2.4.
|
|
2033
|
+
version = '2.4.5',
|
|
2025
2034
|
protocol = 'ZTDS',
|
|
2026
2035
|
event = 'sanitization',
|
|
2027
2036
|
riskLevel = 'LOW EXPOSURE',
|
|
@@ -2465,6 +2474,32 @@ function createGuardedTools(engineOrOptions = {}, toolConfig = {}) {
|
|
|
2465
2474
|
|
|
2466
2475
|
const baseCwd = toolConfig.cwd || process.cwd();
|
|
2467
2476
|
const defaultProfile = toolConfig.profile || engine.defaultProfile || 'Dev';
|
|
2477
|
+
const allowOutsideWorkspace = toolConfig.allowOutsideWorkspace === true;
|
|
2478
|
+
|
|
2479
|
+
function resolveSafeToolPath(inputPath, toolName, paramName = 'filePath') {
|
|
2480
|
+
const workspaceRoot = fs.existsSync(baseCwd) ? fs.realpathSync(baseCwd) : path.resolve(baseCwd);
|
|
2481
|
+
const resolvedPath = path.resolve(workspaceRoot, inputPath);
|
|
2482
|
+
if (allowOutsideWorkspace) return resolvedPath;
|
|
2483
|
+
|
|
2484
|
+
const relToRoot = path.relative(workspaceRoot, resolvedPath);
|
|
2485
|
+
if (relToRoot.startsWith('..') || path.isAbsolute(relToRoot)) {
|
|
2486
|
+
throw new Error(`[${toolName}] Security policy violation — target '${paramName}' resolves outside the active workspace directory (path traversal blocked).`);
|
|
2487
|
+
}
|
|
2488
|
+
let checkCursor = resolvedPath;
|
|
2489
|
+
while (!fs.existsSync(checkCursor)) {
|
|
2490
|
+
const parentCursor = path.dirname(checkCursor);
|
|
2491
|
+
if (parentCursor === checkCursor) break;
|
|
2492
|
+
checkCursor = parentCursor;
|
|
2493
|
+
}
|
|
2494
|
+
if (fs.existsSync(checkCursor)) {
|
|
2495
|
+
const realCheck = fs.realpathSync(checkCursor);
|
|
2496
|
+
const relReal = path.relative(workspaceRoot, realCheck);
|
|
2497
|
+
if (relReal.startsWith('..') || path.isAbsolute(relReal)) {
|
|
2498
|
+
throw new Error(`[${toolName}] Security policy violation — target path symlink escapes the active workspace directory.`);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
return resolvedPath;
|
|
2502
|
+
}
|
|
2468
2503
|
|
|
2469
2504
|
const tools = {
|
|
2470
2505
|
guardExec: {
|
|
@@ -2484,7 +2519,7 @@ function createGuardedTools(engineOrOptions = {}, toolConfig = {}) {
|
|
|
2484
2519
|
if (!command || typeof command !== 'string') {
|
|
2485
2520
|
throw new Error('[guard_exec] Missing or invalid parameter: command must be a string.');
|
|
2486
2521
|
}
|
|
2487
|
-
const execCwd = cwd ?
|
|
2522
|
+
const execCwd = cwd ? resolveSafeToolPath(cwd, 'guard_exec', 'cwd') : baseCwd;
|
|
2488
2523
|
let stdout = '';
|
|
2489
2524
|
let stderr = '';
|
|
2490
2525
|
let exitCode = 0;
|
|
@@ -2548,7 +2583,7 @@ function createGuardedTools(engineOrOptions = {}, toolConfig = {}) {
|
|
|
2548
2583
|
if (!filePath || typeof filePath !== 'string') {
|
|
2549
2584
|
throw new Error('[guard_read_file] Missing or invalid parameter: filePath must be a string.');
|
|
2550
2585
|
}
|
|
2551
|
-
const resolved =
|
|
2586
|
+
const resolved = resolveSafeToolPath(filePath, 'guard_read_file', 'filePath');
|
|
2552
2587
|
if (!fs.existsSync(resolved)) {
|
|
2553
2588
|
throw new Error(`[guard_read_file] File not found: ${resolved}`);
|
|
2554
2589
|
}
|
|
@@ -2639,7 +2674,8 @@ function createGuardedTools(engineOrOptions = {}, toolConfig = {}) {
|
|
|
2639
2674
|
required: ['filePath', 'content']
|
|
2640
2675
|
},
|
|
2641
2676
|
execute: async ({ filePath, content, createBackup = true } = {}) => {
|
|
2642
|
-
const
|
|
2677
|
+
const safeFilePath = resolveSafeToolPath(filePath, 'guard_apply_patch', 'filePath');
|
|
2678
|
+
const patchResult = applyPatch(safeFilePath, content, {
|
|
2643
2679
|
tokenMap: engine.sessionMap,
|
|
2644
2680
|
createBackup
|
|
2645
2681
|
});
|
|
@@ -2670,7 +2706,7 @@ function createGuardedTools(engineOrOptions = {}, toolConfig = {}) {
|
|
|
2670
2706
|
required: []
|
|
2671
2707
|
},
|
|
2672
2708
|
execute: async ({ staged = false, cwd, profile = defaultProfile } = {}) => {
|
|
2673
|
-
const repoCwd = cwd ?
|
|
2709
|
+
const repoCwd = cwd ? resolveSafeToolPath(cwd, 'guard_git_diff', 'cwd') : baseCwd;
|
|
2674
2710
|
const gitCmd = staged ? 'git diff --cached' : 'git diff';
|
|
2675
2711
|
let diffOutput = '';
|
|
2676
2712
|
|
package/index.js
CHANGED
|
@@ -542,8 +542,17 @@ export function sanitize(text, options = {}) {
|
|
|
542
542
|
wasTruncated = true;
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
+
// Filter user-supplied custom rules against catastrophic backtracking (PS-VDP-2026-10-002 parity)
|
|
546
|
+
const nestedQuantifierRegex = /\([^)]*(\+|\*|\{\d+,?\d*\})\)[+*]|\([^)]*(\+|\*|\{\d+,?\d*\})\)\{\d+,?\d*\}|\([^)]*\|[^)]*\)(?:[+*]|\{\d+,?\d*\})/;
|
|
547
|
+
const safeCustomRules = (Array.isArray(customRules) ? customRules : []).filter(r => {
|
|
548
|
+
const pat = typeof r === 'string' ? r : (r && (r.pattern || (r.regex && r.regex.source)));
|
|
549
|
+
if (!pat || typeof pat !== 'string' || pat.length > 500) return false;
|
|
550
|
+
if (nestedQuantifierRegex.test(pat)) return false;
|
|
551
|
+
return true;
|
|
552
|
+
});
|
|
553
|
+
|
|
545
554
|
// Auto-inject DevOps Secrets rules when in Dev profile or when detectSecrets is requested
|
|
546
|
-
const activeRules = [...
|
|
555
|
+
const activeRules = [...safeCustomRules];
|
|
547
556
|
if (profile.toLowerCase() === 'dev' || detectSecrets === true) {
|
|
548
557
|
DEVOPS_SECRETS_DETECTOR.forEach(rule => {
|
|
549
558
|
activeRules.push({
|
|
@@ -1954,7 +1963,7 @@ export function formatCefEvent(eventData = {}) {
|
|
|
1954
1963
|
const {
|
|
1955
1964
|
vendor = 'PrivacyScrubber',
|
|
1956
1965
|
product = 'ZTDS',
|
|
1957
|
-
version = '2.4.
|
|
1966
|
+
version = '2.4.5',
|
|
1958
1967
|
eventClassId = 'PII_REDACTION',
|
|
1959
1968
|
eventName = 'Zero-Trust PII Sanitization Executed',
|
|
1960
1969
|
riskLevel = 'LOW EXPOSURE',
|
|
@@ -2023,7 +2032,7 @@ export function formatSyslogEvent(eventData = {}) {
|
|
|
2023
2032
|
export function formatJsonlEvent(eventData = {}) {
|
|
2024
2033
|
const {
|
|
2025
2034
|
timestamp = new Date().toISOString(),
|
|
2026
|
-
version = '2.4.
|
|
2035
|
+
version = '2.4.5',
|
|
2027
2036
|
protocol = 'ZTDS',
|
|
2028
2037
|
event = 'sanitization',
|
|
2029
2038
|
riskLevel = 'LOW EXPOSURE',
|
|
@@ -2467,6 +2476,32 @@ export function createGuardedTools(engineOrOptions = {}, toolConfig = {}) {
|
|
|
2467
2476
|
|
|
2468
2477
|
const baseCwd = toolConfig.cwd || process.cwd();
|
|
2469
2478
|
const defaultProfile = toolConfig.profile || engine.defaultProfile || 'Dev';
|
|
2479
|
+
const allowOutsideWorkspace = toolConfig.allowOutsideWorkspace === true;
|
|
2480
|
+
|
|
2481
|
+
function resolveSafeToolPath(inputPath, toolName, paramName = 'filePath') {
|
|
2482
|
+
const workspaceRoot = fs.existsSync(baseCwd) ? fs.realpathSync(baseCwd) : path.resolve(baseCwd);
|
|
2483
|
+
const resolvedPath = path.resolve(workspaceRoot, inputPath);
|
|
2484
|
+
if (allowOutsideWorkspace) return resolvedPath;
|
|
2485
|
+
|
|
2486
|
+
const relToRoot = path.relative(workspaceRoot, resolvedPath);
|
|
2487
|
+
if (relToRoot.startsWith('..') || path.isAbsolute(relToRoot)) {
|
|
2488
|
+
throw new Error(`[${toolName}] Security policy violation — target '${paramName}' resolves outside the active workspace directory (path traversal blocked).`);
|
|
2489
|
+
}
|
|
2490
|
+
let checkCursor = resolvedPath;
|
|
2491
|
+
while (!fs.existsSync(checkCursor)) {
|
|
2492
|
+
const parentCursor = path.dirname(checkCursor);
|
|
2493
|
+
if (parentCursor === checkCursor) break;
|
|
2494
|
+
checkCursor = parentCursor;
|
|
2495
|
+
}
|
|
2496
|
+
if (fs.existsSync(checkCursor)) {
|
|
2497
|
+
const realCheck = fs.realpathSync(checkCursor);
|
|
2498
|
+
const relReal = path.relative(workspaceRoot, realCheck);
|
|
2499
|
+
if (relReal.startsWith('..') || path.isAbsolute(relReal)) {
|
|
2500
|
+
throw new Error(`[${toolName}] Security policy violation — target path symlink escapes the active workspace directory.`);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
return resolvedPath;
|
|
2504
|
+
}
|
|
2470
2505
|
|
|
2471
2506
|
const tools = {
|
|
2472
2507
|
guardExec: {
|
|
@@ -2486,7 +2521,7 @@ export function createGuardedTools(engineOrOptions = {}, toolConfig = {}) {
|
|
|
2486
2521
|
if (!command || typeof command !== 'string') {
|
|
2487
2522
|
throw new Error('[guard_exec] Missing or invalid parameter: command must be a string.');
|
|
2488
2523
|
}
|
|
2489
|
-
const execCwd = cwd ?
|
|
2524
|
+
const execCwd = cwd ? resolveSafeToolPath(cwd, 'guard_exec', 'cwd') : baseCwd;
|
|
2490
2525
|
let stdout = '';
|
|
2491
2526
|
let stderr = '';
|
|
2492
2527
|
let exitCode = 0;
|
|
@@ -2550,7 +2585,7 @@ export function createGuardedTools(engineOrOptions = {}, toolConfig = {}) {
|
|
|
2550
2585
|
if (!filePath || typeof filePath !== 'string') {
|
|
2551
2586
|
throw new Error('[guard_read_file] Missing or invalid parameter: filePath must be a string.');
|
|
2552
2587
|
}
|
|
2553
|
-
const resolved =
|
|
2588
|
+
const resolved = resolveSafeToolPath(filePath, 'guard_read_file', 'filePath');
|
|
2554
2589
|
if (!fs.existsSync(resolved)) {
|
|
2555
2590
|
throw new Error(`[guard_read_file] File not found: ${resolved}`);
|
|
2556
2591
|
}
|
|
@@ -2641,7 +2676,8 @@ export function createGuardedTools(engineOrOptions = {}, toolConfig = {}) {
|
|
|
2641
2676
|
required: ['filePath', 'content']
|
|
2642
2677
|
},
|
|
2643
2678
|
execute: async ({ filePath, content, createBackup = true } = {}) => {
|
|
2644
|
-
const
|
|
2679
|
+
const safeFilePath = resolveSafeToolPath(filePath, 'guard_apply_patch', 'filePath');
|
|
2680
|
+
const patchResult = applyPatch(safeFilePath, content, {
|
|
2645
2681
|
tokenMap: engine.sessionMap,
|
|
2646
2682
|
createBackup
|
|
2647
2683
|
});
|
|
@@ -2672,7 +2708,7 @@ export function createGuardedTools(engineOrOptions = {}, toolConfig = {}) {
|
|
|
2672
2708
|
required: []
|
|
2673
2709
|
},
|
|
2674
2710
|
execute: async ({ staged = false, cwd, profile = defaultProfile } = {}) => {
|
|
2675
|
-
const repoCwd = cwd ?
|
|
2711
|
+
const repoCwd = cwd ? resolveSafeToolPath(cwd, 'guard_git_diff', 'cwd') : baseCwd;
|
|
2676
2712
|
const gitCmd = staged ? 'git diff --cached' : 'git diff';
|
|
2677
2713
|
let diffOutput = '';
|
|
2678
2714
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@privacyscrubber/sdk",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.5",
|
|
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",
|