shellward 0.7.11 → 0.7.12

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.
@@ -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:560px;width:92%;margin:40px;padding:40px;border-radius:18px;
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:30px;margin:14px 0 8px;letter-spacing:-.5px}
398
- .sub{color:#64748b;margin:0 0 26px}
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:600;color:#475569}
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
- input[type=file]{padding:12px;background:#f8fafc;cursor:pointer}
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.11",
3
+ "version": "0.7.12",
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": [
@@ -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:560px;width:92%;margin:40px;padding:40px;border-radius:18px;
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:30px;margin:14px 0 8px;letter-spacing:-.5px}
380
- .sub{color:#64748b;margin:0 0 26px}
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:600;color:#475569}
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
- input[type=file]{padding:12px;background:#f8fafc;cursor:pointer}
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;