shellward 0.7.3 → 0.7.5
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 +4 -1
- package/dist/web/scan-server.js +17 -7
- package/package.json +3 -2
- package/src/web/scan-server.ts +17 -7
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/shellward)
|
|
10
10
|
[](./LICENSE)
|
|
11
|
-
[](#performance)
|
|
12
12
|
[](#performance)
|
|
13
13
|
|
|
14
14
|
**🌐 官网: https://jnmetacode.github.io/shellward/**
|
|
@@ -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/web/scan-server.js
CHANGED
|
@@ -227,6 +227,7 @@ function formPage(local) {
|
|
|
227
227
|
<label>① 选择本地项目文件夹(推荐)</label>
|
|
228
228
|
<input type="file" id="dir" webkitdirectory directory multiple>
|
|
229
229
|
<button id="dbtn" type="submit">开始体检 →</button>
|
|
230
|
+
<div id="status" class="status"></div>
|
|
230
231
|
<p class="hint">📂 直接选你的项目文件夹,无需敲路径。文件仅发送到<b>本机的本地服务</b>处理,<b>不经过任何外部服务器、不出本机</b>。</p>
|
|
231
232
|
</form>
|
|
232
233
|
<div class="or">— 或 —</div>` : '';
|
|
@@ -243,17 +244,20 @@ function formPage(local) {
|
|
|
243
244
|
${local ? UPLOAD_SCRIPT : ''}`);
|
|
244
245
|
}
|
|
245
246
|
// 客户端:读取所选文件夹 → 过滤(跳过 node_modules 等、仅文本/配置、限大小) → POST 到本机服务
|
|
247
|
+
// 注意:过滤后缀须与服务端 SCAN_EXT 对齐(含 .md),否则 markdown 项目会被全滤光显得"扫不了"。
|
|
246
248
|
const UPLOAD_SCRIPT = `<script>
|
|
247
249
|
(function(){
|
|
248
250
|
var SKIP=/(^|\\/)(node_modules|\\.git|dist|build|\\.next|out|vendor|coverage|\\.venv|venv|__pycache__|target|\\.cache)(\\/|$)/;
|
|
249
|
-
var EXT=/\\.(ts|tsx|js|jsx|mjs|cjs|py|go|rb|java|php|rs|json|yaml|yml|toml|ini|conf|sh|txt|csv)$/i;
|
|
251
|
+
var EXT=/\\.(ts|tsx|js|jsx|mjs|cjs|py|go|rb|java|php|rs|json|yaml|yml|toml|ini|conf|sh|txt|csv|md|mdx|ipynb|properties|xml|gradle|tf)$/i;
|
|
250
252
|
var ENV=/(^|\\/)\\.env(\\.|$)/; var DEP=/^(package\\.json|requirements\\.txt|pyproject\\.toml|go\\.mod)$/;
|
|
251
253
|
var form=document.getElementById('dirform'); if(!form) return;
|
|
254
|
+
var statusEl=document.getElementById('status');
|
|
255
|
+
function s(m){ if(statusEl){statusEl.textContent=m;statusEl.style.display='block';} }
|
|
252
256
|
form.addEventListener('submit', async function(e){
|
|
253
257
|
e.preventDefault();
|
|
254
258
|
var inp=document.getElementById('dir'), btn=document.getElementById('dbtn');
|
|
255
|
-
if(!inp.files||!inp.files.length){
|
|
256
|
-
btn.disabled=true;
|
|
259
|
+
if(!inp.files||!inp.files.length){ s('请先点上方按钮选择项目文件夹'); return; }
|
|
260
|
+
btn.disabled=true;
|
|
257
261
|
var picked=[], total=0, root='';
|
|
258
262
|
for(var i=0;i<inp.files.length;i++){
|
|
259
263
|
var f=inp.files[i], rel=f.webkitRelativePath||f.name; if(!root)root=rel.split('/')[0];
|
|
@@ -264,13 +268,17 @@ const UPLOAD_SCRIPT = `<script>
|
|
|
264
268
|
if(picked.length>=3000||total>8388608) break;
|
|
265
269
|
total+=f.size; picked.push(f);
|
|
266
270
|
}
|
|
267
|
-
if(!picked.length){
|
|
268
|
-
|
|
271
|
+
if(!picked.length){ s('未找到可扫描的源码/配置文件(已自动跳过 node_modules、图片、超大文件)。请选含代码或配置的目录。'); btn.disabled=false; return; }
|
|
272
|
+
s('读取 '+picked.length+' 个文件…');
|
|
269
273
|
var out=[]; for(var j=0;j<picked.length;j++){ try{ out.push({path:picked[j].webkitRelativePath||picked[j].name, content:await picked[j].text()}); }catch(_){} }
|
|
274
|
+
s('扫描中…('+out.length+' 个文件,请稍候)');
|
|
270
275
|
try{
|
|
271
276
|
var resp=await fetch('/scan-files',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({root:root,files:out})});
|
|
272
|
-
|
|
273
|
-
|
|
277
|
+
if(!resp.ok){ s('扫描失败:HTTP '+resp.status+'。请重试,或改用命令行 npx shellward scan。'); btn.disabled=false; return; }
|
|
278
|
+
var html=await resp.text();
|
|
279
|
+
// 用 Blob URL 跳转展示报告(比 document.write 可靠)
|
|
280
|
+
window.location.href=URL.createObjectURL(new Blob([html],{type:'text/html'}));
|
|
281
|
+
}catch(err){ s('扫描失败:'+(err&&err.message||err)+'。请重试。'); btn.disabled=false; }
|
|
274
282
|
});
|
|
275
283
|
})();
|
|
276
284
|
</script>`;
|
|
@@ -302,6 +310,8 @@ button{background:#cb0000;color:#fff;border:0;border-radius:10px;padding:14px;fo
|
|
|
302
310
|
font-weight:700;cursor:pointer;margin-top:4px}button:hover{background:#a80000}
|
|
303
311
|
button:disabled{background:#94a3b8;cursor:default}
|
|
304
312
|
form{margin:0 0 14px}.or{text-align:center;color:#94a3b8;font-size:13px;margin:6px 0 14px}
|
|
313
|
+
.status{display:none;margin:10px 0 0;padding:10px 14px;border-radius:8px;background:#f1f5f9;
|
|
314
|
+
color:#334155;font-size:13.5px;border-left:3px solid #cb0000;text-align:left}
|
|
305
315
|
.foot{margin:24px 0 0;font-size:12.5px;color:#94a3b8}.foot a,.back{color:#cb0000;text-decoration:none}
|
|
306
316
|
.back{font-weight:600}
|
|
307
317
|
</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.5",
|
|
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": [
|
|
@@ -57,7 +57,7 @@
|
|
|
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 && npx tsx test-compliance.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 && npx tsx test-web.ts",
|
|
61
61
|
"test:redos": "npx tsx test-redos.ts",
|
|
62
62
|
"test:compliance": "npx tsx test-compliance.ts",
|
|
63
63
|
"test:integration": "npx tsx test-integration.ts",
|
|
@@ -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/web/scan-server.ts
CHANGED
|
@@ -212,6 +212,7 @@ function formPage(local: boolean): string {
|
|
|
212
212
|
<label>① 选择本地项目文件夹(推荐)</label>
|
|
213
213
|
<input type="file" id="dir" webkitdirectory directory multiple>
|
|
214
214
|
<button id="dbtn" type="submit">开始体检 →</button>
|
|
215
|
+
<div id="status" class="status"></div>
|
|
215
216
|
<p class="hint">📂 直接选你的项目文件夹,无需敲路径。文件仅发送到<b>本机的本地服务</b>处理,<b>不经过任何外部服务器、不出本机</b>。</p>
|
|
216
217
|
</form>
|
|
217
218
|
<div class="or">— 或 —</div>` : ''
|
|
@@ -230,17 +231,20 @@ function formPage(local: boolean): string {
|
|
|
230
231
|
}
|
|
231
232
|
|
|
232
233
|
// 客户端:读取所选文件夹 → 过滤(跳过 node_modules 等、仅文本/配置、限大小) → POST 到本机服务
|
|
234
|
+
// 注意:过滤后缀须与服务端 SCAN_EXT 对齐(含 .md),否则 markdown 项目会被全滤光显得"扫不了"。
|
|
233
235
|
const UPLOAD_SCRIPT = `<script>
|
|
234
236
|
(function(){
|
|
235
237
|
var SKIP=/(^|\\/)(node_modules|\\.git|dist|build|\\.next|out|vendor|coverage|\\.venv|venv|__pycache__|target|\\.cache)(\\/|$)/;
|
|
236
|
-
var EXT=/\\.(ts|tsx|js|jsx|mjs|cjs|py|go|rb|java|php|rs|json|yaml|yml|toml|ini|conf|sh|txt|csv)$/i;
|
|
238
|
+
var EXT=/\\.(ts|tsx|js|jsx|mjs|cjs|py|go|rb|java|php|rs|json|yaml|yml|toml|ini|conf|sh|txt|csv|md|mdx|ipynb|properties|xml|gradle|tf)$/i;
|
|
237
239
|
var ENV=/(^|\\/)\\.env(\\.|$)/; var DEP=/^(package\\.json|requirements\\.txt|pyproject\\.toml|go\\.mod)$/;
|
|
238
240
|
var form=document.getElementById('dirform'); if(!form) return;
|
|
241
|
+
var statusEl=document.getElementById('status');
|
|
242
|
+
function s(m){ if(statusEl){statusEl.textContent=m;statusEl.style.display='block';} }
|
|
239
243
|
form.addEventListener('submit', async function(e){
|
|
240
244
|
e.preventDefault();
|
|
241
245
|
var inp=document.getElementById('dir'), btn=document.getElementById('dbtn');
|
|
242
|
-
if(!inp.files||!inp.files.length){
|
|
243
|
-
btn.disabled=true;
|
|
246
|
+
if(!inp.files||!inp.files.length){ s('请先点上方按钮选择项目文件夹'); return; }
|
|
247
|
+
btn.disabled=true;
|
|
244
248
|
var picked=[], total=0, root='';
|
|
245
249
|
for(var i=0;i<inp.files.length;i++){
|
|
246
250
|
var f=inp.files[i], rel=f.webkitRelativePath||f.name; if(!root)root=rel.split('/')[0];
|
|
@@ -251,13 +255,17 @@ const UPLOAD_SCRIPT = `<script>
|
|
|
251
255
|
if(picked.length>=3000||total>8388608) break;
|
|
252
256
|
total+=f.size; picked.push(f);
|
|
253
257
|
}
|
|
254
|
-
if(!picked.length){
|
|
255
|
-
|
|
258
|
+
if(!picked.length){ s('未找到可扫描的源码/配置文件(已自动跳过 node_modules、图片、超大文件)。请选含代码或配置的目录。'); btn.disabled=false; return; }
|
|
259
|
+
s('读取 '+picked.length+' 个文件…');
|
|
256
260
|
var out=[]; for(var j=0;j<picked.length;j++){ try{ out.push({path:picked[j].webkitRelativePath||picked[j].name, content:await picked[j].text()}); }catch(_){} }
|
|
261
|
+
s('扫描中…('+out.length+' 个文件,请稍候)');
|
|
257
262
|
try{
|
|
258
263
|
var resp=await fetch('/scan-files',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({root:root,files:out})});
|
|
259
|
-
|
|
260
|
-
|
|
264
|
+
if(!resp.ok){ s('扫描失败:HTTP '+resp.status+'。请重试,或改用命令行 npx shellward scan。'); btn.disabled=false; return; }
|
|
265
|
+
var html=await resp.text();
|
|
266
|
+
// 用 Blob URL 跳转展示报告(比 document.write 可靠)
|
|
267
|
+
window.location.href=URL.createObjectURL(new Blob([html],{type:'text/html'}));
|
|
268
|
+
}catch(err){ s('扫描失败:'+(err&&err.message||err)+'。请重试。'); btn.disabled=false; }
|
|
261
269
|
});
|
|
262
270
|
})();
|
|
263
271
|
</script>`
|
|
@@ -291,6 +299,8 @@ button{background:#cb0000;color:#fff;border:0;border-radius:10px;padding:14px;fo
|
|
|
291
299
|
font-weight:700;cursor:pointer;margin-top:4px}button:hover{background:#a80000}
|
|
292
300
|
button:disabled{background:#94a3b8;cursor:default}
|
|
293
301
|
form{margin:0 0 14px}.or{text-align:center;color:#94a3b8;font-size:13px;margin:6px 0 14px}
|
|
302
|
+
.status{display:none;margin:10px 0 0;padding:10px 14px;border-radius:8px;background:#f1f5f9;
|
|
303
|
+
color:#334155;font-size:13.5px;border-left:3px solid #cb0000;text-align:left}
|
|
294
304
|
.foot{margin:24px 0 0;font-size:12.5px;color:#94a3b8}.foot a,.back{color:#cb0000;text-decoration:none}
|
|
295
305
|
.back{font-weight:600}
|
|
296
306
|
</style></head><body>${body}</body></html>`
|