@liuhange/dsh-data-masking 1.0.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 +18 -0
- package/lib/index.js +131 -0
- package/lib/invariant.js +5 -0
- package/lib/types/index.d.ts +5 -0
- package/lib/types/invariant.d.ts +3 -0
- package/lib/types/maskingStrategyExecutor.d.ts +7 -0
- package/lib/types/sensitiveFieldScanner.d.ts +9 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-data-masking
|
|
2
|
+
|
|
3
|
+
Data masking plugin for DeepSeek Harness. Identifies and masks sensitive fields (ID card, phone, bank card, email).
|
|
4
|
+
|
|
5
|
+
## Tool: mask_sensitive_data
|
|
6
|
+
|
|
7
|
+
- **Parameters**: `filePath` (required), `strategy` (optional: FULL | PARTIAL | GENERALIZE, default PARTIAL)
|
|
8
|
+
- **Output**: Masking report with field counts and output file path
|
|
9
|
+
- **Output file**: `{originalName}_masked.{ext}`
|
|
10
|
+
|
|
11
|
+
## Business Rules
|
|
12
|
+
|
|
13
|
+
All sensitive field patterns and masking levels are read from `config/business-rules.json`. No business logic is hardcoded.
|
|
14
|
+
|
|
15
|
+
## Known Limitations and Deferred Work
|
|
16
|
+
|
|
17
|
+
- Regex patterns are compiled at runtime from config strings
|
|
18
|
+
- Large file streaming masking not yet implemented
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { AuditLogger, BusinessRulesLoader, FileFormatAdapter, PathValidator, ReportGenerator } from "@deepseek-ai/dsh-data-asset-shared";
|
|
5
|
+
//#region lib/types/sensitiveFieldScanner.js
|
|
6
|
+
var SensitiveFieldScanner = class {
|
|
7
|
+
scan(lines, sensitivePatterns) {
|
|
8
|
+
const fields = [];
|
|
9
|
+
const typeCounts = {};
|
|
10
|
+
const patternEntries = Object.entries(sensitivePatterns);
|
|
11
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
12
|
+
const line = lines[lineIndex] ?? "";
|
|
13
|
+
for (const [type, config] of patternEntries) {
|
|
14
|
+
const regex = new RegExp(config.pattern, "g");
|
|
15
|
+
let match;
|
|
16
|
+
while ((match = regex.exec(line)) !== null) {
|
|
17
|
+
fields.push({
|
|
18
|
+
type,
|
|
19
|
+
value: match[0],
|
|
20
|
+
line: lineIndex + 1,
|
|
21
|
+
column: match.index + 1
|
|
22
|
+
});
|
|
23
|
+
typeCounts[type] = (typeCounts[type] ?? 0) + 1;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
fields,
|
|
29
|
+
typeCounts
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region lib/types/maskingStrategyExecutor.js
|
|
35
|
+
var MaskingStrategyExecutor = class {
|
|
36
|
+
execute(value, _type, strategy) {
|
|
37
|
+
switch (this.resolveStrategy(strategy)) {
|
|
38
|
+
case "FULL": return "***";
|
|
39
|
+
case "PARTIAL": return this.applyPartial(value);
|
|
40
|
+
case "GENERALIZE": return "<脱敏数据>";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
resolveStrategy(strategy) {
|
|
44
|
+
if (strategy === "FULL" || strategy === "PARTIAL" || strategy === "GENERALIZE") return strategy;
|
|
45
|
+
return "PARTIAL";
|
|
46
|
+
}
|
|
47
|
+
applyPartial(value, keepPrefix = 3, keepSuffix = 4) {
|
|
48
|
+
if (value.length <= keepPrefix + keepSuffix) return "***";
|
|
49
|
+
return value.slice(0, keepPrefix) + "***" + value.slice(-keepSuffix);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region lib/types/index.js
|
|
54
|
+
const name = "data-masking";
|
|
55
|
+
const inject = ["tools"];
|
|
56
|
+
function apply(ctx) {
|
|
57
|
+
const loader = new BusinessRulesLoader();
|
|
58
|
+
const formatAdapter = new FileFormatAdapter();
|
|
59
|
+
const reportGenerator = new ReportGenerator();
|
|
60
|
+
const pathValidator = new PathValidator();
|
|
61
|
+
const auditLogger = new AuditLogger();
|
|
62
|
+
const scanner = new SensitiveFieldScanner();
|
|
63
|
+
const strategyExecutor = new MaskingStrategyExecutor();
|
|
64
|
+
ctx.tools.register(defineTool({
|
|
65
|
+
name: "mask_sensitive_data",
|
|
66
|
+
description: "识别并脱敏数据中的敏感字段(身份证、手机号、银行卡、邮箱)",
|
|
67
|
+
parameters: {
|
|
68
|
+
filePath: {
|
|
69
|
+
type: "string",
|
|
70
|
+
required: true,
|
|
71
|
+
description: "待处理文件的路径"
|
|
72
|
+
},
|
|
73
|
+
strategy: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "脱敏策略: FULL | PARTIAL | GENERALIZE"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
output: {
|
|
79
|
+
schema: { type: "string" },
|
|
80
|
+
render: (_args, value) => [{
|
|
81
|
+
type: "text",
|
|
82
|
+
text: value
|
|
83
|
+
}]
|
|
84
|
+
},
|
|
85
|
+
async execute(args) {
|
|
86
|
+
const { config } = loader.load();
|
|
87
|
+
const workingDir = process.cwd();
|
|
88
|
+
const filePath = args.filePath;
|
|
89
|
+
const strategy = args.strategy ?? "PARTIAL";
|
|
90
|
+
try {
|
|
91
|
+
pathValidator.validate(filePath, workingDir);
|
|
92
|
+
} catch {
|
|
93
|
+
return `错误:路径不合法 - ${filePath}`;
|
|
94
|
+
}
|
|
95
|
+
const fullPath = path.resolve(filePath);
|
|
96
|
+
if (!fs.existsSync(fullPath)) return `错误:文件不存在 - ${fullPath}`;
|
|
97
|
+
const readResult = await formatAdapter.read(fullPath);
|
|
98
|
+
const lines = readResult.lines;
|
|
99
|
+
const scanResult = scanner.scan(lines, config.sensitivePatterns);
|
|
100
|
+
let maskedLines = [...lines];
|
|
101
|
+
for (const field of scanResult.fields) {
|
|
102
|
+
const fieldType = field.type;
|
|
103
|
+
const fieldStrategy = config.sensitivePatterns[fieldType].level ?? strategy;
|
|
104
|
+
const maskedValue = strategyExecutor.execute(field.value, fieldType, fieldStrategy);
|
|
105
|
+
maskedLines = maskedLines.map((line) => line.replace(field.value, maskedValue));
|
|
106
|
+
}
|
|
107
|
+
const ext = path.extname(fullPath);
|
|
108
|
+
const baseName = path.basename(fullPath, ext);
|
|
109
|
+
const outputPath = path.join(path.dirname(fullPath), `${baseName}_masked${ext}`);
|
|
110
|
+
await formatAdapter.write(outputPath, maskedLines, readResult.format);
|
|
111
|
+
const report = reportGenerator.generateMaskingReport({
|
|
112
|
+
inputPath: fullPath,
|
|
113
|
+
outputPath,
|
|
114
|
+
strategy: strategyExecutor.resolveStrategy(strategy),
|
|
115
|
+
findings: scanResult.fields,
|
|
116
|
+
fieldTypeCounts: scanResult.typeCounts
|
|
117
|
+
});
|
|
118
|
+
auditLogger.log({
|
|
119
|
+
pluginName: "data-masking",
|
|
120
|
+
operation: "mask_sensitive_data",
|
|
121
|
+
inputPath: fullPath,
|
|
122
|
+
outputPath,
|
|
123
|
+
result: "SUCCESS"
|
|
124
|
+
});
|
|
125
|
+
return report;
|
|
126
|
+
}
|
|
127
|
+
}));
|
|
128
|
+
console.log("[data-masking] 脱敏插件已加载");
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
export { apply, inject, name };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MaskingStrategy, SensitiveFieldType } from '@deepseek-ai/dsh-data-asset-shared';
|
|
2
|
+
export declare class MaskingStrategyExecutor {
|
|
3
|
+
execute(value: string, _type: SensitiveFieldType, strategy: MaskingStrategy): string;
|
|
4
|
+
resolveStrategy(strategy: MaskingStrategy): MaskingStrategy;
|
|
5
|
+
private applyPartial;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=maskingStrategyExecutor.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SensitiveField, SensitivePatterns } from '@deepseek-ai/dsh-data-asset-shared';
|
|
2
|
+
export interface ScanResult {
|
|
3
|
+
fields: SensitiveField[];
|
|
4
|
+
typeCounts: Record<string, number>;
|
|
5
|
+
}
|
|
6
|
+
export declare class SensitiveFieldScanner {
|
|
7
|
+
scan(lines: string[], sensitivePatterns: SensitivePatterns): ScanResult;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=sensitiveFieldScanner.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liuhange/dsh-data-masking",
|
|
3
|
+
"description": "Data masking plugin: sensitive field identification and graded masking (FULL/PARTIAL/GENERALIZE)",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"publishConfig": { "access": "public" },
|
|
6
|
+
"repository": { "type": "git", "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", "directory": "packages/data-asset/data-masking" },
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "lib/index.js",
|
|
9
|
+
"types": "lib/types/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": { "types": "./lib/types/index.d.ts", "default": "./lib/index.js" },
|
|
12
|
+
"./invariant": { "types": "./lib/types/invariant.d.ts", "default": "./lib/invariant.js" },
|
|
13
|
+
"./src/*": "./src/*",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": ["lib/index.js", "lib/invariant.js", "lib/types/**/*.d.ts"],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@deepseek-ai/cordis": "^0.1.0",
|
|
20
|
+
"@deepseek-ai/dsh-tools": "^0.1.0",
|
|
21
|
+
"@liuhange/dsh-data-asset-shared": "^1.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|