From 28214254b50cb3d5ca1de960a3130def41fc4fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Wed, 8 Jul 2026 14:19:04 +0800 Subject: [PATCH] Add files via upload --- plugins/README.md | 3 +- .../cyberstrikeai-browser-extension/README.md | 4 +- .../README.zh-CN.md | 20 +- .../dist/cyberstrikeai-browser-extension.zip | Bin 55551 -> 61459 bytes .../lib/api.js | 33 ++- .../lib/auth-session.js | 54 ++++ .../lib/constants.js | 2 +- .../lib/storage.js | 110 ++++++- .../manifest.json | 2 +- .../panel/panel.html | 25 +- .../panel/panel.js | 277 +++++++++++++++--- .../popup/popup.html | 16 +- .../popup/popup.js | 34 ++- 13 files changed, 483 insertions(+), 97 deletions(-) create mode 100644 plugins/browser-extension/cyberstrikeai-browser-extension/lib/auth-session.js diff --git a/plugins/README.md b/plugins/README.md index c3896a05..7503c38a 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -11,7 +11,7 @@ Optional integrations that connect CyberStrikeAI with other tools. ### Browser (Chrome / Edge) - **Path**: `plugins/browser-extension/cyberstrikeai-browser-extension/` -- **Version**: **0.3.5** +- **Version**: **0.3.8** - **Install**: `chrome://extensions/` → Load unpacked → F12 → **CyberStrikeAI** tab - **Package**: `bash package.sh` → `dist/cyberstrikeai-browser-extension.zip` - **Docs**: `README.zh-CN.md` (full) / `README.md` (summary) @@ -20,6 +20,7 @@ Optional integrations that connect CyberStrikeAI with other tools. | Feature | Description | |---------|-------------| +| Token expiry | Remaining time + 30s validate probe; detects server restart / unreachable | | Capture pause | **● Capturing** / **○ Paused** — stop recording without closing DevTools | | HTTP/1.1 display | Raw HAR in memory; UI + AI prompt normalized (no `:method` pseudo-headers) | | Collapsible conn bar | Host/Port/Validate collapses after success | diff --git a/plugins/browser-extension/cyberstrikeai-browser-extension/README.md b/plugins/browser-extension/cyberstrikeai-browser-extension/README.md index 33b93a0e..b7af0ca7 100644 --- a/plugins/browser-extension/cyberstrikeai-browser-extension/README.md +++ b/plugins/browser-extension/cyberstrikeai-browser-extension/README.md @@ -1,6 +1,6 @@ ## CyberStrikeAI Browser Extension -**Version 0.3.5** — Full docs: **README.zh-CN.md** +**Version 0.3.8** — Full docs: **README.zh-CN.md** Chromium DevTools extension: capture Network traffic and send it to CyberStrikeAI for AI-assisted security testing. Aligned with the Burp Suite plugin. @@ -28,7 +28,7 @@ Chromium DevTools extension: capture Network traffic and send it to CyberStrikeA - **Deferred Markdown** — plain text while streaming; render after done; skip above 100KB - **Stop** — abort local stream + server cancel via `conversationId` - **Latest XHR**, **Copy**, project/role/agent send dialog -- Session token + optional host permissions +- Session token with **expires_at** tracking, 30s server probe, restart/unreachable detection ### Data limits (no unbounded growth) diff --git a/plugins/browser-extension/cyberstrikeai-browser-extension/README.zh-CN.md b/plugins/browser-extension/cyberstrikeai-browser-extension/README.zh-CN.md index be6a652b..069f90c7 100644 --- a/plugins/browser-extension/cyberstrikeai-browser-extension/README.zh-CN.md +++ b/plugins/browser-extension/cyberstrikeai-browser-extension/README.zh-CN.md @@ -1,6 +1,6 @@ ## CyberStrikeAI 浏览器扩展 -**当前版本:0.3.5** +**当前版本:0.3.8**(UI 为英文;中文说明见下文) Chrome / Edge(Chromium)DevTools 扩展:在开发者工具中捕获 **Network** 流量,发送到 CyberStrikeAI 进行 AI 辅助安全测试。能力与 Burp Suite 插件对齐,并按生产场景做了性能与体验优化。 @@ -81,8 +81,13 @@ Cookie: ... #### 安全与权限 - Token 存 **chrome.storage.session**(关浏览器失效) -- **optional_host_permissions**:Validate 时按需授权 CyberStrikeAI 地址 -- Markdown iframe 使用 `sandbox` +- 登录后保存 **`expires_at`**,状态栏显示 **剩余时间**(如 `OK · 剩余 11h 30m`) +- **不会自动续期**:过期后需重新 Validate(需 Password) +- 本地过期检测(30s)+ 服务端 `/api/auth/validate` 探测(同周期;切回面板时立即探测) +- 服务不可达时显示 **无法连接服务**;重启后 Token 失效显示 **服务已重启或 Token 已失效** +- **401/403** 时自动清空 Token 并展开连接栏 +- Send 前主动校验 Token 有效性 +- **optional_host_permissions**:Validate 时按需授权 --- @@ -141,6 +146,12 @@ Cookie: ... **扩展更新后报错 `chrome.runtime.connect` undefined?** 扩展重载后旧 DevTools 面板上下文失效。请:**关闭 DevTools → 重新加载扩展 → 再开 F12**。 +**Token 过期会自动刷新吗?** +**不会自动续期**(无 refresh token)。插件会保存 `expires_at`、显示剩余时间;每 30s 向服务端校验,切回 DevTools 时立即校验。服务重启后 session 清空,会提示重新 Validate。 + +**重启服务后状态还显示 OK?** +v0.3.7 起每 30s 探测 `/api/auth/validate`;不可达显示黄色警告,Token 失效则清空并展开连接栏。重载扩展后请关闭 DevTools 再开 F12。 + **Request 里为什么曾经有 `:authority`、`:method`?** HTTP/2 伪首部。展示与 AI Prompt 已归一化为 HTTP/1.1;原始 HAR 仍保存在内存 entry 中。 @@ -205,8 +216,9 @@ panel/ popup/ popup.html / popup.js / popup.css # 只读状态 lib/ + auth-session.js # Token 过期检测与剩余时间提示 api.js # 登录、SSE、项目/角色 API - storage.js # 配置与 session token + storage.js # 配置 + session token + expires_at capture.js # HAR 摘要、静态过滤 http-normalize.js # HTTP/2 → HTTP/1.1 展示/Prompt formatter.js # toPrompt 组装 diff --git a/plugins/browser-extension/cyberstrikeai-browser-extension/dist/cyberstrikeai-browser-extension.zip b/plugins/browser-extension/cyberstrikeai-browser-extension/dist/cyberstrikeai-browser-extension.zip index 5c708c99143423f7310f6422b90e3baa933da468..f9f83fb56efbe1adc614b111720bdbe04f8494a9 100644 GIT binary patch delta 26495 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 M!N9x^{!ID*2MgvcApigX delta 20539 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

