shellward 0.6.1 → 0.6.3
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 +78 -5
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +144 -0
- package/dist/commands/compliance.d.ts +2 -0
- package/dist/commands/compliance.js +21 -0
- package/dist/commands/index.js +6 -2
- package/dist/compliance/audit.d.ts +57 -0
- package/dist/compliance/audit.js +234 -0
- package/dist/compliance/project-scan.d.ts +23 -0
- package/dist/compliance/project-scan.js +225 -0
- package/dist/compliance/regulations.d.ts +35 -0
- package/dist/compliance/regulations.js +218 -0
- package/dist/compliance/report.d.ts +9 -0
- package/dist/compliance/report.js +215 -0
- package/dist/mcp-server.js +36 -0
- package/dist/rules/domestic-alternatives.d.ts +27 -0
- package/dist/rules/domestic-alternatives.js +62 -0
- package/dist/rules/overseas-llm.d.ts +37 -0
- package/dist/rules/overseas-llm.js +147 -0
- package/package.json +4 -3
- package/src/cli.ts +154 -0
- package/src/commands/compliance.ts +25 -0
- package/src/commands/index.ts +6 -2
- package/src/compliance/audit.ts +310 -0
- package/src/compliance/project-scan.ts +263 -0
- package/src/compliance/regulations.ts +260 -0
- package/src/compliance/report.ts +242 -0
- package/src/mcp-server.ts +37 -0
- package/src/rules/domestic-alternatives.ts +90 -0
- package/src/rules/overseas-llm.ts +174 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// src/compliance/project-scan.ts — 项目真实风险扫描
|
|
2
|
+
//
|
|
3
|
+
// 体检的灵魂:报告的是「用户项目的真实风险」,不是「ShellWard 的开关」。
|
|
4
|
+
// 零依赖遍历当前项目目录,找出可截图、可定位 (文件:行) 的合规风险:
|
|
5
|
+
// ① 境外大模型端点(硬编码 base_url/key)→ 数据出境风险
|
|
6
|
+
// ② 硬编码密钥(API key / 私钥 / 口令 / 连接串)
|
|
7
|
+
// ③ 文件中的中文 PII(身份证 / 手机号 / 银行卡)
|
|
8
|
+
// ④ .env 等敏感文件权限过宽
|
|
9
|
+
|
|
10
|
+
import { readdirSync, statSync, readFileSync } from 'fs'
|
|
11
|
+
import { join, relative, basename } from 'path'
|
|
12
|
+
import { detectOverseasLLM, detectOverseasDeps, isDependencyManifest } from '../rules/overseas-llm.js'
|
|
13
|
+
import { SENSITIVE_PATTERNS } from '../rules/sensitive-patterns.js'
|
|
14
|
+
|
|
15
|
+
export type FindingKind = 'overseas' | 'secret' | 'pii' | 'env-perm'
|
|
16
|
+
|
|
17
|
+
export interface ProjectFinding {
|
|
18
|
+
kind: FindingKind
|
|
19
|
+
/** 相对项目根的路径 */
|
|
20
|
+
file: string
|
|
21
|
+
line?: number
|
|
22
|
+
/** 人类可读结论 */
|
|
23
|
+
detail: string
|
|
24
|
+
severity: 'critical' | 'high' | 'medium'
|
|
25
|
+
/** 仅 overseas:命中的境外端点信息,供体检评分并入 */
|
|
26
|
+
endpointId?: string
|
|
27
|
+
provider_zh?: string
|
|
28
|
+
provider_en?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ProjectScanResult {
|
|
32
|
+
root: string
|
|
33
|
+
filesScanned: number
|
|
34
|
+
truncated: boolean
|
|
35
|
+
findings: ProjectFinding[]
|
|
36
|
+
counts: Record<FindingKind, number>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ===== 扫描边界(零依赖、可控) =====
|
|
40
|
+
const SKIP_DIRS = new Set([
|
|
41
|
+
'node_modules', '.git', 'dist', 'build', '.next', 'out',
|
|
42
|
+
'vendor', 'coverage', '.venv', 'venv', '__pycache__', '.cache', 'target',
|
|
43
|
+
])
|
|
44
|
+
const SCAN_EXT = new Set([
|
|
45
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
46
|
+
'.py', '.go', '.rb', '.java', '.php', '.rs',
|
|
47
|
+
'.json', '.yaml', '.yml', '.toml', '.ini', '.conf', '.sh', '.txt', '.csv',
|
|
48
|
+
])
|
|
49
|
+
const MAX_FILES = 3000
|
|
50
|
+
const MAX_FILE_BYTES = 512 * 1024
|
|
51
|
+
const MAX_FINDINGS_PER_FILE = 20
|
|
52
|
+
const MAX_TOTAL_FINDINGS = 500
|
|
53
|
+
|
|
54
|
+
// 密钥类 vs PII 类(按 sensitive-patterns 的 id 归类)
|
|
55
|
+
const SECRET_IDS = new Set([
|
|
56
|
+
'openai_key', 'anthropic_key', 'aws_access', 'github_token',
|
|
57
|
+
'generic_api_key', 'private_key', 'jwt', 'password', 'conn_string',
|
|
58
|
+
])
|
|
59
|
+
const PII_IDS = new Set(['id_card_cn', 'phone_cn', 'bank_card_cn', 'ssn_us', 'credit_card'])
|
|
60
|
+
|
|
61
|
+
/** 扫描项目目录,返回真实风险发现 */
|
|
62
|
+
export function scanProject(root: string): ProjectScanResult {
|
|
63
|
+
const findings: ProjectFinding[] = []
|
|
64
|
+
const state = { files: 0, truncated: false }
|
|
65
|
+
|
|
66
|
+
walk(root, root, findings, state)
|
|
67
|
+
|
|
68
|
+
const counts: Record<FindingKind, number> = { overseas: 0, secret: 0, pii: 0, 'env-perm': 0 }
|
|
69
|
+
for (const f of findings) counts[f.kind]++
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
root,
|
|
73
|
+
filesScanned: state.files,
|
|
74
|
+
truncated: state.truncated,
|
|
75
|
+
findings,
|
|
76
|
+
counts,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function walk(
|
|
81
|
+
dir: string,
|
|
82
|
+
root: string,
|
|
83
|
+
findings: ProjectFinding[],
|
|
84
|
+
state: { files: number; truncated: boolean },
|
|
85
|
+
): void {
|
|
86
|
+
if (state.files >= MAX_FILES || findings.length >= MAX_TOTAL_FINDINGS) {
|
|
87
|
+
state.truncated = true
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
let entries: string[]
|
|
91
|
+
try {
|
|
92
|
+
entries = readdirSync(dir)
|
|
93
|
+
} catch {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const name of entries) {
|
|
98
|
+
if (state.files >= MAX_FILES || findings.length >= MAX_TOTAL_FINDINGS) {
|
|
99
|
+
state.truncated = true
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
if (name.startsWith('.') && !name.startsWith('.env')) {
|
|
103
|
+
// 跳过隐藏文件/目录,但保留 .env*
|
|
104
|
+
if (name !== '.') continue
|
|
105
|
+
}
|
|
106
|
+
const full = join(dir, name)
|
|
107
|
+
let st
|
|
108
|
+
try {
|
|
109
|
+
st = statSync(full)
|
|
110
|
+
} catch {
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (st.isDirectory()) {
|
|
115
|
+
if (SKIP_DIRS.has(name)) continue
|
|
116
|
+
walk(full, root, findings, state)
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!st.isFile()) continue
|
|
121
|
+
|
|
122
|
+
// .env 权限检查(任意大小)
|
|
123
|
+
if (/^\.env(\..+)?$/.test(name)) {
|
|
124
|
+
checkEnvPerm(full, root, st.mode, findings)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 内容扫描:文本类扩展 + .env* + 依赖清单(含 go.mod),且大小受限
|
|
128
|
+
const isEnv = /^\.env(\..+)?$/.test(name)
|
|
129
|
+
const isDep = isDependencyManifest(name)
|
|
130
|
+
const ext = name.includes('.') ? name.slice(name.lastIndexOf('.')) : ''
|
|
131
|
+
if (!isEnv && !isDep && !SCAN_EXT.has(ext)) continue
|
|
132
|
+
if (st.size > MAX_FILE_BYTES) continue
|
|
133
|
+
|
|
134
|
+
let content: string
|
|
135
|
+
try {
|
|
136
|
+
content = readFileSync(full, 'utf-8')
|
|
137
|
+
} catch {
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
state.files++
|
|
141
|
+
if (isDep) scanDependencies(name, content, full, root, findings)
|
|
142
|
+
scanContent(content, full, root, findings)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function scanDependencies(
|
|
147
|
+
name: string,
|
|
148
|
+
content: string,
|
|
149
|
+
full: string,
|
|
150
|
+
root: string,
|
|
151
|
+
findings: ProjectFinding[],
|
|
152
|
+
): void {
|
|
153
|
+
const deps = detectOverseasDeps(name, content)
|
|
154
|
+
if (deps.length === 0) return
|
|
155
|
+
const file = rel(root, full)
|
|
156
|
+
const lines = content.split('\n')
|
|
157
|
+
for (const d of deps) {
|
|
158
|
+
if (findings.length >= MAX_TOTAL_FINDINGS) break
|
|
159
|
+
// 粗定位包名所在行
|
|
160
|
+
let line: number | undefined
|
|
161
|
+
for (let i = 0; i < lines.length; i++) {
|
|
162
|
+
if (lines[i].toLowerCase().includes(d.pkg.toLowerCase())) { line = i + 1; break }
|
|
163
|
+
}
|
|
164
|
+
findings.push({
|
|
165
|
+
kind: 'overseas',
|
|
166
|
+
file,
|
|
167
|
+
line,
|
|
168
|
+
detail: `境外大模型 SDK 依赖: ${d.pkg} (${d.provider_zh}) — 项目内含数据出境通道,调用即可能构成出境`,
|
|
169
|
+
severity: 'high',
|
|
170
|
+
provider_zh: d.provider_zh,
|
|
171
|
+
provider_en: d.provider_en,
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function checkEnvPerm(full: string, root: string, mode: number, findings: ProjectFinding[]): void {
|
|
177
|
+
// 仅 POSIX 有意义;Windows 上 mode 不可靠,跳过
|
|
178
|
+
if (process.platform === 'win32') return
|
|
179
|
+
const perm = mode & 0o777
|
|
180
|
+
if (perm > 0o600) {
|
|
181
|
+
findings.push({
|
|
182
|
+
kind: 'env-perm',
|
|
183
|
+
file: rel(root, full),
|
|
184
|
+
detail: `权限过宽 (${perm.toString(8)}),建议 chmod 600 — 含密钥的 .env 不应组/其他可读`,
|
|
185
|
+
severity: 'high',
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function scanContent(
|
|
191
|
+
content: string,
|
|
192
|
+
full: string,
|
|
193
|
+
root: string,
|
|
194
|
+
findings: ProjectFinding[],
|
|
195
|
+
): void {
|
|
196
|
+
const file = rel(root, full)
|
|
197
|
+
const lines = content.split('\n')
|
|
198
|
+
let perFile = 0
|
|
199
|
+
const dedup = new Set<string>()
|
|
200
|
+
|
|
201
|
+
for (let i = 0; i < lines.length; i++) {
|
|
202
|
+
if (perFile >= MAX_FINDINGS_PER_FILE || findings.length >= MAX_TOTAL_FINDINGS) break
|
|
203
|
+
const line = lines[i]
|
|
204
|
+
if (!line || line.length > 4000) continue
|
|
205
|
+
|
|
206
|
+
// ① 境外大模型端点
|
|
207
|
+
const ov = detectOverseasLLM(line)
|
|
208
|
+
if (ov.isOverseas) {
|
|
209
|
+
const key = `overseas:${ov.endpointId}`
|
|
210
|
+
if (!dedup.has(key)) {
|
|
211
|
+
dedup.add(key)
|
|
212
|
+
findings.push({
|
|
213
|
+
kind: 'overseas',
|
|
214
|
+
file,
|
|
215
|
+
line: i + 1,
|
|
216
|
+
detail: `境外大模型端点: ${ov.provider_zh} — 向其发送个人信息/重要数据即构成数据出境`,
|
|
217
|
+
severity: 'critical',
|
|
218
|
+
endpointId: ov.endpointId,
|
|
219
|
+
provider_zh: ov.provider_zh,
|
|
220
|
+
provider_en: ov.provider_en,
|
|
221
|
+
})
|
|
222
|
+
perFile++
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ② / ③ 密钥与 PII
|
|
227
|
+
for (const pat of SENSITIVE_PATTERNS) {
|
|
228
|
+
if (perFile >= MAX_FINDINGS_PER_FILE) break
|
|
229
|
+
const isSecret = SECRET_IDS.has(pat.id)
|
|
230
|
+
const isPII = PII_IDS.has(pat.id)
|
|
231
|
+
if (!isSecret && !isPII) continue
|
|
232
|
+
const re = new RegExp(pat.regex.source, pat.regex.flags)
|
|
233
|
+
let m: RegExpExecArray | null
|
|
234
|
+
while ((m = re.exec(line)) !== null) {
|
|
235
|
+
if (pat.validate && !pat.validate(m[0])) continue
|
|
236
|
+
const key = `${pat.id}:${i}`
|
|
237
|
+
if (dedup.has(key)) break
|
|
238
|
+
dedup.add(key)
|
|
239
|
+
findings.push({
|
|
240
|
+
kind: isSecret ? 'secret' : 'pii',
|
|
241
|
+
file,
|
|
242
|
+
line: i + 1,
|
|
243
|
+
detail: isSecret
|
|
244
|
+
? `硬编码${pat.name}: ${preview(m[0])} — 凭据不应写入源码/配置`
|
|
245
|
+
: `${pat.name}: ${preview(m[0])} — 个人信息出现在文件中,需评估最小必要与脱敏`,
|
|
246
|
+
severity: isSecret ? 'critical' : 'high',
|
|
247
|
+
})
|
|
248
|
+
perFile++
|
|
249
|
+
if (perFile >= MAX_FINDINGS_PER_FILE) break
|
|
250
|
+
if (!re.global) break
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function preview(s: string): string {
|
|
257
|
+
return s.length > 10 ? s.slice(0, 6) + '***' : s.slice(0, 3) + '***'
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function rel(root: string, full: string): string {
|
|
261
|
+
const r = relative(root, full)
|
|
262
|
+
return r || basename(full)
|
|
263
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// src/compliance/regulations.ts — 中国 AI 合规控制项映射层
|
|
2
|
+
//
|
|
3
|
+
// 把网安法 / PIPL / 等保2.0 / 数据出境 / 生成式AI标识 等法规的「可核查控制项」
|
|
4
|
+
// 结构化为统一数据模型,作为合规体检引擎 (compliance/audit.ts) 的数据源。
|
|
5
|
+
//
|
|
6
|
+
// 每条控制项 = 一条真实法规条款 → 一个可被 ShellWard 检测或支撑的技术点。
|
|
7
|
+
// 法规出处见 README「合规」章节与研究报告(cac.gov.cn / npc.gov.cn / 国标平台)。
|
|
8
|
+
|
|
9
|
+
/** 控制项归属的法规 */
|
|
10
|
+
export type Regulation =
|
|
11
|
+
| 'CSL' // 《网络安全法》(2025修正, 2026-01-01 生效)
|
|
12
|
+
| 'PIPL' // 《个人信息保护法》
|
|
13
|
+
| 'MLPS' // 等级保护 2.0 (GB/T 22239-2019)
|
|
14
|
+
| 'CBDT' // 数据出境 (促进数据跨境流动规定 / 安全评估)
|
|
15
|
+
| 'GENAI' // 生成式AI暂行办法 + 标识办法 (GB 45438-2025)
|
|
16
|
+
|
|
17
|
+
/** 控制项的检测方式 —— 决定体检引擎用哪种 checker */
|
|
18
|
+
export type CheckMethod =
|
|
19
|
+
| 'capability' // ShellWard 自身能力是否启用 (查 config.layers)
|
|
20
|
+
| 'config' // ShellWard 配置是否合规 (查 mode / threshold 等)
|
|
21
|
+
| 'audit' // 审计日志是否存在且满足留存要求
|
|
22
|
+
| 'env' // 运行环境扫描 (root / 端口 / .env 权限 / 出境端点)
|
|
23
|
+
| 'manual' // 无法自动判定,需人工确认 (主体责任,平台仅提供举证)
|
|
24
|
+
|
|
25
|
+
export type Severity = 'critical' | 'high' | 'medium' | 'low'
|
|
26
|
+
|
|
27
|
+
export interface ComplianceControl {
|
|
28
|
+
/** 稳定 ID,体检引擎据此映射 checker,报告据此引用 */
|
|
29
|
+
id: string
|
|
30
|
+
regulation: Regulation
|
|
31
|
+
/** 法规条款 / 国标编号 */
|
|
32
|
+
article: string
|
|
33
|
+
title_zh: string
|
|
34
|
+
title_en: string
|
|
35
|
+
/** 监管要求原文要点 */
|
|
36
|
+
requirement_zh: string
|
|
37
|
+
requirement_en: string
|
|
38
|
+
method: CheckMethod
|
|
39
|
+
severity: Severity
|
|
40
|
+
/** 不满足时的整改建议 */
|
|
41
|
+
remediation_zh: string
|
|
42
|
+
remediation_en: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 法规中文显示名 */
|
|
46
|
+
export const REGULATION_NAMES: Record<Regulation, { zh: string; en: string }> = {
|
|
47
|
+
CSL: { zh: '网络安全法 (2026.1.1)', en: 'Cybersecurity Law (2026-01-01)' },
|
|
48
|
+
PIPL: { zh: '个人信息保护法', en: 'PIPL' },
|
|
49
|
+
MLPS: { zh: '等保 2.0 (GB/T 22239)', en: 'MLPS 2.0' },
|
|
50
|
+
CBDT: { zh: '数据出境', en: 'Cross-Border Data Transfer' },
|
|
51
|
+
GENAI: { zh: '生成式AI / 内容标识', en: 'GenAI / Content Labeling' },
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 合规控制项清单。
|
|
56
|
+
* 注:本清单帮助企业"满足"合规技术要求,不等于"替企业完成"合规
|
|
57
|
+
* (备案 / 定级 / PIA 主体责任不可外包)。method='manual' 的项 ShellWard 仅提供举证支撑。
|
|
58
|
+
*/
|
|
59
|
+
export const COMPLIANCE_CONTROLS: ComplianceControl[] = [
|
|
60
|
+
// ===== 网络安全法 (CSL) =====
|
|
61
|
+
{
|
|
62
|
+
id: 'csl-audit-log',
|
|
63
|
+
regulation: 'CSL',
|
|
64
|
+
article: '第二十三条',
|
|
65
|
+
title_zh: '网络日志留存不少于 6 个月',
|
|
66
|
+
title_en: 'Retain network logs for ≥6 months',
|
|
67
|
+
requirement_zh: '采取监测、记录网络运行状态的技术措施,留存相关网络日志不少于六个月。',
|
|
68
|
+
requirement_en: 'Retain network operation logs for no less than six months.',
|
|
69
|
+
method: 'audit',
|
|
70
|
+
severity: 'high',
|
|
71
|
+
remediation_zh: '启用 ShellWard 审计日志,确保工具调用 / prompt / 决策链统一留痕、防篡改、保留 ≥6 个月。',
|
|
72
|
+
remediation_en: 'Enable ShellWard audit logging with ≥6-month, tamper-resistant retention.',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'csl-content-block',
|
|
76
|
+
regulation: 'CSL',
|
|
77
|
+
article: '第四十九条',
|
|
78
|
+
title_zh: '发现禁止信息立即停止传输并留痕',
|
|
79
|
+
title_en: 'Stop transmission of prohibited info & keep records',
|
|
80
|
+
requirement_zh: '发现法律禁止发布或传输的信息,立即停止传输、采取处置措施、保存记录并报告。',
|
|
81
|
+
requirement_en: 'On detecting prohibited information, immediately stop transmission, dispose, record and report.',
|
|
82
|
+
method: 'capability',
|
|
83
|
+
severity: 'high',
|
|
84
|
+
remediation_zh: '启用输出内容审查层 (outputScanner / outboundGuard),对生成内容实时审查、阻断并留痕。',
|
|
85
|
+
remediation_en: 'Enable output content review layers to block and log prohibited content in real time.',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'csl-intrusion',
|
|
89
|
+
regulation: 'CSL',
|
|
90
|
+
article: '第二十一条 / 等保',
|
|
91
|
+
title_zh: '入侵防范与异常监测',
|
|
92
|
+
title_en: 'Intrusion prevention & anomaly monitoring',
|
|
93
|
+
requirement_zh: '采取防范计算机病毒和网络攻击、网络侵入等危害网络安全行为的技术措施。',
|
|
94
|
+
requirement_en: 'Take technical measures against intrusion and attacks endangering network security.',
|
|
95
|
+
method: 'capability',
|
|
96
|
+
severity: 'medium',
|
|
97
|
+
remediation_zh: '启用提示注入检测 (inputAuditor) 与危险命令拦截 (toolBlocker)。',
|
|
98
|
+
remediation_en: 'Enable prompt-injection detection and dangerous-command blocking.',
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// ===== 个人信息保护法 (PIPL) =====
|
|
102
|
+
{
|
|
103
|
+
id: 'pipl-spi-detect',
|
|
104
|
+
regulation: 'PIPL',
|
|
105
|
+
article: '第二十八条',
|
|
106
|
+
title_zh: '敏感个人信息识别 (7类+未成年人)',
|
|
107
|
+
title_en: 'Detect sensitive personal information',
|
|
108
|
+
requirement_zh: '识别生物识别、医疗健康、金融账户、行踪轨迹等敏感个人信息及不满14岁未成年人信息。',
|
|
109
|
+
requirement_en: 'Detect sensitive PI: biometrics, health, financial accounts, location, minors under 14.',
|
|
110
|
+
method: 'capability',
|
|
111
|
+
severity: 'critical',
|
|
112
|
+
remediation_zh: '启用 PII/敏感数据扫描层 (outputScanner),覆盖身份证、银行卡、手机号等中文敏感信息。',
|
|
113
|
+
remediation_en: 'Enable PII/SPI scanning covering Chinese ID, bank card, phone, etc.',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'pipl-minimize',
|
|
117
|
+
regulation: 'PIPL',
|
|
118
|
+
article: '第六条 / 第十九条',
|
|
119
|
+
title_zh: '最小必要 + 数据流向管控',
|
|
120
|
+
title_en: 'Data minimization & flow control',
|
|
121
|
+
requirement_zh: '处理个人信息应限于实现处理目的的最小范围,非必要不收集、不外发。',
|
|
122
|
+
requirement_en: 'Process PI within the minimum scope necessary for the purpose.',
|
|
123
|
+
method: 'capability',
|
|
124
|
+
severity: 'high',
|
|
125
|
+
remediation_zh: '启用数据流追踪层 (dataFlowGuard):读取敏感数据后向外发送将被拦截。',
|
|
126
|
+
remediation_en: 'Enable data-flow tracking: outbound send is blocked after sensitive data access.',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'pipl-pia',
|
|
130
|
+
regulation: 'PIPL',
|
|
131
|
+
article: '第五十五条 / 第五十六条',
|
|
132
|
+
title_zh: '个人信息保护影响评估 (PIA) 并留存 ≥3 年',
|
|
133
|
+
title_en: 'Conduct PIA & retain records ≥3 years',
|
|
134
|
+
requirement_zh: '处理敏感PI、自动化决策、对外提供、出境等情形须事前进行 PIA,报告留存至少 3 年。',
|
|
135
|
+
requirement_en: 'Conduct a PIA for sensitive PI / automated decisions / cross-border transfer; retain ≥3 years.',
|
|
136
|
+
method: 'audit',
|
|
137
|
+
severity: 'high',
|
|
138
|
+
remediation_zh: '由数据流入/出口触发 PIA 工作流并将报告纳入防篡改审计存储(≥3 年)。',
|
|
139
|
+
remediation_en: 'Trigger PIA workflow on data ingress/egress; store reports in tamper-resistant audit (≥3y).',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 'pipl-auto-decision',
|
|
143
|
+
regulation: 'PIPL',
|
|
144
|
+
article: '第二十四条',
|
|
145
|
+
title_zh: '自动化决策记录 + 人工复核回退',
|
|
146
|
+
title_en: 'Automated-decision logging & human-in-the-loop',
|
|
147
|
+
requirement_zh: '自动化决策应保证透明、结果公平,并提供拒绝纯自动化决策、要求说明的途径。',
|
|
148
|
+
requirement_en: 'Ensure transparency for automated decisions and a human-review fallback.',
|
|
149
|
+
method: 'capability',
|
|
150
|
+
severity: 'medium',
|
|
151
|
+
remediation_zh: '记录自动化决策调用(输入特征/模型版本/输出),高风险动作转人工复核 (securityGate)。',
|
|
152
|
+
remediation_en: 'Log automated decisions and route high-risk actions to human review.',
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// ===== 等保 2.0 (MLPS) =====
|
|
156
|
+
{
|
|
157
|
+
id: 'mlps-audit-fields',
|
|
158
|
+
regulation: 'MLPS',
|
|
159
|
+
article: 'GB/T 22239 8.1.4.3',
|
|
160
|
+
title_zh: '安全审计:覆盖每用户、记录五要素',
|
|
161
|
+
title_en: 'Security audit: per-user, five-element records',
|
|
162
|
+
requirement_zh: '审计覆盖每个用户,记录时间、用户、类型、成败及其他相关信息,审计记录受保护防中断。',
|
|
163
|
+
requirement_en: 'Audit each user; record time, user, type, result and other info; protect audit records.',
|
|
164
|
+
method: 'audit',
|
|
165
|
+
severity: 'high',
|
|
166
|
+
remediation_zh: '使用 ShellWard 审计日志记录五要素并集中防篡改存储。',
|
|
167
|
+
remediation_en: 'Use ShellWard audit log to record five elements with tamper-resistant storage.',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'mlps-access-control',
|
|
171
|
+
regulation: 'MLPS',
|
|
172
|
+
article: 'GB/T 22239 8.1.4.2',
|
|
173
|
+
title_zh: '访问控制:最小权限 + 工具/数据粒度',
|
|
174
|
+
title_en: 'Access control: least privilege & granularity',
|
|
175
|
+
requirement_zh: '按最小权限分配,访问控制粒度达到用户/进程级及文件/数据库表级。',
|
|
176
|
+
requirement_en: 'Assign least privilege; control granularity to user/process and file/table level.',
|
|
177
|
+
method: 'capability',
|
|
178
|
+
severity: 'medium',
|
|
179
|
+
remediation_zh: '启用工具策略层 (securityGate),对高风险工具/资源按最小授权管控。',
|
|
180
|
+
remediation_en: 'Enable tool-policy gate with least-privilege control over high-risk tools.',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'mlps-not-root',
|
|
184
|
+
regulation: 'MLPS',
|
|
185
|
+
article: 'GB/T 22239 8.1.x',
|
|
186
|
+
title_zh: '不以特权账户 (root) 运行',
|
|
187
|
+
title_en: 'Do not run as privileged (root) account',
|
|
188
|
+
requirement_zh: '应遵循最小化原则,避免以最高权限账户长期运行业务进程。',
|
|
189
|
+
requirement_en: 'Follow minimization; avoid running business processes as the root account.',
|
|
190
|
+
method: 'env',
|
|
191
|
+
severity: 'medium',
|
|
192
|
+
remediation_zh: '使用普通用户运行 + 容器隔离,避免以 root 启动 Agent。',
|
|
193
|
+
remediation_en: 'Run as non-root user with container isolation.',
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
// ===== 数据出境 (CBDT) =====
|
|
197
|
+
{
|
|
198
|
+
id: 'cbdt-overseas-llm',
|
|
199
|
+
regulation: 'CBDT',
|
|
200
|
+
article: '促进数据跨境流动规定',
|
|
201
|
+
title_zh: '境外大模型调用 = 数据出境识别',
|
|
202
|
+
title_en: 'Detect overseas LLM calls as data export',
|
|
203
|
+
requirement_zh: '向境外接收方提供个人信息/重要数据须识别并走相应路径;重要数据一律不得无评估出境。',
|
|
204
|
+
requirement_en: 'Identify PI/important-data export to overseas recipients; important data needs assessment.',
|
|
205
|
+
method: 'env',
|
|
206
|
+
severity: 'critical',
|
|
207
|
+
remediation_zh: '识别请求目的地是否境外大模型端点 (OpenAI/Anthropic/Gemini 等),标记"数据出境"事件;含敏感数据应路由境内已备案模型或先脱敏。',
|
|
208
|
+
remediation_en: 'Detect overseas LLM endpoints, flag data-export events; route sensitive data to domestic models or de-identify first.',
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: 'cbdt-redact-before-export',
|
|
212
|
+
regulation: 'CBDT',
|
|
213
|
+
article: '安全评估 / 标准合同',
|
|
214
|
+
title_zh: '出境前脱敏 / 防裸数据出境',
|
|
215
|
+
title_en: 'De-identify before export / block raw export',
|
|
216
|
+
requirement_zh: '出境数据应最小化并采取加密、去标识化等安全措施,防止敏感个人信息裸数据出境。',
|
|
217
|
+
requirement_en: 'Minimize and de-identify export data; prevent raw sensitive PI from leaving the border.',
|
|
218
|
+
method: 'capability',
|
|
219
|
+
severity: 'high',
|
|
220
|
+
remediation_zh: '启用数据流追踪 + 出境拦截:检测到敏感数据流向境外端点时阻断或脱敏。',
|
|
221
|
+
remediation_en: 'Enable data-flow tracking + export interception to block or de-identify sensitive egress.',
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
// ===== 生成式AI / 内容标识 (GENAI) =====
|
|
225
|
+
{
|
|
226
|
+
id: 'genai-label',
|
|
227
|
+
regulation: 'GENAI',
|
|
228
|
+
article: '标识办法 / GB 45438-2025',
|
|
229
|
+
title_zh: 'AI生成内容标识 (显式 + 元数据)',
|
|
230
|
+
title_en: 'Label AI-generated content (explicit + metadata)',
|
|
231
|
+
requirement_zh: '生成合成内容须添加用户可感知的显式标识及文件元数据隐式标识 (XMP / TC260 命名空间)。',
|
|
232
|
+
requirement_en: 'Add user-visible explicit labels and metadata implicit labels (XMP / TC260 namespace).',
|
|
233
|
+
method: 'manual',
|
|
234
|
+
severity: 'high',
|
|
235
|
+
remediation_zh: '在输出层追加"AI生成"显式标识,导出文件按 GB 45438 写入 XMP 元数据 7 字段(路线图功能)。',
|
|
236
|
+
remediation_en: 'Append explicit "AI-generated" labels; write GB 45438 XMP metadata on export (roadmap).',
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
id: 'genai-content-safety',
|
|
240
|
+
regulation: 'GENAI',
|
|
241
|
+
article: '暂行办法第四条 / 安全基本要求',
|
|
242
|
+
title_zh: '内容安全过滤 (违禁类别 + 拒答率)',
|
|
243
|
+
title_en: 'Content safety filtering (prohibited categories)',
|
|
244
|
+
requirement_zh: '生成内容不得含违法不良信息;安全评估要求应拒答率 ≥95%、误拒率 ≤5%。',
|
|
245
|
+
requirement_en: 'Generated content must exclude prohibited info; refusal rate ≥95%, false-refusal ≤5%.',
|
|
246
|
+
method: 'manual',
|
|
247
|
+
severity: 'high',
|
|
248
|
+
remediation_zh: '接入内容安全过滤引擎对标 31 类违禁内容(路线图功能,可对接国产审核 API)。',
|
|
249
|
+
remediation_en: 'Integrate content-safety filtering for the 31 prohibited categories (roadmap).',
|
|
250
|
+
},
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
/** 按法规分组 */
|
|
254
|
+
export function controlsByRegulation(): Record<Regulation, ComplianceControl[]> {
|
|
255
|
+
const out = {} as Record<Regulation, ComplianceControl[]>
|
|
256
|
+
for (const c of COMPLIANCE_CONTROLS) {
|
|
257
|
+
;(out[c.regulation] ||= []).push(c)
|
|
258
|
+
}
|
|
259
|
+
return out
|
|
260
|
+
}
|