@liuhange/dsh-data-asset-compliance-check 3.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/lib/__tests__/complianceChecker.test.js +71 -0
- package/lib/complianceChecker.js +60 -0
- package/lib/index.js +68 -0
- package/lib/invariant.js +35 -0
- package/lib/types.js +2 -0
- package/package.json +28 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { checkCompliance } from '../complianceChecker.js';
|
|
3
|
+
const config = {
|
|
4
|
+
sourceRules: [
|
|
5
|
+
{ id: 'SRC_001', condition: '数据来源须有合同或协议', lawRef: '数据安全法第八条', compliant: true },
|
|
6
|
+
{ id: 'SRC_002', condition: '采集方式须明示告知', lawRef: '个人信息保护法第十四条', compliant: true },
|
|
7
|
+
{ id: 'SRC_003', condition: '不得非法获取他人数据', lawRef: '数据安全法第八条', compliant: true },
|
|
8
|
+
],
|
|
9
|
+
processingRules: [
|
|
10
|
+
{ id: 'PRC_001', condition: '加工不得超出授权范围', lawRef: '个人信息保护法第十三条', compliant: true },
|
|
11
|
+
{ id: 'PRC_002', condition: '个人信息须脱敏处理', lawRef: '个人信息保护法第五十一条', compliant: true },
|
|
12
|
+
],
|
|
13
|
+
usageRules: [
|
|
14
|
+
{ id: 'USE_001', condition: '使用不得超出约定场景', lawRef: '数据安全法第八条', compliant: true },
|
|
15
|
+
],
|
|
16
|
+
personalInfoMinimizeFields: ['idCard', 'phone', 'bankCard', 'email', 'address', 'name', 'birthDate'],
|
|
17
|
+
};
|
|
18
|
+
const policyDocs = [{ name: '数据安全法', docNumber: '主席令第八十四号', coreRequirement: '安全保护' }];
|
|
19
|
+
describe('complianceChecker', () => {
|
|
20
|
+
it('全合规场景通过', () => {
|
|
21
|
+
const report = checkCompliance({
|
|
22
|
+
assetName: '测试资产',
|
|
23
|
+
sourceDescription: '通过合同获取,已明示告知用户',
|
|
24
|
+
processingDescription: '在授权范围内加工,个人信息已脱敏',
|
|
25
|
+
usageDescription: '用于约定场景',
|
|
26
|
+
hasPersonalInfo: true,
|
|
27
|
+
personalInfoFields: ['phone', 'email'],
|
|
28
|
+
}, config, policyDocs);
|
|
29
|
+
expect(report.overallPassed).toBe(true);
|
|
30
|
+
expect(report.sourceCompliance.passed).toBe(true);
|
|
31
|
+
expect(report.processingCompliance.passed).toBe(true);
|
|
32
|
+
expect(report.usageCompliance.passed).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
it('来源含非法获取→不通过', () => {
|
|
35
|
+
const report = checkCompliance({
|
|
36
|
+
assetName: '测试资产',
|
|
37
|
+
sourceDescription: '非法获取他人数据',
|
|
38
|
+
processingDescription: '正常加工',
|
|
39
|
+
usageDescription: '正常使用',
|
|
40
|
+
hasPersonalInfo: false,
|
|
41
|
+
personalInfoFields: [],
|
|
42
|
+
}, config, policyDocs);
|
|
43
|
+
expect(report.sourceCompliance.passed).toBe(false);
|
|
44
|
+
expect(report.overallPassed).toBe(false);
|
|
45
|
+
expect(report.sourceCompliance.issues.length).toBeGreaterThan(0);
|
|
46
|
+
});
|
|
47
|
+
it('个人信息未脱敏字段→不通过', () => {
|
|
48
|
+
const report = checkCompliance({
|
|
49
|
+
assetName: '测试资产',
|
|
50
|
+
sourceDescription: '合法来源',
|
|
51
|
+
processingDescription: '正常加工',
|
|
52
|
+
usageDescription: '正常使用',
|
|
53
|
+
hasPersonalInfo: true,
|
|
54
|
+
personalInfoFields: ['rawIdCard', 'rawPhone'],
|
|
55
|
+
}, config, policyDocs);
|
|
56
|
+
expect(report.personalInfoMinimize.allMinimized).toBe(false);
|
|
57
|
+
expect(report.overallPassed).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
it('无个人信息时脱敏检查自动通过', () => {
|
|
60
|
+
const report = checkCompliance({
|
|
61
|
+
assetName: '测试资产',
|
|
62
|
+
sourceDescription: '合法来源',
|
|
63
|
+
processingDescription: '正常加工',
|
|
64
|
+
usageDescription: '正常使用',
|
|
65
|
+
hasPersonalInfo: false,
|
|
66
|
+
personalInfoFields: [],
|
|
67
|
+
}, config, policyDocs);
|
|
68
|
+
expect(report.personalInfoMinimize.allMinimized).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=complianceChecker.test.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function checkCompliance(input, config, policyDocuments) {
|
|
2
|
+
const sourceCompliance = checkCategory(input.sourceDescription, config.sourceRules);
|
|
3
|
+
const processingCompliance = checkCategory(input.processingDescription, config.processingRules);
|
|
4
|
+
const usageCompliance = checkCategory(input.usageDescription, config.usageRules);
|
|
5
|
+
const personalInfoMinimize = checkPersonalInfoMinimize(input.hasPersonalInfo, input.personalInfoFields, config.personalInfoMinimizeFields);
|
|
6
|
+
const overallPassed = sourceCompliance.passed &&
|
|
7
|
+
processingCompliance.passed &&
|
|
8
|
+
usageCompliance.passed &&
|
|
9
|
+
personalInfoMinimize.allMinimized;
|
|
10
|
+
return {
|
|
11
|
+
assetName: input.assetName,
|
|
12
|
+
sourceCompliance,
|
|
13
|
+
processingCompliance,
|
|
14
|
+
usageCompliance,
|
|
15
|
+
personalInfoMinimize,
|
|
16
|
+
overallPassed,
|
|
17
|
+
policyReferences: policyDocuments,
|
|
18
|
+
timestamp: new Date().toISOString(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function checkCategory(description, rules) {
|
|
22
|
+
const issues = [];
|
|
23
|
+
const checkedRules = rules.map(rule => {
|
|
24
|
+
const violated = detectViolation(description, rule);
|
|
25
|
+
if (violated) {
|
|
26
|
+
issues.push(`${rule.id}: ${rule.condition}(${rule.lawRef})——检测到违规`);
|
|
27
|
+
return { ...rule, compliant: false };
|
|
28
|
+
}
|
|
29
|
+
return rule;
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
passed: issues.length === 0,
|
|
33
|
+
checkedRules,
|
|
34
|
+
issues,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function detectViolation(description, rule) {
|
|
38
|
+
const negativeIndicators = ['非法', '未授权', '未经同意', '隐瞒', '虚假', '欺诈', '窃取', '违规'];
|
|
39
|
+
const conditionLower = rule.condition.toLowerCase();
|
|
40
|
+
const hasNegative = negativeIndicators.some(ind => description.includes(ind));
|
|
41
|
+
const conditionMentionsNegative = negativeIndicators.some(ind => conditionLower.includes(ind));
|
|
42
|
+
return hasNegative && conditionMentionsNegative;
|
|
43
|
+
}
|
|
44
|
+
function checkPersonalInfoMinimize(hasPersonalInfo, fields, minimizeFields) {
|
|
45
|
+
const issues = [];
|
|
46
|
+
if (!hasPersonalInfo) {
|
|
47
|
+
return { hasPersonalInfo, fields, allMinimized: true, issues };
|
|
48
|
+
}
|
|
49
|
+
const unminimized = fields.filter(f => !minimizeFields.includes(f));
|
|
50
|
+
if (unminimized.length > 0) {
|
|
51
|
+
issues.push(`以下个人信息字段未脱敏: ${unminimized.join(', ')}`);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
hasPersonalInfo,
|
|
55
|
+
fields,
|
|
56
|
+
allMinimized: unminimized.length === 0,
|
|
57
|
+
issues,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=complianceChecker.js.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { checkCompliance } from './complianceChecker.js';
|
|
2
|
+
import { validateComplianceArgs } from './invariant.js';
|
|
3
|
+
export const name = '@liuhange/dsh-data-asset-compliance-check';
|
|
4
|
+
export const inject = ['tools'];
|
|
5
|
+
export function apply(ctx) {
|
|
6
|
+
ctx.tools.register({
|
|
7
|
+
name: 'check_compliance',
|
|
8
|
+
description: '数据资产合规审查:来源合规+加工合规+用途合规三查,个人信息脱敏检查,输出合规报告与政策依据',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
assetName: { type: 'string', description: '数据资产名称' },
|
|
13
|
+
sourceDescription: { type: 'string', description: '数据来源描述' },
|
|
14
|
+
processingDescription: { type: 'string', description: '数据加工描述' },
|
|
15
|
+
usageDescription: { type: 'string', description: '数据使用场景描述' },
|
|
16
|
+
hasPersonalInfo: { type: 'boolean', description: '是否包含个人信息' },
|
|
17
|
+
personalInfoFields: { type: 'array', items: { type: 'string' }, description: '个人信息字段列表' },
|
|
18
|
+
},
|
|
19
|
+
required: ['assetName', 'sourceDescription', 'processingDescription', 'usageDescription'],
|
|
20
|
+
},
|
|
21
|
+
async execute(args) {
|
|
22
|
+
try {
|
|
23
|
+
const validated = validateComplianceArgs(args);
|
|
24
|
+
const { readFileSync } = await import('node:fs');
|
|
25
|
+
let rulesConfig;
|
|
26
|
+
try {
|
|
27
|
+
rulesConfig = JSON.parse(readFileSync(process.env.BUSINESS_RULES_PATH || 'config/business-rules.json', 'utf-8'));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return JSON.stringify({ error: 'COMPLIANCE_RULES_MISSING', message: '无法加载business-rules.json' });
|
|
31
|
+
}
|
|
32
|
+
const complianceConfig = rulesConfig.complianceCheck;
|
|
33
|
+
if (!complianceConfig) {
|
|
34
|
+
return JSON.stringify({ error: 'COMPLIANCE_RULES_MISSING', message: 'complianceCheck配置段缺失' });
|
|
35
|
+
}
|
|
36
|
+
let policyDocuments;
|
|
37
|
+
try {
|
|
38
|
+
const policyConfig = JSON.parse(readFileSync(process.env.POLICY_REFS_PATH || 'config/policy-references.json', 'utf-8'));
|
|
39
|
+
const stage = policyConfig.policyReferences?.find((s) => s.stage === 'COMPLIANCE_CHECK');
|
|
40
|
+
policyDocuments = stage?.documents ?? [];
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
policyDocuments = [];
|
|
44
|
+
}
|
|
45
|
+
if (!policyDocuments || policyDocuments.length === 0) {
|
|
46
|
+
return JSON.stringify({ error: 'POLICY_REFERENCES_EMPTY', message: 'COMPLIANCE_CHECK阶段政策依据缺失' });
|
|
47
|
+
}
|
|
48
|
+
const report = checkCompliance({
|
|
49
|
+
assetName: validated.assetName,
|
|
50
|
+
sourceDescription: validated.sourceDescription,
|
|
51
|
+
processingDescription: validated.processingDescription,
|
|
52
|
+
usageDescription: validated.usageDescription,
|
|
53
|
+
hasPersonalInfo: validated.hasPersonalInfo,
|
|
54
|
+
personalInfoFields: validated.personalInfoFields,
|
|
55
|
+
}, complianceConfig, policyDocuments);
|
|
56
|
+
return JSON.stringify(report, null, 2);
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
const err = e;
|
|
60
|
+
return JSON.stringify({
|
|
61
|
+
error: err.message.split(':')[0] ?? 'UNKNOWN_ERROR',
|
|
62
|
+
message: err.message,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=index.js.map
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function invariant(condition, message) {
|
|
2
|
+
if (!condition) {
|
|
3
|
+
throw new Error(`INVARIANT_VIOLATION: ${message}`);
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export function validateComplianceArgs(args) {
|
|
7
|
+
if (!args || typeof args !== 'object') {
|
|
8
|
+
throw new Error('INVALID_ARGS: args must be an object');
|
|
9
|
+
}
|
|
10
|
+
if (!args.assetName || typeof args.assetName !== 'string') {
|
|
11
|
+
throw new Error('INVALID_ARGS: assetName is required and must be a string');
|
|
12
|
+
}
|
|
13
|
+
if (typeof args.sourceDescription !== 'string') {
|
|
14
|
+
throw new Error('INVALID_ARGS: sourceDescription is required and must be a string');
|
|
15
|
+
}
|
|
16
|
+
if (typeof args.processingDescription !== 'string') {
|
|
17
|
+
throw new Error('INVALID_ARGS: processingDescription is required and must be a string');
|
|
18
|
+
}
|
|
19
|
+
if (typeof args.usageDescription !== 'string') {
|
|
20
|
+
throw new Error('INVALID_ARGS: usageDescription is required and must be a string');
|
|
21
|
+
}
|
|
22
|
+
const hasPersonalInfo = args.hasPersonalInfo === true;
|
|
23
|
+
const personalInfoFields = Array.isArray(args.personalInfoFields)
|
|
24
|
+
? args.personalInfoFields.filter(f => typeof f === 'string')
|
|
25
|
+
: [];
|
|
26
|
+
return {
|
|
27
|
+
assetName: args.assetName,
|
|
28
|
+
sourceDescription: args.sourceDescription,
|
|
29
|
+
processingDescription: args.processingDescription,
|
|
30
|
+
usageDescription: args.usageDescription,
|
|
31
|
+
hasPersonalInfo,
|
|
32
|
+
personalInfoFields,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=invariant.js.map
|
package/lib/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liuhange/dsh-data-asset-compliance-check",
|
|
3
|
+
"description": "Data asset compliance check plugin: source + processing + usage three-dimensional compliance audit",
|
|
4
|
+
"version": "3.0.0",
|
|
5
|
+
"publishConfig": { "access": "public" },
|
|
6
|
+
"repository": { "type": "git", "url": "git+https://github.com/liuhange789/data-asset-inspector.git", "directory": "packages/data-asset/data-asset-compliance-check" },
|
|
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
|
+
"./src/*": "./src/*",
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": ["lib/**/*.js", "lib/types/**/*.d.ts"],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"build": "tsc"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"@deepseek-ai/cordis": "^4.0.0",
|
|
24
|
+
"@deepseek-ai/dsh-tools": "0.0.1-rc.1",
|
|
25
|
+
"@liuhange/dsh-data-asset-shared": "^3.0.0"
|
|
26
|
+
},
|
|
27
|
+
"dsh": { "bundle": { "patch": true } }
|
|
28
|
+
}
|