d8~PW>%+qPbJl6Nt z^$E~z7e0fzVG8r#R?Da>&%Dp@IDTWxBW*p&{j-H?fj&Pl#B=qBz|&MaYC$t0zd8(S zSmp3$qrOkhp~=E+-of?9Aq6^oO< zuWeo)Cs93;;RYwiXCH3;^+dRpbR5S4g?lA0K1dNJG)BSfye@}${k+TliH^QA8!uA7 zn(a-00^9#W@&<8uF(%SF>2=6bP|-Vd+im3^EFj<+vO)a}>;~0(=Xpf`g zN}GpQ>_UTM25D)d<(@J`w&OOlSVbpoWDKRU1~T^&^Hk?()<8HyosTwBQXa5nc1em7O%VzTb85)Q7{my5y5GaPW24vxzL6KWSzF z11bncQZ7Pcu`bd6`X0eG1#Y{~OgOtu>m2P$h$tK0g>Q-HIFK{YH?Al;rxdZ`oOUJa zv^}Z@C?+&nfu3JNlRzCxnBjX?%WB=Ot8d-w>o`6KiDZYc1{fom%+5^qr%RJ(Him<2 zZ#fN<#ZHe}R$W?(42kb?d+d@m5w#2ks#x!Nc!YaHy$!c7MG8{OGQGR?5S*|2k6^$( zTj}x!W5ymvZssxUM^4s`$F~Y;%sYsj4y^-zn-HTL>!z*jW|QL|34SrO991xflILog zrI_0ZlGm=-`#s246X@Y06$CsSvy^!65dB&@%E3usgVC<~1uTAbCm>z3r9N?@2w;e- zu;uVdU8;beFw^&b<)e--4|!M2u5+W9m0EnvyROEj8e&T-yd@rFL5?8S1=e&7huJzy zrqtMftKpBFlGZ^Byy0L!T^izgzL%&dc3rA1OqSI(**ETktJR|-?pJBK0#S{069&ik z`(ln4w|{-s+G@Zj)eL)GB031efPjVi=v67i36Ky`fsuYZfQgg}t5HoigNlSkK*BxW zL$uO_8J(_S1!7nHN%K?pR3Hjxee$1h1m_3ov=Q`+8>fSKXh&CQRfoBcKeQ(2G)IF_ z=&#Ld5hUBWiIK3y>)lJmdwMOvz<$&LUG$)F*)1SLF4<~HR?Nb?FjUVK`1mYS`gse8bs< zY*tK8u9{_rEx9M1kDfV51I_fE)0$8Og*Vk&V8X@ zTh9E|-hC&1Z30H_5l#0;#4NCD3#Uj0sTwz+3uq9)XE2lYzUsW@J78?z8#}&Gh#4UG z463~iogGN-e`|B;qHuTY1{yZ6CK^clP&rAy^%|Gdw@8{NvbxE6gLbYSksJe9ODfvP z8zd>gGP&H-oS3|~O;ol~?LkDB}WD z;NQoB7a>s%2RP-WrWKyfYHD$91^OVP8F$){k$vk4x!UCf~a@jb2v}!-rGN6j`S?dVGognL4u#lzwGo||0>`|_<1IvKgc6i*Q zd%=4$)+W$#3$nmwFL(Tq+J5GFar~8x(>BYf14RFagdM4^otq<^N&i^vEtnf`;Q}VK ztt#W)*x+hD(~PT?6K;EYFWimA&KLr^?5G^8w>sC2S&oUSw3uCq_Yto#_j=8gQ#uXkc?f1`Ci1FB#-_plDcm6gvS)F4blc7xdE0{s+)}qjiTj zS^H7543CW*C(mA8DKA>cS~fT4e6^sPS3yzqsMcaqf8@B8Fh1dvFK>X2i%ePNLvNQ? z{=75S;NvS}YN;B;O0uBi!vZn9wNFHm`{cRVB(l~^D=+v8=6(M3qg}0+w%%qdg;V)6 zJE0Gv8)gdS9yS@~j|vZP8x#Nusg)a;nLGkXOJgQA<=uu8ddfgDw9ZhPB$HlouH3I) z4c=B1apU%`9-^H&oJ&?) zD=alDd^@Ey+kRSpaqHAS)1tAQ8zzHpJ!e0M{shs-ODXkh6bklyn3qYrX4DFmBJh&N ztjKRznH>W7fa6s%7Cbk@i#jdcMRM&TVXtl_-n#i;=B-Lgc5~{am)WPrHdlRpQ2(v zByAK{MwJ`JM6q&vd3yPKi67oC{@22NjEWll3#|YO!y7J`f{gG_7Q}e}p2c)DQXHPV zzzQCpJxuzyQ~c;53H<;pIkBp@2TS@mrcROC_HvqR_1Sc z`DoZvc~t)|Hvg*Ya8YuNBdG&W)^uKww+X@$baS_!ak;bpCp5^= 1) return `OK · ${h}h ${m}m left`; + if (m >= 1) return `OK · ${m}m left`; + return 'OK · expiring soon'; +} + +function isAuthHttpStatus(status) { + return status === 401 || status === 403; +} + +/** fetch() failed before HTTP response (server down, connection refused, etc.). */ +function isNetworkFetchError(err) { + if (!err) return false; + if (err.network === true) return true; + if (err.name === 'AbortError') return false; + const msg = String(err.message || err); + return err.name === 'TypeError' && /fetch|network|Failed to fetch/i.test(msg); +} + +function attachHttpStatus(err, status) { + if (err && typeof err === 'object') err.status = status; + return err; +} diff --git a/plugins/browser-extension/cyberstrikeai-browser-extension/lib/constants.js b/plugins/browser-extension/cyberstrikeai-browser-extension/lib/constants.js index 70b1ffed..7dfad46a 100644 --- a/plugins/browser-extension/cyberstrikeai-browser-extension/lib/constants.js +++ b/plugins/browser-extension/cyberstrikeai-browser-extension/lib/constants.js @@ -14,4 +14,4 @@ const CSAI_LIMITS = { }; const CSAI_DEFAULT_INSTRUCTION = - '针对该流量做web渗透测试,并输出测试结果,要求:只针对该接口流量做测试,切勿拓展其他接口'; + 'Perform web penetration testing on this traffic and output results. Test only this endpoint; do not expand to other APIs.'; diff --git a/plugins/browser-extension/cyberstrikeai-browser-extension/lib/storage.js b/plugins/browser-extension/cyberstrikeai-browser-extension/lib/storage.js index 2ba1cdcc..22f42b63 100644 --- a/plugins/browser-extension/cyberstrikeai-browser-extension/lib/storage.js +++ b/plugins/browser-extension/cyberstrikeai-browser-extension/lib/storage.js @@ -27,42 +27,118 @@ const DEFAULTS = { showDebugEvents: false, }; +function extensionContextAlive() { + try { + return !!(typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.id); + } catch (_) { + return false; + } +} + +function extensionContextError() { + const err = new Error('Extension context invalidated'); + err.name = 'ExtensionContextError'; + err.contextInvalidated = true; + return err; +} + +function isExtensionContextError(err) { + if (!err) return false; + if (err.contextInvalidated || err.name === 'ExtensionContextError') return true; + return /extension context invalidated/i.test(String(err.message || err)); +} + +function normalizeStorageError(err) { + if (isExtensionContextError(err)) return extensionContextError(); + return err instanceof Error ? err : new Error(String(err)); +} + +function rejectLastError(reject) { + const msg = chrome.runtime.lastError && chrome.runtime.lastError.message; + if (!msg) return false; + reject(/invalidated/i.test(msg) ? extensionContextError() : new Error(msg)); + return true; +} + function localGet(keys) { - return new Promise((resolve) => chrome.storage.local.get(keys, resolve)); + return new Promise((resolve, reject) => { + try { + if (!extensionContextAlive()) { + reject(extensionContextError()); + return; + } + chrome.storage.local.get(keys, (data) => { + if (rejectLastError(reject)) return; + resolve(data); + }); + } catch (err) { + reject(normalizeStorageError(err)); + } + }); } function localSet(obj) { - return new Promise((resolve) => chrome.storage.local.set(obj, resolve)); + return new Promise((resolve, reject) => { + try { + if (!extensionContextAlive()) { + reject(extensionContextError()); + return; + } + chrome.storage.local.set(obj, () => { + if (rejectLastError(reject)) return; + resolve(); + }); + } catch (err) { + reject(normalizeStorageError(err)); + } + }); } function sessionGet(keys) { - return new Promise((resolve) => { - if (!chrome.storage.session) { - chrome.storage.local.get(keys, resolve); - return; + return new Promise((resolve, reject) => { + try { + if (!extensionContextAlive()) { + reject(extensionContextError()); + return; + } + const store = chrome.storage.session || chrome.storage.local; + store.get(keys, (data) => { + if (rejectLastError(reject)) return; + resolve(data); + }); + } catch (err) { + reject(normalizeStorageError(err)); } - chrome.storage.session.get(keys, resolve); }); } function sessionSet(obj) { - return new Promise((resolve) => { - if (!chrome.storage.session) { - chrome.storage.local.set(obj, resolve); - return; + return new Promise((resolve, reject) => { + try { + if (!extensionContextAlive()) { + reject(extensionContextError()); + return; + } + const store = chrome.storage.session || chrome.storage.local; + store.set(obj, () => { + if (rejectLastError(reject)) return; + resolve(); + }); + } catch (err) { + reject(normalizeStorageError(err)); } - chrome.storage.session.set(obj, resolve); }); } async function loadConfig() { const data = await localGet(Object.values(STORAGE_KEYS)); - const sess = await sessionGet([SESSION_TOKEN_KEY]); + const sess = await sessionGet([SESSION_TOKEN_KEY, SESSION_TOKEN_EXPIRY_KEY]); return { host: data[STORAGE_KEYS.host] || DEFAULTS.host, port: data[STORAGE_KEYS.port] || DEFAULTS.port, https: data[STORAGE_KEYS.https] !== false, token: sess[SESSION_TOKEN_KEY] || '', + tokenExpiresAt: sess[SESSION_TOKEN_EXPIRY_KEY] || '', lastProjectId: data[STORAGE_KEYS.lastProjectId] || '', lastRole: data[STORAGE_KEYS.lastRole] || '', lastAgentMode: data[STORAGE_KEYS.lastAgentMode] || 'eino_single', @@ -88,7 +164,10 @@ async function saveConfig(partial) { if (partial.renderMarkdown != null) localMap[STORAGE_KEYS.renderMarkdown] = partial.renderMarkdown; if (partial.showDebugEvents != null) localMap[STORAGE_KEYS.showDebugEvents] = partial.showDebugEvents; if (Object.keys(localMap).length) await localSet(localMap); - if (partial.token != null) await sessionSet({ [SESSION_TOKEN_KEY]: partial.token }); + const sessionMap = {}; + if (partial.token != null) sessionMap[SESSION_TOKEN_KEY] = partial.token; + if (partial.tokenExpiresAt != null) sessionMap[SESSION_TOKEN_EXPIRY_KEY] = partial.tokenExpiresAt; + if (Object.keys(sessionMap).length) await sessionSet(sessionMap); } function baseUrlFrom(cfg) { @@ -98,6 +177,7 @@ function baseUrlFrom(cfg) { /** Request optional host permission for the configured CyberStrikeAI origin. */ async function ensureHostPermission(baseUrl) { + if (!extensionContextAlive()) throw extensionContextError(); if (!chrome.permissions || !chrome.permissions.request) return; let origin; try { @@ -109,6 +189,6 @@ async function ensureHostPermission(baseUrl) { if (has) return; const granted = await chrome.permissions.request({ origins: [origin] }); if (!granted) { - throw new Error('需要授权访问 CyberStrikeAI 服务器地址'); + throw new Error('Permission required to access the CyberStrikeAI server'); } } diff --git a/plugins/browser-extension/cyberstrikeai-browser-extension/manifest.json b/plugins/browser-extension/cyberstrikeai-browser-extension/manifest.json index fbe2bfa3..afcd1dc6 100644 --- a/plugins/browser-extension/cyberstrikeai-browser-extension/manifest.json +++ b/plugins/browser-extension/cyberstrikeai-browser-extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "CyberStrikeAI", - "version": "0.3.5", + "version": "0.3.9", "description": "Capture browser HTTP traffic and send to CyberStrikeAI for AI-assisted security testing.", "devtools_page": "devtools.html", "permissions": ["storage"], diff --git a/plugins/browser-extension/cyberstrikeai-browser-extension/panel/panel.html b/plugins/browser-extension/cyberstrikeai-browser-extension/panel/panel.html index 936d92c2..3e32ea59 100644 --- a/plugins/browser-extension/cyberstrikeai-browser-extension/panel/panel.html +++ b/plugins/browser-extension/cyberstrikeai-browser-extension/panel/panel.html @@ -1,5 +1,5 @@ - + @@ -8,7 +8,7 @@

