shellward 0.6.1 → 0.6.2
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 +167 -0
- package/dist/mcp-server.js +36 -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 +189 -0
- package/src/mcp-server.ts +37 -0
- package/src/rules/overseas-llm.ts +174 -0
package/dist/mcp-server.js
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
// }
|
|
22
22
|
import { ShellWard } from './core/engine.js';
|
|
23
23
|
import { McpBaseline } from './mcp-baseline.js';
|
|
24
|
+
import { runComplianceAudit } from './compliance/audit.js';
|
|
25
|
+
import { renderComplianceReport } from './compliance/report.js';
|
|
24
26
|
import { readFileSync } from 'fs';
|
|
25
27
|
import { createInterface } from 'readline';
|
|
26
28
|
import { fileURLToPath } from 'url';
|
|
@@ -140,6 +142,16 @@ const TOOLS = [
|
|
|
140
142
|
properties: {},
|
|
141
143
|
},
|
|
142
144
|
},
|
|
145
|
+
{
|
|
146
|
+
name: 'compliance_check',
|
|
147
|
+
description: 'Run a China AI-compliance health check (网安法/PIPL/等保2.0/数据出境/AI标识) and return a red/yellow/green scorecard report. Detects overseas LLM endpoints (data-export risk), audit-log retention, enabled defense layers, and root execution.',
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: 'object',
|
|
150
|
+
properties: {
|
|
151
|
+
format: { type: 'string', enum: ['report', 'json'], description: 'Output format: "report" (Markdown scorecard, default) or "json" (raw result)' },
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
143
155
|
];
|
|
144
156
|
// ===== Tool Execution =====
|
|
145
157
|
function executeTool(name, args) {
|
|
@@ -263,6 +275,30 @@ function executeTool(name, args) {
|
|
|
263
275
|
],
|
|
264
276
|
};
|
|
265
277
|
}
|
|
278
|
+
case 'compliance_check': {
|
|
279
|
+
const report = runComplianceAudit(guard.config);
|
|
280
|
+
if (args.format === 'json') {
|
|
281
|
+
return {
|
|
282
|
+
score: report.score,
|
|
283
|
+
grade: report.grade,
|
|
284
|
+
passed: report.passed,
|
|
285
|
+
warned: report.warned,
|
|
286
|
+
failed: report.failed,
|
|
287
|
+
manual: report.manual,
|
|
288
|
+
total: report.total,
|
|
289
|
+
results: report.results.map(r => ({
|
|
290
|
+
id: r.control.id,
|
|
291
|
+
regulation: r.control.regulation,
|
|
292
|
+
article: r.control.article,
|
|
293
|
+
title: guard.locale === 'zh' ? r.control.title_zh : r.control.title_en,
|
|
294
|
+
status: r.status,
|
|
295
|
+
detail: guard.locale === 'zh' ? r.detail_zh : r.detail_en,
|
|
296
|
+
})),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
// default: return Markdown report as text
|
|
300
|
+
return { report: renderComplianceReport(report, guard.locale) };
|
|
301
|
+
}
|
|
266
302
|
default:
|
|
267
303
|
throw new Error(`Unknown tool: ${name}`);
|
|
268
304
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface OverseasEndpoint {
|
|
2
|
+
id: string;
|
|
3
|
+
/** 主机名匹配(小写、去端口)。命中其一即视为境外大模型端点 */
|
|
4
|
+
hosts: string[];
|
|
5
|
+
provider_zh: string;
|
|
6
|
+
provider_en: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const OVERSEAS_LLM_ENDPOINTS: OverseasEndpoint[];
|
|
9
|
+
export interface OverseasMatch {
|
|
10
|
+
isOverseas: boolean;
|
|
11
|
+
endpointId?: string;
|
|
12
|
+
provider_zh?: string;
|
|
13
|
+
provider_en?: string;
|
|
14
|
+
host?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 从任意字符串(URL / base_url / 命令行 / 配置)中提取主机名并判断是否境外大模型端点。
|
|
18
|
+
* 同时识别裸 URL 与命令行里的 https://... 形式。
|
|
19
|
+
*/
|
|
20
|
+
export declare function detectOverseasLLM(text: string): OverseasMatch;
|
|
21
|
+
/** 包名(小写)→ 厂商 */
|
|
22
|
+
export declare const OVERSEAS_LLM_PACKAGES: Record<string, {
|
|
23
|
+
zh: string;
|
|
24
|
+
en: string;
|
|
25
|
+
}>;
|
|
26
|
+
export interface DepMatch {
|
|
27
|
+
pkg: string;
|
|
28
|
+
provider_zh: string;
|
|
29
|
+
provider_en: string;
|
|
30
|
+
}
|
|
31
|
+
/** 依赖清单文件名(小写)判定 */
|
|
32
|
+
export declare function isDependencyManifest(filename: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* 从依赖清单内容中提取境外大模型 SDK 依赖。
|
|
35
|
+
* 对 package.json 解析 JSON 的 deps/devDeps/peerDeps;其余按"出现包名"宽松匹配。
|
|
36
|
+
*/
|
|
37
|
+
export declare function detectOverseasDeps(filename: string, content: string): DepMatch[];
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// src/rules/overseas-llm.ts — 境外大模型端点识别(数据出境检测)
|
|
2
|
+
//
|
|
3
|
+
// 中国差异化能力:识别请求是否指向「境外大模型 / 境外 AI 服务」端点。
|
|
4
|
+
// 在中国监管下,向境外大模型 API 发送个人信息/重要数据 = 数据出境,受
|
|
5
|
+
// 《促进数据跨境流动规定》《数据出境安全评估办法》约束。英文工具没有
|
|
6
|
+
// "出境"概念,这是 ShellWard 面向中国市场的护城河之一。
|
|
7
|
+
//
|
|
8
|
+
// 用途:
|
|
9
|
+
// - 体检引擎据此判断"是否存在数据出境风险"
|
|
10
|
+
// - 网关层据此标记"数据出境"事件 / 触发出境前脱敏(路线图)
|
|
11
|
+
//
|
|
12
|
+
// 边界:仅做端点归属判断(不做 IP 归属解析),覆盖主流境外大模型与聚合网关。
|
|
13
|
+
export const OVERSEAS_LLM_ENDPOINTS = [
|
|
14
|
+
{ id: 'openai', hosts: ['api.openai.com', 'openai.com'], provider_zh: 'OpenAI', provider_en: 'OpenAI' },
|
|
15
|
+
{ id: 'anthropic', hosts: ['api.anthropic.com', 'anthropic.com'], provider_zh: 'Anthropic Claude', provider_en: 'Anthropic Claude' },
|
|
16
|
+
{ id: 'google', hosts: ['generativelanguage.googleapis.com', 'aiplatform.googleapis.com'], provider_zh: 'Google Gemini', provider_en: 'Google Gemini' },
|
|
17
|
+
{ id: 'azure-openai', hosts: ['openai.azure.com'], provider_zh: 'Azure OpenAI', provider_en: 'Azure OpenAI' },
|
|
18
|
+
{ id: 'aws-bedrock', hosts: ['bedrock-runtime', 'bedrock.', 'amazonaws.com'], provider_zh: 'AWS Bedrock', provider_en: 'AWS Bedrock' },
|
|
19
|
+
{ id: 'cohere', hosts: ['api.cohere.ai', 'api.cohere.com'], provider_zh: 'Cohere', provider_en: 'Cohere' },
|
|
20
|
+
{ id: 'mistral', hosts: ['api.mistral.ai'], provider_zh: 'Mistral AI', provider_en: 'Mistral AI' },
|
|
21
|
+
{ id: 'groq', hosts: ['api.groq.com'], provider_zh: 'Groq', provider_en: 'Groq' },
|
|
22
|
+
{ id: 'together', hosts: ['api.together.xyz', 'api.together.ai'], provider_zh: 'Together AI', provider_en: 'Together AI' },
|
|
23
|
+
{ id: 'perplexity', hosts: ['api.perplexity.ai'], provider_zh: 'Perplexity', provider_en: 'Perplexity' },
|
|
24
|
+
{ id: 'openrouter', hosts: ['openrouter.ai'], provider_zh: 'OpenRouter (聚合网关)', provider_en: 'OpenRouter (gateway)' },
|
|
25
|
+
{ id: 'huggingface', hosts: ['api-inference.huggingface.co', 'huggingface.co'], provider_zh: 'HuggingFace', provider_en: 'HuggingFace' },
|
|
26
|
+
{ id: 'xai', hosts: ['api.x.ai'], provider_zh: 'xAI Grok', provider_en: 'xAI Grok' },
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* 从任意字符串(URL / base_url / 命令行 / 配置)中提取主机名并判断是否境外大模型端点。
|
|
30
|
+
* 同时识别裸 URL 与命令行里的 https://... 形式。
|
|
31
|
+
*/
|
|
32
|
+
export function detectOverseasLLM(text) {
|
|
33
|
+
if (!text)
|
|
34
|
+
return { isOverseas: false };
|
|
35
|
+
const lower = text.toLowerCase();
|
|
36
|
+
// 提取所有候选主机名:URL 中的 host,或文本中出现的端点关键字
|
|
37
|
+
const hosts = extractHosts(lower);
|
|
38
|
+
for (const ep of OVERSEAS_LLM_ENDPOINTS) {
|
|
39
|
+
for (const h of ep.hosts) {
|
|
40
|
+
// host 列表里带 '.' 或子串形式(如 'bedrock.')做包含匹配,纯域名做后缀/相等匹配
|
|
41
|
+
const hit = hosts.some(host => host === h || host.endsWith('.' + h) || host.includes(h))
|
|
42
|
+
|| lower.includes('://' + h)
|
|
43
|
+
|| lower.includes(h);
|
|
44
|
+
if (hit) {
|
|
45
|
+
return {
|
|
46
|
+
isOverseas: true,
|
|
47
|
+
endpointId: ep.id,
|
|
48
|
+
provider_zh: ep.provider_zh,
|
|
49
|
+
provider_en: ep.provider_en,
|
|
50
|
+
host: h,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { isOverseas: false };
|
|
56
|
+
}
|
|
57
|
+
// ===== 境外大模型 SDK 依赖检测 =====
|
|
58
|
+
// 几乎每个国产 AI 项目的依赖清单里都躺着 openai/anthropic 等境外 SDK —— 这是
|
|
59
|
+
// 一条"数据出境通道",命中率远高于扫 URL 字符串。英文工具不会把它框定为"出境"。
|
|
60
|
+
/** 包名(小写)→ 厂商 */
|
|
61
|
+
export const OVERSEAS_LLM_PACKAGES = {
|
|
62
|
+
// npm
|
|
63
|
+
'openai': { zh: 'OpenAI', en: 'OpenAI' },
|
|
64
|
+
'@anthropic-ai/sdk': { zh: 'Anthropic Claude', en: 'Anthropic Claude' },
|
|
65
|
+
'@google/generative-ai': { zh: 'Google Gemini', en: 'Google Gemini' },
|
|
66
|
+
'@google-cloud/aiplatform': { zh: 'Google Vertex AI', en: 'Google Vertex AI' },
|
|
67
|
+
'cohere-ai': { zh: 'Cohere', en: 'Cohere' },
|
|
68
|
+
'@mistralai/mistralai': { zh: 'Mistral AI', en: 'Mistral AI' },
|
|
69
|
+
'groq-sdk': { zh: 'Groq', en: 'Groq' },
|
|
70
|
+
'@huggingface/inference': { zh: 'HuggingFace', en: 'HuggingFace' },
|
|
71
|
+
'replicate': { zh: 'Replicate', en: 'Replicate' },
|
|
72
|
+
'@langchain/openai': { zh: 'OpenAI (LangChain)', en: 'OpenAI (LangChain)' },
|
|
73
|
+
'@langchain/anthropic': { zh: 'Anthropic (LangChain)', en: 'Anthropic (LangChain)' },
|
|
74
|
+
'@langchain/google-genai': { zh: 'Gemini (LangChain)', en: 'Gemini (LangChain)' },
|
|
75
|
+
// python
|
|
76
|
+
'anthropic': { zh: 'Anthropic Claude', en: 'Anthropic Claude' },
|
|
77
|
+
'google-generativeai': { zh: 'Google Gemini', en: 'Google Gemini' },
|
|
78
|
+
'google-cloud-aiplatform': { zh: 'Google Vertex AI', en: 'Google Vertex AI' },
|
|
79
|
+
'cohere': { zh: 'Cohere', en: 'Cohere' },
|
|
80
|
+
'mistralai': { zh: 'Mistral AI', en: 'Mistral AI' },
|
|
81
|
+
'groq': { zh: 'Groq', en: 'Groq' },
|
|
82
|
+
'together': { zh: 'Together AI', en: 'Together AI' },
|
|
83
|
+
'huggingface-hub': { zh: 'HuggingFace', en: 'HuggingFace' },
|
|
84
|
+
'langchain-openai': { zh: 'OpenAI (LangChain)', en: 'OpenAI (LangChain)' },
|
|
85
|
+
'langchain-anthropic': { zh: 'Anthropic (LangChain)', en: 'Anthropic (LangChain)' },
|
|
86
|
+
'langchain-google-genai': { zh: 'Gemini (LangChain)', en: 'Gemini (LangChain)' },
|
|
87
|
+
// go
|
|
88
|
+
'github.com/sashabaranov/go-openai': { zh: 'OpenAI (Go)', en: 'OpenAI (Go)' },
|
|
89
|
+
'github.com/anthropics/anthropic-sdk-go': { zh: 'Anthropic (Go)', en: 'Anthropic (Go)' },
|
|
90
|
+
};
|
|
91
|
+
/** 依赖清单文件名(小写)判定 */
|
|
92
|
+
export function isDependencyManifest(filename) {
|
|
93
|
+
const f = filename.toLowerCase();
|
|
94
|
+
return f === 'package.json' || f === 'requirements.txt' || f === 'pyproject.toml' || f === 'go.mod';
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 从依赖清单内容中提取境外大模型 SDK 依赖。
|
|
98
|
+
* 对 package.json 解析 JSON 的 deps/devDeps/peerDeps;其余按"出现包名"宽松匹配。
|
|
99
|
+
*/
|
|
100
|
+
export function detectOverseasDeps(filename, content) {
|
|
101
|
+
const f = filename.toLowerCase();
|
|
102
|
+
const names = new Set();
|
|
103
|
+
if (f === 'package.json') {
|
|
104
|
+
try {
|
|
105
|
+
const json = JSON.parse(content);
|
|
106
|
+
for (const field of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
|
|
107
|
+
const deps = json[field];
|
|
108
|
+
if (deps && typeof deps === 'object') {
|
|
109
|
+
for (const k of Object.keys(deps))
|
|
110
|
+
names.add(k.toLowerCase());
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch { /* 解析失败则跳过 */ }
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// requirements.txt / pyproject.toml / go.mod:按行宽松提取包名 token
|
|
118
|
+
for (const raw of content.split('\n')) {
|
|
119
|
+
const line = raw.trim().toLowerCase();
|
|
120
|
+
if (!line || line.startsWith('#') || line.startsWith('//'))
|
|
121
|
+
continue;
|
|
122
|
+
// 提取依赖名:requirements `pkg==x` / pyproject `"pkg>=x"` / go.mod `module vX`
|
|
123
|
+
const tokens = line.match(/[a-z0-9_.\-\/@]+/g) || [];
|
|
124
|
+
for (const t of tokens)
|
|
125
|
+
names.add(t);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const matches = [];
|
|
129
|
+
const seen = new Set();
|
|
130
|
+
for (const [pkg, prov] of Object.entries(OVERSEAS_LLM_PACKAGES)) {
|
|
131
|
+
if (names.has(pkg.toLowerCase()) && !seen.has(pkg)) {
|
|
132
|
+
seen.add(pkg);
|
|
133
|
+
matches.push({ pkg, provider_zh: prov.zh, provider_en: prov.en });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return matches;
|
|
137
|
+
}
|
|
138
|
+
/** 从文本中粗提取主机名(URL host 段) */
|
|
139
|
+
function extractHosts(lowerText) {
|
|
140
|
+
const hosts = [];
|
|
141
|
+
const urlRe = /https?:\/\/([a-z0-9.\-]+)(?::\d+)?/g;
|
|
142
|
+
let m;
|
|
143
|
+
while ((m = urlRe.exec(lowerText)) !== null) {
|
|
144
|
+
hosts.push(m[1]);
|
|
145
|
+
}
|
|
146
|
+
return hosts;
|
|
147
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shellward",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"mcpName": "io.github.jnMetaCode/shellward",
|
|
5
5
|
"description": "AI agent security & MCP security middleware — prompt injection detection, AI firewall, runtime guardrails & data-loss prevention for LLM tool calls. 8-layer defense against data exfiltration & dangerous commands. Zero dependencies. SDK + OpenClaw plugin. Supports LangChain, AutoGPT, Claude Code, Cursor, OpenAI Agents, Hermes Agent.",
|
|
6
6
|
"keywords": [
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"type": "module",
|
|
45
45
|
"bin": {
|
|
46
|
-
"shellward": "dist/
|
|
46
|
+
"shellward": "dist/cli.js",
|
|
47
47
|
"shellward-mcp": "dist/mcp-server.js"
|
|
48
48
|
},
|
|
49
49
|
"main": "dist/index.js",
|
|
@@ -57,8 +57,9 @@
|
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "tsc",
|
|
59
59
|
"mcp": "npx tsx src/mcp-server.ts",
|
|
60
|
-
"test": "npx tsx test-sdk.ts && npx tsx test-integration.ts && npx tsx test-edge-cases.ts && npx tsx test-rugpull.ts && npx tsx test-redos.ts && npx tsx test-mcp-client.ts && npx tsx test-mcp.ts",
|
|
60
|
+
"test": "npx tsx test-sdk.ts && npx tsx test-integration.ts && npx tsx test-edge-cases.ts && npx tsx test-rugpull.ts && npx tsx test-redos.ts && npx tsx test-mcp-client.ts && npx tsx test-mcp.ts && npx tsx test-compliance.ts",
|
|
61
61
|
"test:redos": "npx tsx test-redos.ts",
|
|
62
|
+
"test:compliance": "npx tsx test-compliance.ts",
|
|
62
63
|
"test:integration": "npx tsx test-integration.ts",
|
|
63
64
|
"test:edge": "npx tsx test-edge-cases.ts",
|
|
64
65
|
"test:sdk": "npx tsx test-sdk.ts",
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
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
|
+
|
|
12
|
+
import { resolve } from 'path'
|
|
13
|
+
import { writeFileSync } from 'fs'
|
|
14
|
+
import { ShellWard } from './core/engine.js'
|
|
15
|
+
import { runProjectComplianceAudit } from './compliance/audit.js'
|
|
16
|
+
import { renderComplianceReport, renderProjectFindings } from './compliance/report.js'
|
|
17
|
+
import { resolveLocale } from './types.js'
|
|
18
|
+
|
|
19
|
+
const argv = process.argv.slice(2)
|
|
20
|
+
const wantsHelp = argv.includes('--help') || argv.includes('-h') || argv[0] === 'help'
|
|
21
|
+
const cmd = argv[0] && !argv[0].startsWith('-') ? argv[0] : 'scan'
|
|
22
|
+
|
|
23
|
+
async function main() {
|
|
24
|
+
if (wantsHelp) {
|
|
25
|
+
printHelp()
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (cmd === 'mcp') {
|
|
30
|
+
// 转发到 MCP 服务器(import 即启动 stdio 循环)
|
|
31
|
+
await import('./mcp-server.js')
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (cmd === 'scan') {
|
|
36
|
+
runScan(argv.slice(1))
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.error(`未知命令: ${cmd}\n`)
|
|
41
|
+
printHelp()
|
|
42
|
+
process.exit(2)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function runScan(args: string[]) {
|
|
46
|
+
const json = args.includes('--json')
|
|
47
|
+
const ci = args.includes('--ci')
|
|
48
|
+
const outPath = flagValue(args, '--out')
|
|
49
|
+
const dirArg = args.find(a => !a.startsWith('-'))
|
|
50
|
+
const root = resolve(dirArg || process.cwd())
|
|
51
|
+
|
|
52
|
+
// 用环境变量解析 locale;layers/mode 用默认(代表「采用 ShellWard 默认部署」的合规覆盖)
|
|
53
|
+
const guard = new ShellWard({
|
|
54
|
+
locale: (process.env.SHELLWARD_LOCALE as any) || 'auto',
|
|
55
|
+
mode: (process.env.SHELLWARD_MODE as any) || 'enforce',
|
|
56
|
+
autoCheckOnStartup: false,
|
|
57
|
+
})
|
|
58
|
+
const locale = resolveLocale(guard.config)
|
|
59
|
+
const zh = locale === 'zh'
|
|
60
|
+
|
|
61
|
+
const { report, scan } = runProjectComplianceAudit(guard.config, root)
|
|
62
|
+
|
|
63
|
+
if (json) {
|
|
64
|
+
process.stdout.write(JSON.stringify({
|
|
65
|
+
root,
|
|
66
|
+
score: report.score,
|
|
67
|
+
grade: report.grade,
|
|
68
|
+
summary: { passed: report.passed, warned: report.warned, failed: report.failed, manual: report.manual },
|
|
69
|
+
projectScan: {
|
|
70
|
+
filesScanned: scan.filesScanned,
|
|
71
|
+
truncated: scan.truncated,
|
|
72
|
+
counts: scan.counts,
|
|
73
|
+
findings: scan.findings,
|
|
74
|
+
},
|
|
75
|
+
controls: report.results.map(r => ({
|
|
76
|
+
id: r.control.id, regulation: r.control.regulation, status: r.status,
|
|
77
|
+
})),
|
|
78
|
+
}, null, 2) + '\n')
|
|
79
|
+
} else {
|
|
80
|
+
// 头条:项目实测风险(关于「你的项目」)+ 合规映射评分卡
|
|
81
|
+
const body = [
|
|
82
|
+
renderProjectFindings(scan, locale),
|
|
83
|
+
renderComplianceReport(report, locale),
|
|
84
|
+
].join('\n')
|
|
85
|
+
|
|
86
|
+
if (outPath) {
|
|
87
|
+
const doc = `<!-- 扫描目录: ${root} -->\n\n` + body + '\n'
|
|
88
|
+
writeFileSync(resolve(outPath), doc, 'utf-8')
|
|
89
|
+
process.stdout.write(zh
|
|
90
|
+
? `✅ 合规报告已导出: ${resolve(outPath)}\n 得分 ${report.score}/100 [${report.grade}],可存档用于备案/审计。\n`
|
|
91
|
+
: `✅ Compliance report exported: ${resolve(outPath)}\n Score ${report.score}/100 [${report.grade}].\n`)
|
|
92
|
+
} else {
|
|
93
|
+
const out = [
|
|
94
|
+
zh ? `\n扫描目录: ${root}\n` : `\nScanned: ${root}\n`,
|
|
95
|
+
body,
|
|
96
|
+
'',
|
|
97
|
+
zh
|
|
98
|
+
? '💡 这是只读扫描,未上传任何数据。要在运行时自动拦截风险,把 ShellWard 作为 MCP/插件接入你的 AI Agent。'
|
|
99
|
+
: '💡 Read-only scan, nothing uploaded. To block these risks at runtime, integrate ShellWard as an MCP server/plugin in your AI agent.',
|
|
100
|
+
]
|
|
101
|
+
process.stdout.write(out.join('\n') + '\n')
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// CI 模式:有 critical 项目发现则非零退出
|
|
106
|
+
if (ci) {
|
|
107
|
+
const criticals = scan.findings.filter(f => f.severity === 'critical').length
|
|
108
|
+
if (criticals > 0) process.exit(1)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** 取 `--flag value` 或 `--flag=value` 的值 */
|
|
113
|
+
function flagValue(args: string[], flag: string): string | undefined {
|
|
114
|
+
const i = args.indexOf(flag)
|
|
115
|
+
if (i >= 0 && args[i + 1] && !args[i + 1].startsWith('-')) return args[i + 1]
|
|
116
|
+
const eq = args.find(a => a.startsWith(flag + '='))
|
|
117
|
+
return eq ? eq.slice(flag.length + 1) : undefined
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function printHelp() {
|
|
121
|
+
const lang = (process.env.SHELLWARD_LOCALE === 'en') ? 'en' : 'zh'
|
|
122
|
+
if (lang === 'en') {
|
|
123
|
+
console.log(`ShellWard — AI compliance gateway
|
|
124
|
+
|
|
125
|
+
Usage:
|
|
126
|
+
shellward [scan] [dir] Scan a project for compliance risks (default)
|
|
127
|
+
shellward scan --json Output JSON (for CI)
|
|
128
|
+
shellward scan --ci Exit non-zero if critical findings
|
|
129
|
+
shellward scan --out f Export the full report to a Markdown file
|
|
130
|
+
shellward mcp Start MCP server (stdio)
|
|
131
|
+
shellward --help
|
|
132
|
+
|
|
133
|
+
Detects: overseas LLM endpoints (data-export risk), hardcoded secrets,
|
|
134
|
+
PII in files, .env permissions. Maps to CSL / PIPL / MLPS / cross-border / labeling.`)
|
|
135
|
+
} else {
|
|
136
|
+
console.log(`ShellWard — AI 合规网关
|
|
137
|
+
|
|
138
|
+
用法:
|
|
139
|
+
shellward [scan] [目录] 扫描项目的合规风险(默认命令)
|
|
140
|
+
shellward scan --json 输出 JSON(CI 用)
|
|
141
|
+
shellward scan --ci 有 critical 发现时非零退出
|
|
142
|
+
shellward scan --out 文件 导出完整报告为 Markdown(合规存档)
|
|
143
|
+
shellward mcp 启动 MCP 服务器(stdio)
|
|
144
|
+
shellward --help
|
|
145
|
+
|
|
146
|
+
检测: 境外大模型端点(数据出境)、硬编码密钥、文件中的个人信息、.env 权限。
|
|
147
|
+
映射到 网安法 / PIPL / 等保2.0 / 数据出境 / AI标识。`)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
main().catch(err => {
|
|
152
|
+
console.error(`[ShellWard] ${err?.message || err}`)
|
|
153
|
+
process.exit(1)
|
|
154
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/commands/compliance.ts — /compliance 命令:一键合规体检报告
|
|
2
|
+
//
|
|
3
|
+
// 月1 获客钩子的命令形态。扫一遍配置 + 环境 + 审计日志 + 出境端点,
|
|
4
|
+
// 输出网安法/PIPL/等保/数据出境/AI标识 的红黄绿合规评分卡。
|
|
5
|
+
|
|
6
|
+
import type { ShellWardConfig } from '../types.js'
|
|
7
|
+
import { resolveLocale } from '../types.js'
|
|
8
|
+
import { runComplianceAudit } from '../compliance/audit.js'
|
|
9
|
+
import { renderComplianceReport } from '../compliance/report.js'
|
|
10
|
+
|
|
11
|
+
export function registerComplianceCommand(api: any, config: ShellWardConfig) {
|
|
12
|
+
const locale = resolveLocale(config)
|
|
13
|
+
|
|
14
|
+
api.registerCommand({
|
|
15
|
+
name: 'compliance',
|
|
16
|
+
description: locale === 'zh'
|
|
17
|
+
? '📋 AI 应用合规体检(网安法/PIPL/等保/数据出境/AI标识)'
|
|
18
|
+
: '📋 AI compliance health check (CSL/PIPL/MLPS/Cross-border/Labeling)',
|
|
19
|
+
acceptsArgs: false,
|
|
20
|
+
handler: () => {
|
|
21
|
+
const report = runComplianceAudit(config)
|
|
22
|
+
return { text: renderComplianceReport(report, locale) }
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
}
|
package/src/commands/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { registerScanPluginsCommand } from './scan-plugins.js'
|
|
|
9
9
|
import { registerScanMcpCommand } from './scan-mcp.js'
|
|
10
10
|
import { registerCheckUpdatesCommand } from './check-updates.js'
|
|
11
11
|
import { registerUpgradeOpenClawCommand } from './upgrade-openclaw.js'
|
|
12
|
+
import { registerComplianceCommand } from './compliance.js'
|
|
12
13
|
|
|
13
14
|
/** @returns number of registered commands (for the startup log). */
|
|
14
15
|
export function registerAllCommands(api: any, config: ShellWardConfig): number {
|
|
@@ -22,6 +23,7 @@ export function registerAllCommands(api: any, config: ShellWardConfig): number {
|
|
|
22
23
|
registerScanMcpCommand(api, config)
|
|
23
24
|
registerCheckUpdatesCommand(api, config)
|
|
24
25
|
registerUpgradeOpenClawCommand(api, config)
|
|
26
|
+
registerComplianceCommand(api, config)
|
|
25
27
|
|
|
26
28
|
// Register /cg shortcut with help
|
|
27
29
|
api.registerCommand({
|
|
@@ -35,6 +37,7 @@ export function registerAllCommands(api: any, config: ShellWardConfig): number {
|
|
|
35
37
|
|
|
36
38
|
| 命令 | 说明 |
|
|
37
39
|
|------|------|
|
|
40
|
+
| \`/compliance\` | 🆕 AI 合规体检(网安法/PIPL/等保/数据出境/AI标识 红黄绿评分卡) |
|
|
38
41
|
| \`/security\` | 安全状态总览(防御层、审计统计、系统检查) |
|
|
39
42
|
| \`/audit [数量] [过滤]\` | 查看审计日志 (过滤: block/audit/critical/high) |
|
|
40
43
|
| \`/harden\` | 安全扫描 · \`/harden fix\` 自动修复权限 |
|
|
@@ -50,6 +53,7 @@ L5 安全门 · L6 回复审计 · L7 数据流监控 · L8 会话安全`
|
|
|
50
53
|
|
|
51
54
|
| Command | Description |
|
|
52
55
|
|---------|-------------|
|
|
56
|
+
| \`/compliance\` | 🆕 AI compliance check (CSL/PIPL/MLPS/cross-border/labeling scorecard) |
|
|
53
57
|
| \`/security\` | Security status overview (layers, audit stats, system checks) |
|
|
54
58
|
| \`/audit [count] [filter]\` | View audit log (filter: block/audit/critical/high) |
|
|
55
59
|
| \`/harden\` | Security scan · \`/harden fix\` to auto-fix permissions |
|
|
@@ -64,6 +68,6 @@ L5 Security Gate · L6 Outbound Guard · L7 Data Flow Guard · L8 Session Guard`
|
|
|
64
68
|
}),
|
|
65
69
|
})
|
|
66
70
|
|
|
67
|
-
//
|
|
68
|
-
return
|
|
71
|
+
// 8 individual commands + /cg help
|
|
72
|
+
return 9
|
|
69
73
|
}
|