shellward 0.7.11 → 0.7.13
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/dist/compliance/audit.js +3 -2
- package/dist/compliance/html-report.js +19 -1
- package/dist/compliance/project-scan.js +0 -0
- package/dist/compliance/report.js +1 -1
- package/dist/web/scan-server.js +22 -13
- package/package.json +1 -1
- package/src/compliance/audit.ts +5 -4
- package/src/compliance/html-report.ts +23 -1
- package/src/compliance/project-scan.ts +0 -0
- package/src/compliance/report.ts +1 -1
- package/src/web/scan-server.ts +23 -13
package/dist/compliance/audit.js
CHANGED
|
@@ -236,9 +236,10 @@ function checkEnv(c, env) {
|
|
|
236
236
|
if (env.overseas.length > 0) {
|
|
237
237
|
const names = env.overseas.map(o => o.provider_zh).join(', ');
|
|
238
238
|
const namesEn = env.overseas.map(o => o.provider_en).join(', ');
|
|
239
|
-
|
|
239
|
+
// 检出境外调用是"事实",是否违规取决于是否发送 PII/重要数据 → 标"需评估"而非"不合规"
|
|
240
|
+
return mk(c, 'warn', `检测到境外大模型调用: ${names}。是否违规取决于发送的数据:涉及个人信息/重要数据需走合规路径或改用境内模型;仅非个人/非重要数据通常可接受。`, `Overseas LLM detected: ${namesEn}. Compliance depends on the data sent — PI/important data needs a compliant path; non-personal data is usually fine.`);
|
|
240
241
|
}
|
|
241
|
-
return mk(c, 'pass', '
|
|
242
|
+
return mk(c, 'pass', '未检测到境外大模型调用', 'No overseas LLM detected');
|
|
242
243
|
}
|
|
243
244
|
return mk(c, 'manual', '需人工确认', 'Manual check required');
|
|
244
245
|
}
|
|
@@ -14,7 +14,7 @@ const STATUS = {
|
|
|
14
14
|
manual: { zh: '待确认', en: 'Review', cls: 'manual' },
|
|
15
15
|
};
|
|
16
16
|
const KIND = {
|
|
17
|
-
overseas: { zh: '
|
|
17
|
+
overseas: { zh: '境外大模型调用(需评估出境)', en: 'Overseas LLM (assess export)', icon: '🌐' },
|
|
18
18
|
secret: { zh: '硬编码密钥', en: 'Hardcoded secret', icon: '🔑' },
|
|
19
19
|
pii: { zh: '个人信息暴露', en: 'PII exposure', icon: '🪪' },
|
|
20
20
|
'env-perm': { zh: '.env 权限', en: '.env permission', icon: '📂' },
|
|
@@ -161,6 +161,18 @@ export function renderHtmlReport(report, scan, locale, meta) {
|
|
|
161
161
|
}
|
|
162
162
|
S.push('</tbody></table></div>');
|
|
163
163
|
}
|
|
164
|
+
// ===== 接入运行时指引:把 ⚪ 待核验项变成可验证(回答"需要接入才能测")=====
|
|
165
|
+
if (report.staticScan && report.manual > 0) {
|
|
166
|
+
const cfg = `{
|
|
167
|
+
"mcpServers": {
|
|
168
|
+
"shellward": { "command": "npx", "args": ["-y", "-p", "shellward", "shellward-mcp"] }
|
|
169
|
+
}
|
|
170
|
+
}`;
|
|
171
|
+
S.push(sectionHead('🔌', t('让待核验项可验证:接入运行时', 'Make ⚪ items verifiable: deploy runtime'), t('静态扫描测不了运行时行为,接入后这些项才能被持续验证', 'Static scan cannot test runtime behavior; deploy to validate')));
|
|
172
|
+
S.push(`<p class="note">${t(`把下面这段加到 Claude Desktop / Cursor / OpenClaw 的 MCP 配置,重启后 ShellWard 就作为<b>运行时防护</b>在你的 AI 应用里运行——上方 ${report.manual} 项(审计留存、运行时拦截、数据外发管控等)即可被真实验证。`, `Add this to your Claude Desktop / Cursor / OpenClaw MCP config and restart — ShellWard then runs as a <b>runtime guard</b>, validating the ${report.manual} ⚪ items above.`)}</p>`);
|
|
173
|
+
S.push(`<div class="cfg"><pre id="mcpcfg">${esc(cfg)}</pre><button onclick="(function(b){navigator.clipboard&&navigator.clipboard.writeText(document.getElementById('mcpcfg').textContent).then(function(){b.textContent='${t('已复制', 'Copied')}'})})(this)">${t('复制', 'Copy')}</button></div>`);
|
|
174
|
+
S.push(`<p class="muted">${t('或命令行直接起:', 'Or run directly:')} <code>npx shellward mcp</code> · ${t('文档', 'Docs')}: <a href="https://github.com/jnMetaCode/shellward">github.com/jnMetaCode/shellward</a></p>`);
|
|
175
|
+
}
|
|
164
176
|
const disclaimer = t('本报告由 ShellWard 合规网关自动生成,帮助评估并满足合规技术要求,不构成法律意见,亦不替代算法备案/定级备案/PIA 等主体责任。⚪ 待确认项需结合业务人工判定。', 'Generated by ShellWard Compliance Gateway. Assists with technical compliance; not legal advice. ⚪ items require manual review.');
|
|
165
177
|
return `<!DOCTYPE html>
|
|
166
178
|
<html lang="${zh ? 'zh-CN' : 'en'}">
|
|
@@ -323,6 +335,12 @@ table.tbl td.right{width:64px}
|
|
|
323
335
|
.manual-note{margin:8px 36px 12px;font-size:13px;color:#475569;background:#eff6ff;
|
|
324
336
|
border-left:3px solid #3b82f6;line-height:1.6}
|
|
325
337
|
.manual-note code{background:#dbeafe;padding:1px 6px;border-radius:5px}
|
|
338
|
+
.cfg{position:relative;margin:8px 36px}
|
|
339
|
+
.cfg pre{background:#0f172a;color:#e2e8f0;border-radius:10px;padding:16px 18px;margin:0;
|
|
340
|
+
font-family:ui-monospace,Menlo,monospace;font-size:12.5px;overflow-x:auto;line-height:1.5}
|
|
341
|
+
.cfg button{position:absolute;top:10px;right:12px;background:#1e293b;color:#94a3b8;border:0;
|
|
342
|
+
border-radius:6px;padding:5px 12px;font-size:12px;cursor:pointer}
|
|
343
|
+
.cfg button:hover{color:#fff}
|
|
326
344
|
|
|
327
345
|
/* 法规分组 */
|
|
328
346
|
.reg{margin:14px 36px;padding:0;border:1px solid var(--line);border-radius:12px;overflow:hidden}
|
|
Binary file
|
|
@@ -116,7 +116,7 @@ export function renderComplianceReport(report, locale) {
|
|
|
116
116
|
return L.join('\n');
|
|
117
117
|
}
|
|
118
118
|
const KIND_LABEL = {
|
|
119
|
-
overseas: { zh: '
|
|
119
|
+
overseas: { zh: '境外大模型调用(需评估出境)', en: 'Overseas LLM (assess export)', icon: '🌐' },
|
|
120
120
|
secret: { zh: '硬编码密钥', en: 'Hardcoded secret', icon: '🔑' },
|
|
121
121
|
pii: { zh: '个人信息暴露', en: 'PII exposure', icon: '🪪' },
|
|
122
122
|
'env-perm': { zh: '.env 权限', en: '.env permission', icon: '📂' },
|
package/dist/web/scan-server.js
CHANGED
|
@@ -34,9 +34,12 @@ export function validateRepoUrl(input) {
|
|
|
34
34
|
return { ok: false, reason: '仅支持 github.com / gitlab.com / gitee.com / bitbucket.org 的公开仓库 URL' };
|
|
35
35
|
return { ok: true, url };
|
|
36
36
|
}
|
|
37
|
+
// 报告「返回」链接:本地模式用绝对地址(上传报告经 blob: URL 打开,相对 '/' 会失效)
|
|
38
|
+
let SERVER_BASE = '/';
|
|
37
39
|
export function startWebServer(opts) {
|
|
38
40
|
const locale = resolveLocale(DEFAULT_CONFIG);
|
|
39
41
|
const host = opts.local ? '127.0.0.1' : '0.0.0.0';
|
|
42
|
+
SERVER_BASE = opts.local ? `http://localhost:${opts.port}/` : '/';
|
|
40
43
|
let active = 0;
|
|
41
44
|
const server = createServer(async (req, res) => {
|
|
42
45
|
try {
|
|
@@ -110,7 +113,7 @@ async function handleRepo(res, repo, locale, inc, dec) {
|
|
|
110
113
|
try {
|
|
111
114
|
await cloneRepo(v.url, dir);
|
|
112
115
|
const { report, scan } = runProjectComplianceAudit(DEFAULT_CONFIG, dir);
|
|
113
|
-
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: v.url, backLink:
|
|
116
|
+
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: v.url, backLink: SERVER_BASE }));
|
|
114
117
|
}
|
|
115
118
|
catch (e) {
|
|
116
119
|
const msg = esc(e?.message || String(e));
|
|
@@ -133,7 +136,7 @@ async function handleLocal(res, path, locale, inc, dec) {
|
|
|
133
136
|
inc();
|
|
134
137
|
try {
|
|
135
138
|
const { report, scan } = runProjectComplianceAudit(DEFAULT_CONFIG, root);
|
|
136
|
-
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root, backLink:
|
|
139
|
+
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root, backLink: SERVER_BASE }));
|
|
137
140
|
}
|
|
138
141
|
finally {
|
|
139
142
|
dec();
|
|
@@ -192,7 +195,7 @@ async function handleUpload(req, res, locale, inc, dec) {
|
|
|
192
195
|
}
|
|
193
196
|
const { report, scan } = runProjectComplianceAudit(DEFAULT_CONFIG, dir);
|
|
194
197
|
const rootName = typeof payload.root === 'string' && payload.root ? payload.root : '(uploaded folder)';
|
|
195
|
-
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: rootName, backLink:
|
|
198
|
+
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: rootName, backLink: SERVER_BASE }));
|
|
196
199
|
}
|
|
197
200
|
catch (e) {
|
|
198
201
|
send(res, 500, 'text/html', errorPage('扫描失败:' + esc(e?.message || String(e))));
|
|
@@ -248,7 +251,7 @@ function handleDemo(res, locale, inc, dec) {
|
|
|
248
251
|
writeFileSync(join(dir, 'data', 'customers.csv'), 'name,id_card,phone,card\n张三,110101199003071233,13800138000,4111111111111111\n');
|
|
249
252
|
writeFileSync(join(dir, '.env'), 'AWS_ACCESS_KEY=AKIARZ9MKP2QWLS7YV3N\nDB_PASSWORD=Sup3rS3cretProdPwd2026\n');
|
|
250
253
|
const { report, scan } = runProjectComplianceAudit(DEFAULT_CONFIG, dir);
|
|
251
|
-
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: '示例项目(含风险)/ demo-ai-app', backLink:
|
|
254
|
+
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: '示例项目(含风险)/ demo-ai-app', backLink: SERVER_BASE }));
|
|
252
255
|
}
|
|
253
256
|
catch (e) {
|
|
254
257
|
send(res, 500, 'text/html', errorPage('演示失败:' + esc(e?.message || String(e))));
|
|
@@ -391,20 +394,26 @@ function page(title, body) {
|
|
|
391
394
|
*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;
|
|
392
395
|
background:linear-gradient(135deg,#eef1f6,#e2e8f0);color:#0f172a;
|
|
393
396
|
font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}
|
|
394
|
-
.hero{background:#fff;max-width:
|
|
395
|
-
box-shadow:0 12px 40px rgba(15,23,42,.12);text-align:center}
|
|
396
|
-
.logo{font-weight:800;font-size:15px}.logo span{color:#cb0000}
|
|
397
|
-
h1{font-size:
|
|
398
|
-
.sub{color:#64748b;margin:0 0
|
|
397
|
+
.hero{background:#fff;max-width:580px;width:92%;margin:40px;padding:38px 40px 34px;border-radius:18px;
|
|
398
|
+
box-shadow:0 12px 40px rgba(15,23,42,.12);text-align:center;border-top:4px solid #cb0000}
|
|
399
|
+
.logo{font-weight:800;font-size:15px;letter-spacing:.2px}.logo span{color:#cb0000}
|
|
400
|
+
h1{font-size:29px;margin:12px 0 8px;letter-spacing:-.5px}
|
|
401
|
+
.sub{color:#64748b;margin:0 0 24px;font-size:15px}
|
|
399
402
|
form{display:flex;flex-direction:column;gap:10px;text-align:left}
|
|
400
|
-
label{font-size:13px;font-weight:
|
|
403
|
+
label{font-size:13px;font-weight:700;color:#334155}
|
|
401
404
|
input{padding:14px 16px;border:1px solid #cbd5e1;border-radius:10px;font-size:16px;width:100%}
|
|
402
405
|
input:focus{outline:none;border-color:#cb0000;box-shadow:0 0 0 3px rgba(203,0,0,.12)}
|
|
403
|
-
.hint{font-size:12.5px;color:#64748b;margin:2px 0 6px}
|
|
406
|
+
.hint{font-size:12.5px;color:#64748b;margin:2px 0 6px;line-height:1.55}
|
|
404
407
|
.hint code{background:#f1f5f9;padding:1px 6px;border-radius:5px}
|
|
405
|
-
|
|
408
|
+
/* 文件选择器:美化成虚线投放区 + 红色按钮 */
|
|
409
|
+
input[type=file]{width:100%;padding:20px 16px;border:2px dashed #cbd5e1;border-radius:12px;
|
|
410
|
+
background:#f8fafc;cursor:pointer;font-size:14px;color:#64748b;transition:.15s}
|
|
411
|
+
input[type=file]:hover{border-color:#cb0000;background:#fff}
|
|
412
|
+
input[type=file]::file-selector-button{background:#cb0000;color:#fff;border:0;border-radius:8px;
|
|
413
|
+
padding:9px 18px;margin-right:14px;font-weight:700;font-size:14px;cursor:pointer}
|
|
414
|
+
input[type=file]::file-selector-button:hover{background:#a80000}
|
|
406
415
|
button{background:#cb0000;color:#fff;border:0;border-radius:10px;padding:14px;font-size:16px;
|
|
407
|
-
font-weight:700;cursor:pointer;margin-top:4px}button:hover{background:#a80000}
|
|
416
|
+
font-weight:700;cursor:pointer;margin-top:4px;transition:.15s}button:hover{background:#a80000}
|
|
408
417
|
button:disabled{background:#94a3b8;cursor:default}
|
|
409
418
|
form{margin:0 0 14px}.or{text-align:center;color:#94a3b8;font-size:13px;margin:6px 0 14px}
|
|
410
419
|
.status{display:none;margin:10px 0 0;padding:10px 14px;border-radius:8px;background:#f1f5f9;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shellward",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.13",
|
|
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": [
|
package/src/compliance/audit.ts
CHANGED
|
@@ -317,11 +317,12 @@ function checkEnv(c: ComplianceControl, env: EnvFacts): ControlResult {
|
|
|
317
317
|
if (env.overseas.length > 0) {
|
|
318
318
|
const names = env.overseas.map(o => o.provider_zh).join(', ')
|
|
319
319
|
const namesEn = env.overseas.map(o => o.provider_en).join(', ')
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
320
|
+
// 检出境外调用是"事实",是否违规取决于是否发送 PII/重要数据 → 标"需评估"而非"不合规"
|
|
321
|
+
return mk(c, 'warn',
|
|
322
|
+
`检测到境外大模型调用: ${names}。是否违规取决于发送的数据:涉及个人信息/重要数据需走合规路径或改用境内模型;仅非个人/非重要数据通常可接受。`,
|
|
323
|
+
`Overseas LLM detected: ${namesEn}. Compliance depends on the data sent — PI/important data needs a compliant path; non-personal data is usually fine.`)
|
|
323
324
|
}
|
|
324
|
-
return mk(c, 'pass', '
|
|
325
|
+
return mk(c, 'pass', '未检测到境外大模型调用', 'No overseas LLM detected')
|
|
325
326
|
}
|
|
326
327
|
return mk(c, 'manual', '需人工确认', 'Manual check required')
|
|
327
328
|
}
|
|
@@ -20,7 +20,7 @@ const STATUS: Record<ControlStatus, { zh: string; en: string; cls: string }> = {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const KIND: Record<FindingKind, { zh: string; en: string; icon: string }> = {
|
|
23
|
-
overseas: { zh: '
|
|
23
|
+
overseas: { zh: '境外大模型调用(需评估出境)', en: 'Overseas LLM (assess export)', icon: '🌐' },
|
|
24
24
|
secret: { zh: '硬编码密钥', en: 'Hardcoded secret', icon: '🔑' },
|
|
25
25
|
pii: { zh: '个人信息暴露', en: 'PII exposure', icon: '🪪' },
|
|
26
26
|
'env-perm': { zh: '.env 权限', en: '.env permission', icon: '📂' },
|
|
@@ -192,6 +192,22 @@ export function renderHtmlReport(
|
|
|
192
192
|
S.push('</tbody></table></div>')
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
// ===== 接入运行时指引:把 ⚪ 待核验项变成可验证(回答"需要接入才能测")=====
|
|
196
|
+
if (report.staticScan && report.manual > 0) {
|
|
197
|
+
const cfg = `{
|
|
198
|
+
"mcpServers": {
|
|
199
|
+
"shellward": { "command": "npx", "args": ["-y", "-p", "shellward", "shellward-mcp"] }
|
|
200
|
+
}
|
|
201
|
+
}`
|
|
202
|
+
S.push(sectionHead('🔌', t('让待核验项可验证:接入运行时', 'Make ⚪ items verifiable: deploy runtime'),
|
|
203
|
+
t('静态扫描测不了运行时行为,接入后这些项才能被持续验证', 'Static scan cannot test runtime behavior; deploy to validate')))
|
|
204
|
+
S.push(`<p class="note">${t(
|
|
205
|
+
`把下面这段加到 Claude Desktop / Cursor / OpenClaw 的 MCP 配置,重启后 ShellWard 就作为<b>运行时防护</b>在你的 AI 应用里运行——上方 ${report.manual} 项(审计留存、运行时拦截、数据外发管控等)即可被真实验证。`,
|
|
206
|
+
`Add this to your Claude Desktop / Cursor / OpenClaw MCP config and restart — ShellWard then runs as a <b>runtime guard</b>, validating the ${report.manual} ⚪ items above.`)}</p>`)
|
|
207
|
+
S.push(`<div class="cfg"><pre id="mcpcfg">${esc(cfg)}</pre><button onclick="(function(b){navigator.clipboard&&navigator.clipboard.writeText(document.getElementById('mcpcfg').textContent).then(function(){b.textContent='${t('已复制', 'Copied')}'})})(this)">${t('复制', 'Copy')}</button></div>`)
|
|
208
|
+
S.push(`<p class="muted">${t('或命令行直接起:', 'Or run directly:')} <code>npx shellward mcp</code> · ${t('文档', 'Docs')}: <a href="https://github.com/jnMetaCode/shellward">github.com/jnMetaCode/shellward</a></p>`)
|
|
209
|
+
}
|
|
210
|
+
|
|
195
211
|
const disclaimer = t(
|
|
196
212
|
'本报告由 ShellWard 合规网关自动生成,帮助评估并满足合规技术要求,不构成法律意见,亦不替代算法备案/定级备案/PIA 等主体责任。⚪ 待确认项需结合业务人工判定。',
|
|
197
213
|
'Generated by ShellWard Compliance Gateway. Assists with technical compliance; not legal advice. ⚪ items require manual review.')
|
|
@@ -364,6 +380,12 @@ table.tbl td.right{width:64px}
|
|
|
364
380
|
.manual-note{margin:8px 36px 12px;font-size:13px;color:#475569;background:#eff6ff;
|
|
365
381
|
border-left:3px solid #3b82f6;line-height:1.6}
|
|
366
382
|
.manual-note code{background:#dbeafe;padding:1px 6px;border-radius:5px}
|
|
383
|
+
.cfg{position:relative;margin:8px 36px}
|
|
384
|
+
.cfg pre{background:#0f172a;color:#e2e8f0;border-radius:10px;padding:16px 18px;margin:0;
|
|
385
|
+
font-family:ui-monospace,Menlo,monospace;font-size:12.5px;overflow-x:auto;line-height:1.5}
|
|
386
|
+
.cfg button{position:absolute;top:10px;right:12px;background:#1e293b;color:#94a3b8;border:0;
|
|
387
|
+
border-radius:6px;padding:5px 12px;font-size:12px;cursor:pointer}
|
|
388
|
+
.cfg button:hover{color:#fff}
|
|
367
389
|
|
|
368
390
|
/* 法规分组 */
|
|
369
391
|
.reg{margin:14px 36px;padding:0;border:1px solid var(--line);border-radius:12px;overflow:hidden}
|
|
Binary file
|
package/src/compliance/report.ts
CHANGED
|
@@ -130,7 +130,7 @@ export function renderComplianceReport(report: ComplianceReport, locale: 'zh' |
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
const KIND_LABEL: Record<FindingKind, { zh: string; en: string; icon: string }> = {
|
|
133
|
-
overseas: { zh: '
|
|
133
|
+
overseas: { zh: '境外大模型调用(需评估出境)', en: 'Overseas LLM (assess export)', icon: '🌐' },
|
|
134
134
|
secret: { zh: '硬编码密钥', en: 'Hardcoded secret', icon: '🔑' },
|
|
135
135
|
pii: { zh: '个人信息暴露', en: 'PII exposure', icon: '🪪' },
|
|
136
136
|
'env-perm': { zh: '.env 权限', en: '.env permission', icon: '📂' },
|
package/src/web/scan-server.ts
CHANGED
|
@@ -41,9 +41,13 @@ export function validateRepoUrl(input: string): { ok: true; url: string } | { ok
|
|
|
41
41
|
return { ok: true, url }
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// 报告「返回」链接:本地模式用绝对地址(上传报告经 blob: URL 打开,相对 '/' 会失效)
|
|
45
|
+
let SERVER_BASE = '/'
|
|
46
|
+
|
|
44
47
|
export function startWebServer(opts: WebServerOptions): void {
|
|
45
48
|
const locale = resolveLocale(DEFAULT_CONFIG)
|
|
46
49
|
const host = opts.local ? '127.0.0.1' : '0.0.0.0'
|
|
50
|
+
SERVER_BASE = opts.local ? `http://localhost:${opts.port}/` : '/'
|
|
47
51
|
let active = 0
|
|
48
52
|
|
|
49
53
|
const server = createServer(async (req, res) => {
|
|
@@ -113,7 +117,7 @@ async function handleRepo(res: any, repo: string, locale: 'zh' | 'en', inc: () =
|
|
|
113
117
|
try {
|
|
114
118
|
await cloneRepo(v.url, dir)
|
|
115
119
|
const { report, scan } = runProjectComplianceAudit(DEFAULT_CONFIG, dir)
|
|
116
|
-
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: v.url, backLink:
|
|
120
|
+
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: v.url, backLink: SERVER_BASE }))
|
|
117
121
|
} catch (e: any) {
|
|
118
122
|
const msg = esc(e?.message || String(e))
|
|
119
123
|
send(res, 502, 'text/html', errorPage(
|
|
@@ -133,7 +137,7 @@ async function handleLocal(res: any, path: string, locale: 'zh' | 'en', inc: ()
|
|
|
133
137
|
inc()
|
|
134
138
|
try {
|
|
135
139
|
const { report, scan } = runProjectComplianceAudit(DEFAULT_CONFIG, root)
|
|
136
|
-
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root, backLink:
|
|
140
|
+
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root, backLink: SERVER_BASE }))
|
|
137
141
|
} finally {
|
|
138
142
|
dec()
|
|
139
143
|
}
|
|
@@ -176,7 +180,7 @@ async function handleUpload(req: any, res: any, locale: 'zh' | 'en', inc: () =>
|
|
|
176
180
|
}
|
|
177
181
|
const { report, scan } = runProjectComplianceAudit(DEFAULT_CONFIG, dir)
|
|
178
182
|
const rootName = typeof payload.root === 'string' && payload.root ? payload.root : '(uploaded folder)'
|
|
179
|
-
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: rootName, backLink:
|
|
183
|
+
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: rootName, backLink: SERVER_BASE }))
|
|
180
184
|
} catch (e: any) {
|
|
181
185
|
send(res, 500, 'text/html', errorPage('扫描失败:' + esc(e?.message || String(e))))
|
|
182
186
|
} finally {
|
|
@@ -226,7 +230,7 @@ function handleDemo(res: any, locale: 'zh' | 'en', inc: () => void, dec: () => v
|
|
|
226
230
|
writeFileSync(join(dir, '.env'),
|
|
227
231
|
'AWS_ACCESS_KEY=AKIARZ9MKP2QWLS7YV3N\nDB_PASSWORD=Sup3rS3cretProdPwd2026\n')
|
|
228
232
|
const { report, scan } = runProjectComplianceAudit(DEFAULT_CONFIG, dir)
|
|
229
|
-
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: '示例项目(含风险)/ demo-ai-app', backLink:
|
|
233
|
+
send(res, 200, 'text/html', renderHtmlReport(report, scan, locale, { root: '示例项目(含风险)/ demo-ai-app', backLink: SERVER_BASE }))
|
|
230
234
|
} catch (e: any) {
|
|
231
235
|
send(res, 500, 'text/html', errorPage('演示失败:' + esc(e?.message || String(e))))
|
|
232
236
|
} finally {
|
|
@@ -373,20 +377,26 @@ function page(title: string, body: string): string {
|
|
|
373
377
|
*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;
|
|
374
378
|
background:linear-gradient(135deg,#eef1f6,#e2e8f0);color:#0f172a;
|
|
375
379
|
font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}
|
|
376
|
-
.hero{background:#fff;max-width:
|
|
377
|
-
box-shadow:0 12px 40px rgba(15,23,42,.12);text-align:center}
|
|
378
|
-
.logo{font-weight:800;font-size:15px}.logo span{color:#cb0000}
|
|
379
|
-
h1{font-size:
|
|
380
|
-
.sub{color:#64748b;margin:0 0
|
|
380
|
+
.hero{background:#fff;max-width:580px;width:92%;margin:40px;padding:38px 40px 34px;border-radius:18px;
|
|
381
|
+
box-shadow:0 12px 40px rgba(15,23,42,.12);text-align:center;border-top:4px solid #cb0000}
|
|
382
|
+
.logo{font-weight:800;font-size:15px;letter-spacing:.2px}.logo span{color:#cb0000}
|
|
383
|
+
h1{font-size:29px;margin:12px 0 8px;letter-spacing:-.5px}
|
|
384
|
+
.sub{color:#64748b;margin:0 0 24px;font-size:15px}
|
|
381
385
|
form{display:flex;flex-direction:column;gap:10px;text-align:left}
|
|
382
|
-
label{font-size:13px;font-weight:
|
|
386
|
+
label{font-size:13px;font-weight:700;color:#334155}
|
|
383
387
|
input{padding:14px 16px;border:1px solid #cbd5e1;border-radius:10px;font-size:16px;width:100%}
|
|
384
388
|
input:focus{outline:none;border-color:#cb0000;box-shadow:0 0 0 3px rgba(203,0,0,.12)}
|
|
385
|
-
.hint{font-size:12.5px;color:#64748b;margin:2px 0 6px}
|
|
389
|
+
.hint{font-size:12.5px;color:#64748b;margin:2px 0 6px;line-height:1.55}
|
|
386
390
|
.hint code{background:#f1f5f9;padding:1px 6px;border-radius:5px}
|
|
387
|
-
|
|
391
|
+
/* 文件选择器:美化成虚线投放区 + 红色按钮 */
|
|
392
|
+
input[type=file]{width:100%;padding:20px 16px;border:2px dashed #cbd5e1;border-radius:12px;
|
|
393
|
+
background:#f8fafc;cursor:pointer;font-size:14px;color:#64748b;transition:.15s}
|
|
394
|
+
input[type=file]:hover{border-color:#cb0000;background:#fff}
|
|
395
|
+
input[type=file]::file-selector-button{background:#cb0000;color:#fff;border:0;border-radius:8px;
|
|
396
|
+
padding:9px 18px;margin-right:14px;font-weight:700;font-size:14px;cursor:pointer}
|
|
397
|
+
input[type=file]::file-selector-button:hover{background:#a80000}
|
|
388
398
|
button{background:#cb0000;color:#fff;border:0;border-radius:10px;padding:14px;font-size:16px;
|
|
389
|
-
font-weight:700;cursor:pointer;margin-top:4px}button:hover{background:#a80000}
|
|
399
|
+
font-weight:700;cursor:pointer;margin-top:4px;transition:.15s}button:hover{background:#a80000}
|
|
390
400
|
button:disabled{background:#94a3b8;cursor:default}
|
|
391
401
|
form{margin:0 0 14px}.or{text-align:center;color:#94a3b8;font-size:13px;margin:6px 0 14px}
|
|
392
402
|
.status{display:none;margin:10px 0 0;padding:10px 14px;border-radius:8px;background:#f1f5f9;
|