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 CHANGED
@@ -4,17 +4,40 @@
4
4
 
5
5
  # ShellWard
6
6
 
7
- **AI Agent Security Middleware** — Protect AI agents from prompt injection, data exfiltration, and dangerous command execution. ShellWard acts as an LLM security middleware and AI agent firewall, intercepting tool calls at runtime to enforce agent guardrails before damage is done.
8
-
9
- 8-layer defense-in-depth, DLP-style data flow control, zero dependencies. Works as **standalone SDK** or **OpenClaw plugin**.
7
+ **AI Agent Security & Compliance Gateway** — the AI agent security middleware built for **China's regulatory regime** (网安法 / PIPL / 等保2.0 / 数据出境 / AI标识). Scan your project for compliance risks, then block prompt injection, data exfiltration, and dangerous commands at runtime. Chinese-language threat detection + Chinese PII + zero dependencies — things English tools don't do.
10
8
 
11
9
  [![npm](https://img.shields.io/npm/v/shellward?color=cb0000&label=npm)](https://www.npmjs.com/package/shellward)
12
10
  [![license](https://img.shields.io/badge/license-Apache--2.0-blue)](./LICENSE)
13
- [![tests](https://img.shields.io/badge/tests-183%20passing-brightgreen)](#performance)
11
+ [![tests](https://img.shields.io/badge/tests-242%20passing-brightgreen)](#performance)
14
12
  [![deps](https://img.shields.io/badge/dependencies-0-brightgreen)](#performance)
15
13
 
16
14
  [English](#demo) | [中文](#中文)
17
15
 
16
+ ## 30-Second Compliance Scan
17
+
18
+ Zero install, read-only, nothing uploaded. Scan your AI project for compliance risks right now:
19
+
20
+ ```bash
21
+ npx shellward scan
22
+ ```
23
+
24
+ Outputs a red/yellow/green scorecard mapped to 网安法 / PIPL / 等保2.0 / 数据出境 / AI标识, plus the concrete `file:line` findings in your project:
25
+
26
+ ```
27
+ ## 🔍 项目实测风险
28
+ 🌐 数据出境风险: 2 | 🔑 硬编码密钥: 3 | 🪪 个人信息暴露: 2 | 📂 .env 权限: 1
29
+
30
+ - .env:2 境外大模型端点: OpenAI — 向其发送个人信息即构成数据出境
31
+ - src/config.ts:3 硬编码 GitHub Token: ghp_12*** — 凭据不应写入源码
32
+ - customers.csv:2 手机号 13912*** — 个人信息出现在文件中,需评估脱敏
33
+
34
+ 合规得分: 75/100 [B] 🟢 8 | 🟡 3 | 🔴 1 | ⚪ 2
35
+ ```
36
+
37
+ `npx shellward scan --json` for CI · `--ci` to fail the build on critical findings · see [GitHub Action](#github-action-pr-compliance-gate).
38
+
39
+ > Detects overseas-LLM endpoints (**data-export risk** — a China-only concept English tools ignore), hardcoded secrets, Chinese PII in files, and `.env` exposure. When it finds an overseas model (e.g. an `openai` dependency), it **prescribes domestic compliant alternatives** (通义千问 / DeepSeek / Kimi / 智谱) with their OpenAI-compatible `base_url` — most migrations are just a `base_url` swap.
40
+
18
41
  ## Demo
19
42
 
20
43
  ![ShellWard AI agent firewall demo — blocking prompt injection, data exfiltration, and reverse shell attacks in real time](https://github.com/jnMetaCode/shellward/releases/download/v0.5.0/demo-en.gif)
@@ -113,6 +136,7 @@ If installed globally (`npm i -g shellward`), simply use `"command": "shellward-
113
136
  | `check_response` | Audit AI response for canary leaks & PII exposure |
114
137
  | `scan_mcp_tool` | Scan an MCP tool definition for poisoning + rug-pull |
115
138
  | `security_status` | Get current security config & active layers |
139
+ | `compliance_check` | 🆕 Run a China AI-compliance health check (网安法/PIPL/等保/出境/标识) → red/yellow/green scorecard |
116
140
 
117
141
  **Environment variables:**
118
142
 
@@ -155,6 +179,27 @@ openclaw plugins install shellward
155
179
 
156
180
  Zero config, 8 layers active by default.
157
181
 
182
+ ## GitHub Action (PR Compliance Gate)
183
+
184
+ Block hardcoded secrets and overseas-LLM data-export risk before they merge. Add to `.github/workflows/compliance.yml`:
185
+
186
+ ```yaml
187
+ name: Compliance Scan
188
+ on: [push, pull_request]
189
+ jobs:
190
+ compliance:
191
+ runs-on: ubuntu-latest
192
+ steps:
193
+ - uses: actions/checkout@v4
194
+ - uses: jnMetaCode/shellward@main
195
+ with:
196
+ path: '.'
197
+ fail-on-critical: 'true' # fail the build on critical findings
198
+ locale: 'zh' # auto | zh | en
199
+ ```
200
+
201
+ Or run it directly without the Action: `npx shellward scan --ci`.
202
+
158
203
  ## 8-Layer Defense
159
204
 
160
205
  ```
@@ -298,6 +343,7 @@ Invalid regexes are skipped (never throws), so user input can't break the guard.
298
343
 
299
344
  | Command | Description |
300
345
  |---------|-------------|
346
+ | `/compliance` | 🆕 AI compliance scorecard (网安法/PIPL/等保/出境/标识) |
301
347
  | `/security` | Security status overview |
302
348
  | `/audit [n] [filter]` | View audit log (filter: block, audit, critical, high) |
303
349
  | `/harden` | Scan & fix security issues |
@@ -372,7 +418,34 @@ ShellWard is built for teams that need runtime security for AI agents — whethe
372
418
 
373
419
  ## 中文
374
420
 
375
- **AI Agent 安全中间件**保护 AI 代理免受提示词注入、数据泄露、危险命令执行。8 层纵深防御,零依赖。
421
+ **AI Agent 安全 · 合规网关** 唯一为中国监管(网安法 / PIPL / 等保2.0 / 数据出境 / AI标识 GB45438)和中文语境而生的 AI Agent 安全中间件。先一键体检项目合规风险,再在运行时拦截提示注入、数据外泄与危险命令。中文威胁检测 + 中文 PII + 零依赖——英文工具不做的事。
422
+
423
+ ### 30 秒合规体检
424
+
425
+ 零安装、只读、不上传任何数据。现在就扫你的 AI 项目:
426
+
427
+ ```bash
428
+ npx shellward scan
429
+ ```
430
+
431
+ 输出一张映射到 **网安法 / PIPL / 等保2.0 / 数据出境 / AI标识** 的红黄绿评分卡,并列出项目里 `文件:行` 级别的真实风险:
432
+
433
+ ```
434
+ ## 🔍 项目实测风险
435
+ 🌐 数据出境风险: 2 | 🔑 硬编码密钥: 3 | 🪪 个人信息暴露: 2 | 📂 .env 权限: 1
436
+
437
+ - .env:2 境外大模型端点: OpenAI — 向其发送个人信息即构成数据出境
438
+ - src/config.ts:3 硬编码 GitHub Token: ghp_12*** — 凭据不应写入源码
439
+ - customers.csv:2 手机号 13912*** — 个人信息出现在文件中,需评估脱敏
440
+
441
+ 合规得分: 75/100 [B] 🟢 8 | 🟡 3 | 🔴 1 | ⚪ 2
442
+ ```
443
+
444
+ `--json` 供 CI 消费 · `--ci` 发现 critical 时让构建失败 · 也可作 [GitHub Action](#github-action-pr-compliance-gate) 接入 PR 门禁。
445
+
446
+ > **检测重点**:境外大模型端点(**数据出境风险** — 中国独有、英文工具没有这个概念)、硬编码密钥、文件中的中文 PII、`.env` 暴露。命令形态 `/compliance`,MCP 工具 `compliance_check`。
447
+
448
+ ---
376
449
 
377
450
  ![ShellWard AI Agent 安全防火墙演示 — 拦截提示词注入、数据泄露和反弹Shell攻击](https://github.com/jnMetaCode/shellward/releases/download/v0.5.0/demo-zh.gif)
378
451
 
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ // src/cli.ts — ShellWard CLI 入口(零安装合规体检)
3
+ //
4
+ // npx shellward → 扫描当前项目,输出合规体检评分卡
5
+ // npx shellward scan [dir] → 同上,可指定目录
6
+ // npx shellward scan --json→ 输出 JSON(CI 用)
7
+ // npx shellward mcp → 启动 MCP 服务器(stdio,向后兼容)
8
+ // npx shellward --help
9
+ //
10
+ // 设计目标:30 秒、零配置、出一张可截图的「你的项目」合规风险报告。
11
+ import { resolve } from 'path';
12
+ import { writeFileSync } from 'fs';
13
+ import { ShellWard } from './core/engine.js';
14
+ import { runProjectComplianceAudit } from './compliance/audit.js';
15
+ import { renderComplianceReport, renderProjectFindings } from './compliance/report.js';
16
+ import { resolveLocale } from './types.js';
17
+ const argv = process.argv.slice(2);
18
+ const wantsHelp = argv.includes('--help') || argv.includes('-h') || argv[0] === 'help';
19
+ const cmd = argv[0] && !argv[0].startsWith('-') ? argv[0] : 'scan';
20
+ async function main() {
21
+ if (wantsHelp) {
22
+ printHelp();
23
+ return;
24
+ }
25
+ if (cmd === 'mcp') {
26
+ // 转发到 MCP 服务器(import 即启动 stdio 循环)
27
+ await import('./mcp-server.js');
28
+ return;
29
+ }
30
+ if (cmd === 'scan') {
31
+ runScan(argv.slice(1));
32
+ return;
33
+ }
34
+ console.error(`未知命令: ${cmd}\n`);
35
+ printHelp();
36
+ process.exit(2);
37
+ }
38
+ function runScan(args) {
39
+ const json = args.includes('--json');
40
+ const ci = args.includes('--ci');
41
+ const outPath = flagValue(args, '--out');
42
+ const dirArg = args.find(a => !a.startsWith('-'));
43
+ const root = resolve(dirArg || process.cwd());
44
+ // 用环境变量解析 locale;layers/mode 用默认(代表「采用 ShellWard 默认部署」的合规覆盖)
45
+ const guard = new ShellWard({
46
+ locale: process.env.SHELLWARD_LOCALE || 'auto',
47
+ mode: process.env.SHELLWARD_MODE || 'enforce',
48
+ autoCheckOnStartup: false,
49
+ });
50
+ const locale = resolveLocale(guard.config);
51
+ const zh = locale === 'zh';
52
+ const { report, scan } = runProjectComplianceAudit(guard.config, root);
53
+ if (json) {
54
+ process.stdout.write(JSON.stringify({
55
+ root,
56
+ score: report.score,
57
+ grade: report.grade,
58
+ summary: { passed: report.passed, warned: report.warned, failed: report.failed, manual: report.manual },
59
+ projectScan: {
60
+ filesScanned: scan.filesScanned,
61
+ truncated: scan.truncated,
62
+ counts: scan.counts,
63
+ findings: scan.findings,
64
+ },
65
+ controls: report.results.map(r => ({
66
+ id: r.control.id, regulation: r.control.regulation, status: r.status,
67
+ })),
68
+ }, null, 2) + '\n');
69
+ }
70
+ else {
71
+ // 头条:项目实测风险(关于「你的项目」)+ 合规映射评分卡
72
+ const body = [
73
+ renderProjectFindings(scan, locale),
74
+ renderComplianceReport(report, locale),
75
+ ].join('\n');
76
+ if (outPath) {
77
+ const doc = `<!-- 扫描目录: ${root} -->\n\n` + body + '\n';
78
+ writeFileSync(resolve(outPath), doc, 'utf-8');
79
+ process.stdout.write(zh
80
+ ? `✅ 合规报告已导出: ${resolve(outPath)}\n 得分 ${report.score}/100 [${report.grade}],可存档用于备案/审计。\n`
81
+ : `✅ Compliance report exported: ${resolve(outPath)}\n Score ${report.score}/100 [${report.grade}].\n`);
82
+ }
83
+ else {
84
+ const out = [
85
+ zh ? `\n扫描目录: ${root}\n` : `\nScanned: ${root}\n`,
86
+ body,
87
+ '',
88
+ zh
89
+ ? '💡 这是只读扫描,未上传任何数据。要在运行时自动拦截风险,把 ShellWard 作为 MCP/插件接入你的 AI Agent。'
90
+ : '💡 Read-only scan, nothing uploaded. To block these risks at runtime, integrate ShellWard as an MCP server/plugin in your AI agent.',
91
+ ];
92
+ process.stdout.write(out.join('\n') + '\n');
93
+ }
94
+ }
95
+ // CI 模式:有 critical 项目发现则非零退出
96
+ if (ci) {
97
+ const criticals = scan.findings.filter(f => f.severity === 'critical').length;
98
+ if (criticals > 0)
99
+ process.exit(1);
100
+ }
101
+ }
102
+ /** 取 `--flag value` 或 `--flag=value` 的值 */
103
+ function flagValue(args, flag) {
104
+ const i = args.indexOf(flag);
105
+ if (i >= 0 && args[i + 1] && !args[i + 1].startsWith('-'))
106
+ return args[i + 1];
107
+ const eq = args.find(a => a.startsWith(flag + '='));
108
+ return eq ? eq.slice(flag.length + 1) : undefined;
109
+ }
110
+ function printHelp() {
111
+ const lang = (process.env.SHELLWARD_LOCALE === 'en') ? 'en' : 'zh';
112
+ if (lang === 'en') {
113
+ console.log(`ShellWard — AI compliance gateway
114
+
115
+ Usage:
116
+ shellward [scan] [dir] Scan a project for compliance risks (default)
117
+ shellward scan --json Output JSON (for CI)
118
+ shellward scan --ci Exit non-zero if critical findings
119
+ shellward scan --out f Export the full report to a Markdown file
120
+ shellward mcp Start MCP server (stdio)
121
+ shellward --help
122
+
123
+ Detects: overseas LLM endpoints (data-export risk), hardcoded secrets,
124
+ PII in files, .env permissions. Maps to CSL / PIPL / MLPS / cross-border / labeling.`);
125
+ }
126
+ else {
127
+ console.log(`ShellWard — AI 合规网关
128
+
129
+ 用法:
130
+ shellward [scan] [目录] 扫描项目的合规风险(默认命令)
131
+ shellward scan --json 输出 JSON(CI 用)
132
+ shellward scan --ci 有 critical 发现时非零退出
133
+ shellward scan --out 文件 导出完整报告为 Markdown(合规存档)
134
+ shellward mcp 启动 MCP 服务器(stdio)
135
+ shellward --help
136
+
137
+ 检测: 境外大模型端点(数据出境)、硬编码密钥、文件中的个人信息、.env 权限。
138
+ 映射到 网安法 / PIPL / 等保2.0 / 数据出境 / AI标识。`);
139
+ }
140
+ }
141
+ main().catch(err => {
142
+ console.error(`[ShellWard] ${err?.message || err}`);
143
+ process.exit(1);
144
+ });
@@ -0,0 +1,2 @@
1
+ import type { ShellWardConfig } from '../types.js';
2
+ export declare function registerComplianceCommand(api: any, config: ShellWardConfig): void;
@@ -0,0 +1,21 @@
1
+ // src/commands/compliance.ts — /compliance 命令:一键合规体检报告
2
+ //
3
+ // 月1 获客钩子的命令形态。扫一遍配置 + 环境 + 审计日志 + 出境端点,
4
+ // 输出网安法/PIPL/等保/数据出境/AI标识 的红黄绿合规评分卡。
5
+ import { resolveLocale } from '../types.js';
6
+ import { runComplianceAudit } from '../compliance/audit.js';
7
+ import { renderComplianceReport } from '../compliance/report.js';
8
+ export function registerComplianceCommand(api, config) {
9
+ const locale = resolveLocale(config);
10
+ api.registerCommand({
11
+ name: 'compliance',
12
+ description: locale === 'zh'
13
+ ? '📋 AI 应用合规体检(网安法/PIPL/等保/数据出境/AI标识)'
14
+ : '📋 AI compliance health check (CSL/PIPL/MLPS/Cross-border/Labeling)',
15
+ acceptsArgs: false,
16
+ handler: () => {
17
+ const report = runComplianceAudit(config);
18
+ return { text: renderComplianceReport(report, locale) };
19
+ },
20
+ });
21
+ }
@@ -7,6 +7,7 @@ import { registerScanPluginsCommand } from './scan-plugins.js';
7
7
  import { registerScanMcpCommand } from './scan-mcp.js';
8
8
  import { registerCheckUpdatesCommand } from './check-updates.js';
9
9
  import { registerUpgradeOpenClawCommand } from './upgrade-openclaw.js';
10
+ import { registerComplianceCommand } from './compliance.js';
10
11
  /** @returns number of registered commands (for the startup log). */
11
12
  export function registerAllCommands(api, config) {
12
13
  const locale = resolveLocale(config);
@@ -18,6 +19,7 @@ export function registerAllCommands(api, config) {
18
19
  registerScanMcpCommand(api, config);
19
20
  registerCheckUpdatesCommand(api, config);
20
21
  registerUpgradeOpenClawCommand(api, config);
22
+ registerComplianceCommand(api, config);
21
23
  // Register /cg shortcut with help
22
24
  api.registerCommand({
23
25
  name: 'cg',
@@ -30,6 +32,7 @@ export function registerAllCommands(api, config) {
30
32
 
31
33
  | 命令 | 说明 |
32
34
  |------|------|
35
+ | \`/compliance\` | 🆕 AI 合规体检(网安法/PIPL/等保/数据出境/AI标识 红黄绿评分卡) |
33
36
  | \`/security\` | 安全状态总览(防御层、审计统计、系统检查) |
34
37
  | \`/audit [数量] [过滤]\` | 查看审计日志 (过滤: block/audit/critical/high) |
35
38
  | \`/harden\` | 安全扫描 · \`/harden fix\` 自动修复权限 |
@@ -45,6 +48,7 @@ L5 安全门 · L6 回复审计 · L7 数据流监控 · L8 会话安全`
45
48
 
46
49
  | Command | Description |
47
50
  |---------|-------------|
51
+ | \`/compliance\` | 🆕 AI compliance check (CSL/PIPL/MLPS/cross-border/labeling scorecard) |
48
52
  | \`/security\` | Security status overview (layers, audit stats, system checks) |
49
53
  | \`/audit [count] [filter]\` | View audit log (filter: block/audit/critical/high) |
50
54
  | \`/harden\` | Security scan · \`/harden fix\` to auto-fix permissions |
@@ -58,6 +62,6 @@ L1 Prompt Guard · L2 Output Scanner · L3 Tool Blocker · L4 Input Auditor
58
62
  L5 Security Gate · L6 Outbound Guard · L7 Data Flow Guard · L8 Session Guard`,
59
63
  }),
60
64
  });
61
- // 7 individual commands + /cg help
62
- return 8;
65
+ // 8 individual commands + /cg help
66
+ return 9;
63
67
  }
@@ -0,0 +1,57 @@
1
+ import type { OverseasMatch } from '../rules/overseas-llm.js';
2
+ import type { ProjectScanResult } from './project-scan.js';
3
+ import type { ComplianceControl, Regulation } from './regulations.js';
4
+ import type { ShellWardConfig } from '../types.js';
5
+ export type ControlStatus = 'pass' | 'warn' | 'fail' | 'manual';
6
+ export interface ControlResult {
7
+ control: ComplianceControl;
8
+ status: ControlStatus;
9
+ detail_zh: string;
10
+ detail_en: string;
11
+ }
12
+ export interface AuditLogFacts {
13
+ exists: boolean;
14
+ entryCount: number;
15
+ /** 最早一条记录时间戳 (ISO),用于判断是否覆盖 6 个月留存 */
16
+ oldestTs?: string;
17
+ newestTs?: string;
18
+ }
19
+ export interface EnvFacts {
20
+ isRoot: boolean;
21
+ auditLog: AuditLogFacts;
22
+ /** 从环境/配置中探测到的境外大模型端点 */
23
+ overseas: OverseasMatch[];
24
+ }
25
+ export interface ComplianceReport {
26
+ /** 0-100 合规得分 */
27
+ score: number;
28
+ /** 总评级 */
29
+ grade: 'A' | 'B' | 'C' | 'D';
30
+ passed: number;
31
+ warned: number;
32
+ failed: number;
33
+ manual: number;
34
+ total: number;
35
+ results: ControlResult[];
36
+ generatedAt: string;
37
+ /** 项目实测风险造成的扣分(仅项目体检路径);0 表示纯控制项评分 */
38
+ projectPenalty?: number;
39
+ }
40
+ /** 采集真实环境事实(运行时调用;测试可绕过直接注入 EnvFacts) */
41
+ export declare function gatherEnvFacts(): EnvFacts;
42
+ /**
43
+ * 运行合规体检。
44
+ * @param config ShellWard 当前配置
45
+ * @param facts 环境事实;不传则从真实环境采集
46
+ */
47
+ export declare function runComplianceAudit(config: ShellWardConfig, facts?: EnvFacts): ComplianceReport;
48
+ export interface ProjectComplianceResult {
49
+ report: ComplianceReport;
50
+ scan: ProjectScanResult;
51
+ }
52
+ /**
53
+ * 面向真实项目的体检:扫描项目目录的真实风险,并入评分,再跑控制项体检。
54
+ * 这是 CLI (`shellward scan`) 的入口 —— 报告关于「用户项目」而非「ShellWard 开关」。
55
+ */
56
+ export declare function runProjectComplianceAudit(config: ShellWardConfig, root: string): ProjectComplianceResult;
57
+ export type { Regulation };
@@ -0,0 +1,234 @@
1
+ // src/compliance/audit.ts — 合规体检引擎
2
+ //
3
+ // 跑遍 COMPLIANCE_CONTROLS,对每个控制项给出 pass / warn / fail / manual,
4
+ // 汇总成红黄绿评分卡。这是「一键合规体检报告」(月1 获客钩子) 的核心。
5
+ //
6
+ // 设计为可注入 (EnvFacts):测试可直接喂事实,运行时则从真实环境采集。
7
+ import { readFileSync, statSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { getHomeDir } from '../utils.js';
10
+ import { detectOverseasLLM } from '../rules/overseas-llm.js';
11
+ import { scanProject } from './project-scan.js';
12
+ import { COMPLIANCE_CONTROLS } from './regulations.js';
13
+ const LOG_FILE = join(getHomeDir(), '.openclaw', 'shellward', 'audit.jsonl');
14
+ const SIX_MONTHS_MS = 182 * 24 * 60 * 60 * 1000;
15
+ /** 层能力映射:控制项 id → 必须启用的层(全部启用才 pass,部分启用 warn,全关 fail) */
16
+ const CAPABILITY_LAYERS = {
17
+ 'csl-content-block': ['outputScanner', 'outboundGuard'],
18
+ 'csl-intrusion': ['inputAuditor', 'toolBlocker'],
19
+ 'pipl-spi-detect': ['outputScanner'],
20
+ 'pipl-minimize': ['dataFlowGuard'],
21
+ 'pipl-auto-decision': ['securityGate'],
22
+ 'mlps-access-control': ['securityGate'],
23
+ 'cbdt-redact-before-export': ['dataFlowGuard'],
24
+ };
25
+ /** 采集真实环境事实(运行时调用;测试可绕过直接注入 EnvFacts) */
26
+ export function gatherEnvFacts() {
27
+ // 1. root 检测
28
+ const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
29
+ // 2. 审计日志事实
30
+ const auditLog = readAuditLogFacts();
31
+ // 3. 出境端点探测:扫描常见环境变量中的 base_url / 端点
32
+ const overseas = [];
33
+ const seen = new Set();
34
+ for (const [k, v] of Object.entries(process.env)) {
35
+ if (!v)
36
+ continue;
37
+ if (!/(_BASE_URL|_API_BASE|_ENDPOINT|_URL|OPENAI|ANTHROPIC|GEMINI|LLM)/i.test(k))
38
+ continue;
39
+ const m = detectOverseasLLM(v);
40
+ if (m.isOverseas && m.endpointId && !seen.has(m.endpointId)) {
41
+ seen.add(m.endpointId);
42
+ overseas.push(m);
43
+ }
44
+ }
45
+ return { isRoot, auditLog, overseas };
46
+ }
47
+ function readAuditLogFacts() {
48
+ try {
49
+ statSync(LOG_FILE);
50
+ const content = readFileSync(LOG_FILE, 'utf-8');
51
+ const lines = content.trim().split('\n').filter(Boolean);
52
+ if (lines.length === 0)
53
+ return { exists: true, entryCount: 0 };
54
+ const firstTs = extractTs(lines[0]);
55
+ const lastTs = extractTs(lines[lines.length - 1]);
56
+ return { exists: true, entryCount: lines.length, oldestTs: firstTs, newestTs: lastTs };
57
+ }
58
+ catch {
59
+ return { exists: false, entryCount: 0 };
60
+ }
61
+ }
62
+ function extractTs(line) {
63
+ const m = line.match(/"ts":"([^"]+)"/);
64
+ return m?.[1];
65
+ }
66
+ /**
67
+ * 运行合规体检。
68
+ * @param config ShellWard 当前配置
69
+ * @param facts 环境事实;不传则从真实环境采集
70
+ */
71
+ export function runComplianceAudit(config, facts) {
72
+ const env = facts ?? gatherEnvFacts();
73
+ const results = COMPLIANCE_CONTROLS.map(c => checkControl(c, config, env));
74
+ let passed = 0, warned = 0, failed = 0, manual = 0;
75
+ for (const r of results) {
76
+ if (r.status === 'pass')
77
+ passed++;
78
+ else if (r.status === 'warn')
79
+ warned++;
80
+ else if (r.status === 'fail')
81
+ failed++;
82
+ else
83
+ manual++;
84
+ }
85
+ const score = computeScore(results);
86
+ return {
87
+ score,
88
+ grade: gradeOf(score),
89
+ passed, warned, failed, manual,
90
+ total: results.length,
91
+ results,
92
+ generatedAt: new Date().toISOString(),
93
+ };
94
+ }
95
+ /**
96
+ * 面向真实项目的体检:扫描项目目录的真实风险,并入评分,再跑控制项体检。
97
+ * 这是 CLI (`shellward scan`) 的入口 —— 报告关于「用户项目」而非「ShellWard 开关」。
98
+ */
99
+ export function runProjectComplianceAudit(config, root) {
100
+ const scan = scanProject(root);
101
+ const env = gatherEnvFacts();
102
+ // 把文件中实测到的境外端点/依赖并入 facts(按 endpointId 或 provider 去重),
103
+ // 使数据出境项基于真实证据(含 SDK 依赖通道)
104
+ const seen = new Set(env.overseas.map(o => o.endpointId || o.provider_en));
105
+ for (const f of scan.findings) {
106
+ if (f.kind !== 'overseas')
107
+ continue;
108
+ const key = f.endpointId || f.provider_en || '';
109
+ if (!key || seen.has(key))
110
+ continue;
111
+ seen.add(key);
112
+ env.overseas.push({
113
+ isOverseas: true,
114
+ endpointId: f.endpointId,
115
+ provider_zh: f.provider_zh,
116
+ provider_en: f.provider_en,
117
+ });
118
+ }
119
+ const report = runComplianceAudit(config, env);
120
+ // 发现驱动评分:项目实测风险按严重度扣分(封顶 40),使分数反映"你的真实风险"
121
+ const penalty = computeProjectPenalty(scan);
122
+ if (penalty > 0) {
123
+ report.score = Math.max(0, report.score - penalty);
124
+ report.grade = gradeOf(report.score);
125
+ report.projectPenalty = penalty;
126
+ }
127
+ return { report, scan };
128
+ }
129
+ const FINDING_PENALTY = { critical: 8, high: 4, medium: 1 };
130
+ const MAX_PROJECT_PENALTY = 40;
131
+ function computeProjectPenalty(scan) {
132
+ let p = 0;
133
+ for (const f of scan.findings)
134
+ p += FINDING_PENALTY[f.severity];
135
+ return Math.min(MAX_PROJECT_PENALTY, p);
136
+ }
137
+ function checkControl(c, config, env) {
138
+ switch (c.method) {
139
+ case 'capability': return checkCapability(c, config);
140
+ case 'config': return checkConfig(c, config);
141
+ case 'audit': return checkAudit(c, env);
142
+ case 'env': return checkEnv(c, env);
143
+ case 'manual': return mk(c, 'manual', '需人工确认 / 路线图功能:' + c.remediation_zh, 'Manual / roadmap: ' + c.remediation_en);
144
+ }
145
+ }
146
+ function checkCapability(c, config) {
147
+ const required = CAPABILITY_LAYERS[c.id];
148
+ if (!required) {
149
+ // 未显式映射的能力项:以 enforce 模式作为兜底信号
150
+ return config.mode === 'enforce'
151
+ ? mk(c, 'pass', '能力已启用 (enforce 模式)', 'Capability active (enforce mode)')
152
+ : mk(c, 'warn', 'audit 模式仅记录不拦截,建议切换 enforce', 'Audit mode logs only; switch to enforce');
153
+ }
154
+ const on = required.filter(l => config.layers[l]);
155
+ if (on.length === required.length) {
156
+ const tail = config.mode === 'enforce' ? '' : '(注意:audit 模式仅记录不拦截)';
157
+ return mk(c, config.mode === 'enforce' ? 'pass' : 'warn', `已启用: ${required.join(', ')}${tail}`, `Enabled: ${required.join(', ')}${config.mode === 'enforce' ? '' : ' (audit mode: log-only)'}`);
158
+ }
159
+ if (on.length > 0) {
160
+ return mk(c, 'warn', `部分启用: ${on.join(', ')};缺少: ${required.filter(l => !on.includes(l)).join(', ')}`, `Partially enabled; missing: ${required.filter(l => !on.includes(l)).join(', ')}`);
161
+ }
162
+ return mk(c, 'fail', `未启用: ${required.join(', ')}`, `Not enabled: ${required.join(', ')}`);
163
+ }
164
+ function checkConfig(c, config) {
165
+ return config.mode === 'enforce'
166
+ ? mk(c, 'pass', 'enforce 模式', 'enforce mode')
167
+ : mk(c, 'warn', 'audit 模式仅记录', 'audit mode logs only');
168
+ }
169
+ function checkAudit(c, env) {
170
+ const a = env.auditLog;
171
+ if (!a.exists || a.entryCount === 0) {
172
+ return mk(c, 'fail', '未发现审计日志或日志为空 — 无法满足留存与举证要求', 'No audit log found or empty — retention/evidence requirement unmet');
173
+ }
174
+ // 判断留存跨度是否覆盖 6 个月
175
+ if (a.oldestTs) {
176
+ const span = Date.now() - new Date(a.oldestTs).getTime();
177
+ if (span >= SIX_MONTHS_MS) {
178
+ return mk(c, 'pass', `审计日志 ${a.entryCount} 条,最早 ${a.oldestTs.slice(0, 10)},已覆盖 ≥6 个月`, `${a.entryCount} entries since ${a.oldestTs.slice(0, 10)}, ≥6 months covered`);
179
+ }
180
+ }
181
+ return mk(c, 'warn', `审计日志已启用 (${a.entryCount} 条),但留存尚未满 6 个月 — 需持续运行积累`, `Audit log active (${a.entryCount} entries) but <6 months retained — keep running`);
182
+ }
183
+ function checkEnv(c, env) {
184
+ if (c.id === 'mlps-not-root') {
185
+ return env.isRoot
186
+ ? mk(c, 'fail', '正在以 root 运行 — 违反最小权限原则', 'Running as root — violates least privilege')
187
+ : mk(c, 'pass', '非 root 运行', 'Not running as root');
188
+ }
189
+ if (c.id === 'cbdt-overseas-llm') {
190
+ if (env.overseas.length > 0) {
191
+ const names = env.overseas.map(o => o.provider_zh).join(', ');
192
+ const namesEn = env.overseas.map(o => o.provider_en).join(', ');
193
+ return mk(c, 'fail', `检测到境外大模型端点配置: ${names} — 若向其发送个人信息/重要数据即构成数据出境,须走合规路径`, `Overseas LLM endpoint(s) configured: ${namesEn} — sending PI/important data = cross-border export`);
194
+ }
195
+ return mk(c, 'pass', '未检测到境外大模型端点配置', 'No overseas LLM endpoint detected');
196
+ }
197
+ return mk(c, 'manual', '需人工确认', 'Manual check required');
198
+ }
199
+ function mk(control, status, detail_zh, detail_en) {
200
+ return { control, status, detail_zh, detail_en };
201
+ }
202
+ // ===== 评分 =====
203
+ const SEVERITY_WEIGHT = {
204
+ critical: 4, high: 3, medium: 2, low: 1,
205
+ };
206
+ /**
207
+ * 加权得分:manual 项不计入分母(不惩罚路线图/人工项)。
208
+ * pass=满分, warn=半分, fail=0。
209
+ */
210
+ function computeScore(results) {
211
+ let earned = 0, possible = 0;
212
+ for (const r of results) {
213
+ if (r.status === 'manual')
214
+ continue;
215
+ const w = SEVERITY_WEIGHT[r.control.severity];
216
+ possible += w;
217
+ if (r.status === 'pass')
218
+ earned += w;
219
+ else if (r.status === 'warn')
220
+ earned += w * 0.5;
221
+ }
222
+ if (possible === 0)
223
+ return 0;
224
+ return Math.round((earned / possible) * 100);
225
+ }
226
+ function gradeOf(score) {
227
+ if (score >= 90)
228
+ return 'A';
229
+ if (score >= 75)
230
+ return 'B';
231
+ if (score >= 60)
232
+ return 'C';
233
+ return 'D';
234
+ }
@@ -0,0 +1,23 @@
1
+ export type FindingKind = 'overseas' | 'secret' | 'pii' | 'env-perm';
2
+ export interface ProjectFinding {
3
+ kind: FindingKind;
4
+ /** 相对项目根的路径 */
5
+ file: string;
6
+ line?: number;
7
+ /** 人类可读结论 */
8
+ detail: string;
9
+ severity: 'critical' | 'high' | 'medium';
10
+ /** 仅 overseas:命中的境外端点信息,供体检评分并入 */
11
+ endpointId?: string;
12
+ provider_zh?: string;
13
+ provider_en?: string;
14
+ }
15
+ export interface ProjectScanResult {
16
+ root: string;
17
+ filesScanned: number;
18
+ truncated: boolean;
19
+ findings: ProjectFinding[];
20
+ counts: Record<FindingKind, number>;
21
+ }
22
+ /** 扫描项目目录,返回真实风险发现 */
23
+ export declare function scanProject(root: string): ProjectScanResult;