shellward 0.7.2 → 0.7.4
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 +3 -0
- package/dist/compliance/audit.js +3 -2
- package/dist/compliance/html-report.js +1 -1
- package/dist/compliance/project-scan.d.ts +4 -0
- package/dist/compliance/project-scan.js +0 -0
- package/dist/compliance/report.js +2 -2
- package/package.json +2 -1
- package/src/compliance/audit.ts +3 -2
- package/src/compliance/html-report.ts +2 -2
- package/src/compliance/project-scan.ts +0 -0
- package/src/compliance/report.ts +2 -2
package/README.md
CHANGED
|
@@ -390,6 +390,9 @@ Effectiveness is measured, not asserted. `npm run bench` runs every detector ove
|
|
|
390
390
|
| Dangerous commands | 100% | 100% | 100% |
|
|
391
391
|
| PII / secrets | 100% | 100% | 100% |
|
|
392
392
|
| MCP tool poisoning | 100% | 100% | 100% |
|
|
393
|
+
| **Compliance scan** (overseas / secret / PII vs hard negatives) | 100% | 100% | 100% |
|
|
394
|
+
|
|
395
|
+
The compliance scanner has its own gated corpus — `npm run bench:scan` runs the **real `scanProject` pipeline** over 31 labeled cases (17 real risks + 14 hard negatives: domestic endpoints, placeholder keys, doc examples, lock files, invalid checksums). Self-authored corpus, CI-gated against regression.
|
|
393
396
|
|
|
394
397
|
83 gated samples (attacks + hard negatives). Zero-width-interleaved and empty-quote (`r''m`) obfuscation are normalized before matching. The corpus also tracks **5 documented bypasses** (leetspeak, base64, non-zh/en languages, shell variable indirection) that regex/heuristics are not expected to catch — listed explicitly and excluded from the gate rather than hidden.
|
|
395
398
|
|
package/dist/compliance/audit.js
CHANGED
|
@@ -103,11 +103,12 @@ export function runProjectComplianceAudit(config, root) {
|
|
|
103
103
|
const env = gatherEnvFacts();
|
|
104
104
|
// 把文件中实测到的境外端点/依赖并入 facts(按 endpointId 或 provider 去重),
|
|
105
105
|
// 使数据出境项基于真实证据(含 SDK 依赖通道)
|
|
106
|
-
const seen = new Set(env.overseas.map(o => o.endpointId || o.provider_en));
|
|
106
|
+
const seen = new Set(env.overseas.map(o => (o.endpointId || o.provider_en || '').toLowerCase()));
|
|
107
107
|
for (const f of scan.findings) {
|
|
108
108
|
if (f.kind !== 'overseas')
|
|
109
109
|
continue;
|
|
110
|
-
|
|
110
|
+
// 按厂商去重(不区分大小写),避免"端点命中"与"SDK依赖命中"把同一厂商列两次
|
|
111
|
+
const key = (f.provider_en || f.endpointId || '').toLowerCase();
|
|
111
112
|
if (!key || seen.has(key))
|
|
112
113
|
continue;
|
|
113
114
|
seen.add(key);
|
|
@@ -72,7 +72,7 @@ export function renderHtmlReport(report, scan, locale, meta) {
|
|
|
72
72
|
</div>
|
|
73
73
|
</section>`);
|
|
74
74
|
// ===== 项目实测风险 =====
|
|
75
|
-
S.push(sectionHead('🔍', t('项目实测风险', 'Project Scan Findings'), t(`已扫描 ${scan.filesScanned} 个文件${scan.truncated ? '(已达上限)' : ''}
|
|
75
|
+
S.push(sectionHead('🔍', t('项目实测风险', 'Project Scan Findings'), t(`已扫描 ${scan.filesScanned} 个文件${scan.truncated ? '(已达上限)' : ''} · 耗时 ${scan.durationMs ?? '?'}ms · 应用 ${scan.rulesChecked ?? '?'} 条检测规则`, `Scanned ${scan.filesScanned} files${scan.truncated ? ' (limit reached)' : ''} · ${scan.durationMs ?? '?'}ms · ${scan.rulesChecked ?? '?'} detection rules`)));
|
|
76
76
|
if (scan.findings.length === 0) {
|
|
77
77
|
S.push(`<div class="empty">🟢 ${t('未在项目文件中发现硬编码密钥、个人信息暴露或境外端点。', 'No hardcoded secrets, PII, or overseas endpoints found in project files.')}</div>`);
|
|
78
78
|
}
|
|
@@ -18,6 +18,10 @@ export interface ProjectScanResult {
|
|
|
18
18
|
truncated: boolean;
|
|
19
19
|
findings: ProjectFinding[];
|
|
20
20
|
counts: Record<FindingKind, number>;
|
|
21
|
+
/** 扫描耗时(ms) —— 透明度:让用户看到确实在干活 */
|
|
22
|
+
durationMs?: number;
|
|
23
|
+
/** 本次应用的检测规则总数(端点 + SDK 依赖 + 密钥/PII 模式) */
|
|
24
|
+
rulesChecked?: number;
|
|
21
25
|
}
|
|
22
26
|
/** 扫描项目目录,返回真实风险发现 */
|
|
23
27
|
export declare function scanProject(root: string): ProjectScanResult;
|
|
Binary file
|
|
@@ -126,8 +126,8 @@ export function renderProjectFindings(scan, locale) {
|
|
|
126
126
|
L.push(zh ? '## 🔍 项目实测风险' : '## 🔍 Project Scan Findings');
|
|
127
127
|
L.push('');
|
|
128
128
|
L.push(zh
|
|
129
|
-
? `> 已扫描 ${scan.filesScanned} 个文件${scan.truncated ? '
|
|
130
|
-
: `> Scanned ${scan.filesScanned} files${scan.truncated ? ' (limit reached
|
|
129
|
+
? `> 已扫描 ${scan.filesScanned} 个文件${scan.truncated ? '(已达上限)' : ''} · 耗时 ${scan.durationMs ?? '?'}ms · 应用 ${scan.rulesChecked ?? '?'} 条检测规则`
|
|
130
|
+
: `> Scanned ${scan.filesScanned} files${scan.truncated ? ' (limit reached)' : ''} · ${scan.durationMs ?? '?'}ms · ${scan.rulesChecked ?? '?'} detection rules`);
|
|
131
131
|
L.push('');
|
|
132
132
|
if (scan.findings.length === 0) {
|
|
133
133
|
L.push(zh ? '🟢 未在项目文件中发现硬编码密钥、个人信息暴露或境外端点。' : '🟢 No hardcoded secrets, PII exposure, or overseas endpoints found in project files.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shellward",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
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": [
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"test:sdk": "npx tsx test-sdk.ts",
|
|
66
66
|
"test:mcp": "npx tsx test-mcp.ts",
|
|
67
67
|
"bench": "npx tsx bench/run.ts",
|
|
68
|
+
"bench:scan": "npx tsx bench/scan-bench.ts",
|
|
68
69
|
"prepublishOnly": "npm run build"
|
|
69
70
|
},
|
|
70
71
|
"openclaw": {
|
package/src/compliance/audit.ts
CHANGED
|
@@ -171,10 +171,11 @@ export function runProjectComplianceAudit(config: ShellWardConfig, root: string)
|
|
|
171
171
|
|
|
172
172
|
// 把文件中实测到的境外端点/依赖并入 facts(按 endpointId 或 provider 去重),
|
|
173
173
|
// 使数据出境项基于真实证据(含 SDK 依赖通道)
|
|
174
|
-
const seen = new Set(env.overseas.map(o => o.endpointId || o.provider_en))
|
|
174
|
+
const seen = new Set(env.overseas.map(o => (o.endpointId || o.provider_en || '').toLowerCase()))
|
|
175
175
|
for (const f of scan.findings) {
|
|
176
176
|
if (f.kind !== 'overseas') continue
|
|
177
|
-
|
|
177
|
+
// 按厂商去重(不区分大小写),避免"端点命中"与"SDK依赖命中"把同一厂商列两次
|
|
178
|
+
const key = (f.provider_en || f.endpointId || '').toLowerCase()
|
|
178
179
|
if (!key || seen.has(key)) continue
|
|
179
180
|
seen.add(key)
|
|
180
181
|
env.overseas.push({
|
|
@@ -96,8 +96,8 @@ export function renderHtmlReport(
|
|
|
96
96
|
|
|
97
97
|
// ===== 项目实测风险 =====
|
|
98
98
|
S.push(sectionHead('🔍', t('项目实测风险', 'Project Scan Findings'),
|
|
99
|
-
t(`已扫描 ${scan.filesScanned} 个文件${scan.truncated ? '(已达上限)' : ''}
|
|
100
|
-
`Scanned ${scan.filesScanned} files${scan.truncated ? ' (limit reached)' : ''}`)))
|
|
99
|
+
t(`已扫描 ${scan.filesScanned} 个文件${scan.truncated ? '(已达上限)' : ''} · 耗时 ${scan.durationMs ?? '?'}ms · 应用 ${scan.rulesChecked ?? '?'} 条检测规则`,
|
|
100
|
+
`Scanned ${scan.filesScanned} files${scan.truncated ? ' (limit reached)' : ''} · ${scan.durationMs ?? '?'}ms · ${scan.rulesChecked ?? '?'} detection rules`)))
|
|
101
101
|
|
|
102
102
|
if (scan.findings.length === 0) {
|
|
103
103
|
S.push(`<div class="empty">🟢 ${t('未在项目文件中发现硬编码密钥、个人信息暴露或境外端点。',
|
|
Binary file
|
package/src/compliance/report.ts
CHANGED
|
@@ -141,8 +141,8 @@ export function renderProjectFindings(scan: ProjectScanResult, locale: 'zh' | 'e
|
|
|
141
141
|
L.push(zh ? '## 🔍 项目实测风险' : '## 🔍 Project Scan Findings')
|
|
142
142
|
L.push('')
|
|
143
143
|
L.push(zh
|
|
144
|
-
? `> 已扫描 ${scan.filesScanned} 个文件${scan.truncated ? '
|
|
145
|
-
: `> Scanned ${scan.filesScanned} files${scan.truncated ? ' (limit reached
|
|
144
|
+
? `> 已扫描 ${scan.filesScanned} 个文件${scan.truncated ? '(已达上限)' : ''} · 耗时 ${scan.durationMs ?? '?'}ms · 应用 ${scan.rulesChecked ?? '?'} 条检测规则`
|
|
145
|
+
: `> Scanned ${scan.filesScanned} files${scan.truncated ? ' (limit reached)' : ''} · ${scan.durationMs ?? '?'}ms · ${scan.rulesChecked ?? '?'} detection rules`)
|
|
146
146
|
L.push('')
|
|
147
147
|
|
|
148
148
|
if (scan.findings.length === 0) {
|