shellward 0.7.5 → 0.7.6
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.
|
@@ -74,7 +74,14 @@ export function renderHtmlReport(report, scan, locale, meta) {
|
|
|
74
74
|
// ===== 项目实测风险 =====
|
|
75
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
|
+
// 空结果也要展示"检查过程"——逐项列出查了什么、均未命中,证明确实扫了
|
|
78
|
+
S.push(`<div class="empty">🟢 ${t('逐项检查完成,未在可扫描文件中发现风险。', 'All checks passed — no risks found in scannable files.')}</div>`);
|
|
79
|
+
S.push(`<table class="tbl checked"><tbody>
|
|
80
|
+
<tr><td>🌐 ${t('境外大模型端点 + SDK 依赖', 'Overseas LLM endpoints + SDK deps')}</td><td class="muted">${t('OpenAI / Anthropic / Gemini / Cohere… 共 38 个特征', '38 signatures')}</td><td class="right ok">✓ ${t('0 命中', '0 hits')}</td></tr>
|
|
81
|
+
<tr><td>🔑 ${t('硬编码密钥', 'Hardcoded secrets')}</td><td class="muted">${t('OpenAI/GitHub/AWS key、私钥、JWT、口令、连接串', 'OpenAI/GitHub/AWS/private key/JWT/password/conn-string')}</td><td class="right ok">✓ ${t('0 命中', '0 hits')}</td></tr>
|
|
82
|
+
<tr><td>🪪 ${t('中文 PII + 国际 PII', 'Chinese + intl PII')}</td><td class="muted">${t('身份证(校验位)/手机号/银行卡(Luhn)/SSN/信用卡', 'CN ID(checksum)/mobile/UnionPay(Luhn)/SSN/credit card')}</td><td class="right ok">✓ ${t('0 命中', '0 hits')}</td></tr>
|
|
83
|
+
<tr><td>📂 .env ${t('权限', 'permission')}</td><td class="muted">${t('含密钥的 .env 不应组/其他可读', '.env should not be group/other readable')}</td><td class="right ok">✓ ${t('正常', 'OK')}</td></tr>
|
|
84
|
+
</tbody></table>`);
|
|
78
85
|
}
|
|
79
86
|
else {
|
|
80
87
|
S.push('<div class="chips">');
|
|
@@ -258,6 +265,8 @@ section,.reg{padding:0 36px}
|
|
|
258
265
|
padding:0 8px;border-radius:999px;margin-left:4px;font-weight:600}
|
|
259
266
|
.empty{margin:8px 36px;padding:16px 18px;background:var(--pass-bg);color:var(--pass);
|
|
260
267
|
border-radius:10px;font-weight:600;font-size:14px}
|
|
268
|
+
.checked td.ok{color:var(--pass);font-weight:700}
|
|
269
|
+
.checked td:first-child{font-weight:600;white-space:nowrap}
|
|
261
270
|
|
|
262
271
|
/* chips 概览 */
|
|
263
272
|
.chips{display:flex;flex-wrap:wrap;gap:8px;margin:6px 36px 4px}
|
|
@@ -130,7 +130,24 @@ export function renderProjectFindings(scan, locale) {
|
|
|
130
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
|
-
L.push(zh ? '🟢
|
|
133
|
+
L.push(zh ? '🟢 逐项检查完成,未在可扫描文件中发现风险:' : '🟢 All checks passed — no risks found:');
|
|
134
|
+
L.push('');
|
|
135
|
+
if (zh) {
|
|
136
|
+
L.push('| 检查项 | 覆盖 | 结果 |');
|
|
137
|
+
L.push('|---|---|---|');
|
|
138
|
+
L.push('| 🌐 境外大模型端点 + SDK 依赖 | OpenAI/Anthropic/Gemini… 38 个特征 | ✓ 0 命中 |');
|
|
139
|
+
L.push('| 🔑 硬编码密钥 | OpenAI/GitHub/AWS key、私钥、JWT、口令、连接串 | ✓ 0 命中 |');
|
|
140
|
+
L.push('| 🪪 中文+国际 PII | 身份证(校验位)/手机号/银行卡(Luhn)/SSN/信用卡 | ✓ 0 命中 |');
|
|
141
|
+
L.push('| 📂 .env 权限 | 含密钥的 .env 不应组/其他可读 | ✓ 正常 |');
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
L.push('| Check | Coverage | Result |');
|
|
145
|
+
L.push('|---|---|---|');
|
|
146
|
+
L.push('| 🌐 Overseas endpoints + SDK deps | 38 signatures | ✓ 0 hits |');
|
|
147
|
+
L.push('| 🔑 Hardcoded secrets | OpenAI/GitHub/AWS/private key/JWT/password | ✓ 0 hits |');
|
|
148
|
+
L.push('| 🪪 PII (CN + intl) | CN ID/mobile/UnionPay/SSN/credit card | ✓ 0 hits |');
|
|
149
|
+
L.push('| 📂 .env permission | group/other-readable check | ✓ OK |');
|
|
150
|
+
}
|
|
134
151
|
L.push('');
|
|
135
152
|
return L.join('\n');
|
|
136
153
|
}
|
package/dist/web/scan-server.js
CHANGED
|
@@ -52,6 +52,12 @@ export function startWebServer(opts) {
|
|
|
52
52
|
return send(res, 503, 'text/html', errorPage('服务繁忙,请稍后再试'));
|
|
53
53
|
return await handleUpload(req, res, locale, () => { active++; }, () => { active--; });
|
|
54
54
|
}
|
|
55
|
+
// 演示:扫一个内置的「含风险样例项目」——证明"秒出≠没检查"(满屏发现 + 行号)
|
|
56
|
+
if (u.pathname === '/demo') {
|
|
57
|
+
if (active >= MAX_CONCURRENT)
|
|
58
|
+
return send(res, 503, 'text/html', errorPage('服务繁忙,请稍后再试'));
|
|
59
|
+
return handleDemo(res, locale, () => { active++; }, () => { active--; });
|
|
60
|
+
}
|
|
55
61
|
if (u.pathname === '/scan') {
|
|
56
62
|
if (active >= MAX_CONCURRENT) {
|
|
57
63
|
return send(res, 503, 'text/html', errorPage('服务繁忙,请稍后再试(并发上限)'));
|
|
@@ -193,6 +199,36 @@ async function handleUpload(req, res, locale, inc, dec) {
|
|
|
193
199
|
catch { /* ignore */ }
|
|
194
200
|
}
|
|
195
201
|
}
|
|
202
|
+
/** 演示:内置「含风险样例项目」扫描,证明检测真在工作 */
|
|
203
|
+
function handleDemo(res, locale, inc, dec) {
|
|
204
|
+
const dir = mkdtempSync(join(tmpdir(), 'sw-demo-'));
|
|
205
|
+
inc();
|
|
206
|
+
try {
|
|
207
|
+
mkdirSync(join(dir, 'src'), { recursive: true });
|
|
208
|
+
mkdirSync(join(dir, 'data'), { recursive: true });
|
|
209
|
+
writeFileSync(join(dir, 'package.json'), JSON.stringify({
|
|
210
|
+
name: 'demo-ai-app', dependencies: { 'openai': '^4.20.0', '@anthropic-ai/sdk': '^0.20.0', 'express': '^4' },
|
|
211
|
+
}, null, 2));
|
|
212
|
+
writeFileSync(join(dir, 'src', 'config.ts'), 'export const LLM = "https://api.openai.com/v1"\n'
|
|
213
|
+
+ 'const OPENAI_KEY = "sk-Rz9MkP2qWlS7yV3nD8tB1hC4xJ6pQsTuVwYz0"\n'
|
|
214
|
+
+ 'const GITHUB_TOKEN = "ghp_Rz9MkP2qWlS7yV3nD8tB1hC4xJ6pQsTuVwYz"\n'
|
|
215
|
+
+ 'export const ADMIN_PHONE = "13912345678"\n');
|
|
216
|
+
writeFileSync(join(dir, 'data', 'customers.csv'), 'name,id_card,phone,card\n张三,110101199003071233,13800138000,4111111111111111\n');
|
|
217
|
+
writeFileSync(join(dir, '.env'), 'AWS_ACCESS_KEY=AKIARZ9MKP2QWLS7YV3N\nDB_PASSWORD=Sup3rS3cretProdPwd2026\n');
|
|
218
|
+
const { report, scan } = runProjectComplianceAudit(DEFAULT_CONFIG, dir);
|
|
219
|
+
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: '示例项目(含风险)/ demo-ai-app' }));
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
send(res, 500, 'text/html', errorPage('演示失败:' + esc(e?.message || String(e))));
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
dec();
|
|
226
|
+
try {
|
|
227
|
+
rmSync(dir, { recursive: true, force: true });
|
|
228
|
+
}
|
|
229
|
+
catch { /* ignore */ }
|
|
230
|
+
}
|
|
231
|
+
}
|
|
196
232
|
/** 浅克隆公开仓库到临时目录(不鉴权、超时、不执行任何仓库代码) */
|
|
197
233
|
function cloneRepo(url, dir) {
|
|
198
234
|
return new Promise((res, rej) => {
|
|
@@ -238,6 +274,7 @@ function formPage(local) {
|
|
|
238
274
|
<p class="sub">${local ? '选项目文件夹或贴公开仓库链接' : '贴公开仓库链接'},30 秒查出数据出境 / 硬编码密钥 / 个人信息暴露等中国合规红线。</p>
|
|
239
275
|
${uploadForm}
|
|
240
276
|
${urlForm}
|
|
277
|
+
<p class="demo">🤔 觉得"秒出"不真? <a href="/demo">▶ 看一个含风险的示例报告</a>(同样秒出,但满屏发现 + 行号)</p>
|
|
241
278
|
<p class="foot">网安法 2026 · PIPL · 等保2.0 · 数据出境 · AI标识 | 零依赖 · 开源 ·
|
|
242
279
|
<a href="https://github.com/jnMetaCode/shellward">GitHub ⭐</a></p>
|
|
243
280
|
</div>
|
|
@@ -312,6 +349,7 @@ button:disabled{background:#94a3b8;cursor:default}
|
|
|
312
349
|
form{margin:0 0 14px}.or{text-align:center;color:#94a3b8;font-size:13px;margin:6px 0 14px}
|
|
313
350
|
.status{display:none;margin:10px 0 0;padding:10px 14px;border-radius:8px;background:#f1f5f9;
|
|
314
351
|
color:#334155;font-size:13.5px;border-left:3px solid #cb0000;text-align:left}
|
|
352
|
+
.demo{margin:18px 0 0;font-size:13px;color:#475569}.demo a{font-weight:600}
|
|
315
353
|
.foot{margin:24px 0 0;font-size:12.5px;color:#94a3b8}.foot a,.back{color:#cb0000;text-decoration:none}
|
|
316
354
|
.back{font-weight:600}
|
|
317
355
|
</style></head><body>${body}</body></html>`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shellward",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6",
|
|
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": [
|
|
@@ -100,8 +100,14 @@ export function renderHtmlReport(
|
|
|
100
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
|
-
|
|
104
|
-
|
|
103
|
+
// 空结果也要展示"检查过程"——逐项列出查了什么、均未命中,证明确实扫了
|
|
104
|
+
S.push(`<div class="empty">🟢 ${t('逐项检查完成,未在可扫描文件中发现风险。', 'All checks passed — no risks found in scannable files.')}</div>`)
|
|
105
|
+
S.push(`<table class="tbl checked"><tbody>
|
|
106
|
+
<tr><td>🌐 ${t('境外大模型端点 + SDK 依赖', 'Overseas LLM endpoints + SDK deps')}</td><td class="muted">${t('OpenAI / Anthropic / Gemini / Cohere… 共 38 个特征', '38 signatures')}</td><td class="right ok">✓ ${t('0 命中', '0 hits')}</td></tr>
|
|
107
|
+
<tr><td>🔑 ${t('硬编码密钥', 'Hardcoded secrets')}</td><td class="muted">${t('OpenAI/GitHub/AWS key、私钥、JWT、口令、连接串', 'OpenAI/GitHub/AWS/private key/JWT/password/conn-string')}</td><td class="right ok">✓ ${t('0 命中', '0 hits')}</td></tr>
|
|
108
|
+
<tr><td>🪪 ${t('中文 PII + 国际 PII', 'Chinese + intl PII')}</td><td class="muted">${t('身份证(校验位)/手机号/银行卡(Luhn)/SSN/信用卡', 'CN ID(checksum)/mobile/UnionPay(Luhn)/SSN/credit card')}</td><td class="right ok">✓ ${t('0 命中', '0 hits')}</td></tr>
|
|
109
|
+
<tr><td>📂 .env ${t('权限', 'permission')}</td><td class="muted">${t('含密钥的 .env 不应组/其他可读', '.env should not be group/other readable')}</td><td class="right ok">✓ ${t('正常', 'OK')}</td></tr>
|
|
110
|
+
</tbody></table>`)
|
|
105
111
|
} else {
|
|
106
112
|
S.push('<div class="chips">')
|
|
107
113
|
for (const k of KIND_ORDER) if (scan.counts[k] > 0) {
|
|
@@ -296,6 +302,8 @@ section,.reg{padding:0 36px}
|
|
|
296
302
|
padding:0 8px;border-radius:999px;margin-left:4px;font-weight:600}
|
|
297
303
|
.empty{margin:8px 36px;padding:16px 18px;background:var(--pass-bg);color:var(--pass);
|
|
298
304
|
border-radius:10px;font-weight:600;font-size:14px}
|
|
305
|
+
.checked td.ok{color:var(--pass);font-weight:700}
|
|
306
|
+
.checked td:first-child{font-weight:600;white-space:nowrap}
|
|
299
307
|
|
|
300
308
|
/* chips 概览 */
|
|
301
309
|
.chips{display:flex;flex-wrap:wrap;gap:8px;margin:6px 36px 4px}
|
package/src/compliance/report.ts
CHANGED
|
@@ -146,7 +146,23 @@ export function renderProjectFindings(scan: ProjectScanResult, locale: 'zh' | 'e
|
|
|
146
146
|
L.push('')
|
|
147
147
|
|
|
148
148
|
if (scan.findings.length === 0) {
|
|
149
|
-
L.push(zh ? '🟢
|
|
149
|
+
L.push(zh ? '🟢 逐项检查完成,未在可扫描文件中发现风险:' : '🟢 All checks passed — no risks found:')
|
|
150
|
+
L.push('')
|
|
151
|
+
if (zh) {
|
|
152
|
+
L.push('| 检查项 | 覆盖 | 结果 |')
|
|
153
|
+
L.push('|---|---|---|')
|
|
154
|
+
L.push('| 🌐 境外大模型端点 + SDK 依赖 | OpenAI/Anthropic/Gemini… 38 个特征 | ✓ 0 命中 |')
|
|
155
|
+
L.push('| 🔑 硬编码密钥 | OpenAI/GitHub/AWS key、私钥、JWT、口令、连接串 | ✓ 0 命中 |')
|
|
156
|
+
L.push('| 🪪 中文+国际 PII | 身份证(校验位)/手机号/银行卡(Luhn)/SSN/信用卡 | ✓ 0 命中 |')
|
|
157
|
+
L.push('| 📂 .env 权限 | 含密钥的 .env 不应组/其他可读 | ✓ 正常 |')
|
|
158
|
+
} else {
|
|
159
|
+
L.push('| Check | Coverage | Result |')
|
|
160
|
+
L.push('|---|---|---|')
|
|
161
|
+
L.push('| 🌐 Overseas endpoints + SDK deps | 38 signatures | ✓ 0 hits |')
|
|
162
|
+
L.push('| 🔑 Hardcoded secrets | OpenAI/GitHub/AWS/private key/JWT/password | ✓ 0 hits |')
|
|
163
|
+
L.push('| 🪪 PII (CN + intl) | CN ID/mobile/UnionPay/SSN/credit card | ✓ 0 hits |')
|
|
164
|
+
L.push('| 📂 .env permission | group/other-readable check | ✓ OK |')
|
|
165
|
+
}
|
|
150
166
|
L.push('')
|
|
151
167
|
return L.join('\n')
|
|
152
168
|
}
|
package/src/web/scan-server.ts
CHANGED
|
@@ -58,6 +58,11 @@ export function startWebServer(opts: WebServerOptions): void {
|
|
|
58
58
|
if (active >= MAX_CONCURRENT) return send(res, 503, 'text/html', errorPage('服务繁忙,请稍后再试'))
|
|
59
59
|
return await handleUpload(req, res, locale, () => { active++ }, () => { active-- })
|
|
60
60
|
}
|
|
61
|
+
// 演示:扫一个内置的「含风险样例项目」——证明"秒出≠没检查"(满屏发现 + 行号)
|
|
62
|
+
if (u.pathname === '/demo') {
|
|
63
|
+
if (active >= MAX_CONCURRENT) return send(res, 503, 'text/html', errorPage('服务繁忙,请稍后再试'))
|
|
64
|
+
return handleDemo(res, locale, () => { active++ }, () => { active-- })
|
|
65
|
+
}
|
|
61
66
|
if (u.pathname === '/scan') {
|
|
62
67
|
if (active >= MAX_CONCURRENT) {
|
|
63
68
|
return send(res, 503, 'text/html', errorPage('服务繁忙,请稍后再试(并发上限)'))
|
|
@@ -175,6 +180,35 @@ async function handleUpload(req: any, res: any, locale: 'zh' | 'en', inc: () =>
|
|
|
175
180
|
}
|
|
176
181
|
}
|
|
177
182
|
|
|
183
|
+
/** 演示:内置「含风险样例项目」扫描,证明检测真在工作 */
|
|
184
|
+
function handleDemo(res: any, locale: 'zh' | 'en', inc: () => void, dec: () => void) {
|
|
185
|
+
const dir = mkdtempSync(join(tmpdir(), 'sw-demo-'))
|
|
186
|
+
inc()
|
|
187
|
+
try {
|
|
188
|
+
mkdirSync(join(dir, 'src'), { recursive: true })
|
|
189
|
+
mkdirSync(join(dir, 'data'), { recursive: true })
|
|
190
|
+
writeFileSync(join(dir, 'package.json'), JSON.stringify({
|
|
191
|
+
name: 'demo-ai-app', dependencies: { 'openai': '^4.20.0', '@anthropic-ai/sdk': '^0.20.0', 'express': '^4' },
|
|
192
|
+
}, null, 2))
|
|
193
|
+
writeFileSync(join(dir, 'src', 'config.ts'),
|
|
194
|
+
'export const LLM = "https://api.openai.com/v1"\n'
|
|
195
|
+
+ 'const OPENAI_KEY = "sk-Rz9MkP2qWlS7yV3nD8tB1hC4xJ6pQsTuVwYz0"\n'
|
|
196
|
+
+ 'const GITHUB_TOKEN = "ghp_Rz9MkP2qWlS7yV3nD8tB1hC4xJ6pQsTuVwYz"\n'
|
|
197
|
+
+ 'export const ADMIN_PHONE = "13912345678"\n')
|
|
198
|
+
writeFileSync(join(dir, 'data', 'customers.csv'),
|
|
199
|
+
'name,id_card,phone,card\n张三,110101199003071233,13800138000,4111111111111111\n')
|
|
200
|
+
writeFileSync(join(dir, '.env'),
|
|
201
|
+
'AWS_ACCESS_KEY=AKIARZ9MKP2QWLS7YV3N\nDB_PASSWORD=Sup3rS3cretProdPwd2026\n')
|
|
202
|
+
const { report, scan } = runProjectComplianceAudit(DEFAULT_CONFIG, dir)
|
|
203
|
+
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: '示例项目(含风险)/ demo-ai-app' }))
|
|
204
|
+
} catch (e: any) {
|
|
205
|
+
send(res, 500, 'text/html', errorPage('演示失败:' + esc(e?.message || String(e))))
|
|
206
|
+
} finally {
|
|
207
|
+
dec()
|
|
208
|
+
try { rmSync(dir, { recursive: true, force: true }) } catch { /* ignore */ }
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
178
212
|
/** 浅克隆公开仓库到临时目录(不鉴权、超时、不执行任何仓库代码) */
|
|
179
213
|
function cloneRepo(url: string, dir: string): Promise<void> {
|
|
180
214
|
return new Promise((res, rej) => {
|
|
@@ -224,6 +258,7 @@ function formPage(local: boolean): string {
|
|
|
224
258
|
<p class="sub">${local ? '选项目文件夹或贴公开仓库链接' : '贴公开仓库链接'},30 秒查出数据出境 / 硬编码密钥 / 个人信息暴露等中国合规红线。</p>
|
|
225
259
|
${uploadForm}
|
|
226
260
|
${urlForm}
|
|
261
|
+
<p class="demo">🤔 觉得"秒出"不真? <a href="/demo">▶ 看一个含风险的示例报告</a>(同样秒出,但满屏发现 + 行号)</p>
|
|
227
262
|
<p class="foot">网安法 2026 · PIPL · 等保2.0 · 数据出境 · AI标识 | 零依赖 · 开源 ·
|
|
228
263
|
<a href="https://github.com/jnMetaCode/shellward">GitHub ⭐</a></p>
|
|
229
264
|
</div>
|
|
@@ -301,6 +336,7 @@ button:disabled{background:#94a3b8;cursor:default}
|
|
|
301
336
|
form{margin:0 0 14px}.or{text-align:center;color:#94a3b8;font-size:13px;margin:6px 0 14px}
|
|
302
337
|
.status{display:none;margin:10px 0 0;padding:10px 14px;border-radius:8px;background:#f1f5f9;
|
|
303
338
|
color:#334155;font-size:13.5px;border-left:3px solid #cb0000;text-align:left}
|
|
339
|
+
.demo{margin:18px 0 0;font-size:13px;color:#475569}.demo a{font-weight:600}
|
|
304
340
|
.foot{margin:24px 0 0;font-size:12.5px;color:#94a3b8}.foot a,.back{color:#cb0000;text-decoration:none}
|
|
305
341
|
.back{font-weight:600}
|
|
306
342
|
</style></head><body>${body}</body></html>`
|