@@ -17,7 +17,7 @@ CyberStrikeAI
- + Not validated
@@ -50,7 +50,7 @@ - +
@@ -72,7 +72,7 @@
-
加载中…
+
Loading…
-

主工作区在 DevTools 面板:按 F12CyberStrikeAI

+

Main workspace is in DevTools: press F12CyberStrikeAI

    -
  • 连接配置与 Validate 请在 DevTools 面板完成
  • -
  • 默认只捕获 XHR/Fetch
  • -
  • Test History 可回看多次发送
  • -
  • Latest XHR 快速发送最近 API 请求
  • +
  • Connection and Validate are in the DevTools panel
  • +
  • Captures XHR/Fetch by default
  • +
  • Test History stores recent sends
  • +
  • Latest XHR sends the most recent API request
+ + diff --git a/plugins/browser-extension/cyberstrikeai-browser-extension/popup/popup.js b/plugins/browser-extension/cyberstrikeai-browser-extension/popup/popup.js index 28b76cee..8094056c 100644 --- a/plugins/browser-extension/cyberstrikeai-browser-extension/popup/popup.js +++ b/plugins/browser-extension/cyberstrikeai-browser-extension/popup/popup.js @@ -12,17 +12,37 @@ async function renderPopup() { try { const cfg = await loadConfig(); const endpoint = baseUrlFrom(cfg); - if (cfg.token) { - statusEl.className = 'conn-status conn-status--ok'; - statusEl.textContent = `已连接 ${endpoint}`; - } else { + if (!cfg.token) { statusEl.className = 'conn-status conn-status--idle'; - statusEl.textContent = `未验证 · 目标 ${endpoint}`; + statusEl.textContent = `Not validated · ${endpoint}`; + return; + } + if (isTokenExpiredByTime(cfg.tokenExpiresAt)) { + statusEl.className = 'conn-status conn-status--idle'; + statusEl.textContent = `Session expired · ${endpoint}`; + return; + } + + try { + await validateTokenSession(endpoint, cfg.token); + statusEl.className = 'conn-status conn-status--ok'; + statusEl.textContent = `${formatTokenExpiryHint(cfg.tokenExpiresAt)} · ${endpoint}`; + } catch (err) { + if (isAuthHttpStatus(err.status)) { + statusEl.className = 'conn-status conn-status--idle'; + statusEl.textContent = `Server restarted or token invalid · ${endpoint}`; + } else if (isNetworkFetchError(err)) { + statusEl.className = 'conn-status conn-status--idle'; + statusEl.textContent = `Cannot reach server · ${endpoint}`; + } else { + statusEl.className = 'conn-status conn-status--idle'; + statusEl.textContent = `Validation failed · ${endpoint}`; + } } } catch (_) { statusEl.className = 'conn-status conn-status--idle'; - statusEl.textContent = '无法读取配置'; + statusEl.textContent = 'Cannot read config'; } } -renderPopup(); +renderPopup().catch(() => {});