@@ -58,6 +58,11 @@ export function startWebServer(opts: WebServerOptions): void {
5858 if ( active >= MAX_CONCURRENT ) return send ( res , 503 , 'text/html' , errorPage ( '服务繁忙,请稍后再试' ) )
5959 return await handleUpload ( req , res , locale , ( ) => { active ++ } , ( ) => { active -- } )
6060 }
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+ }
6166 if ( u . pathname === '/scan' ) {
6267 if ( active >= MAX_CONCURRENT ) {
6368 return send ( res , 503 , 'text/html' , errorPage ( '服务繁忙,请稍后再试(并发上限)' ) )
@@ -175,6 +180,35 @@ async function handleUpload(req: any, res: any, locale: 'zh' | 'en', inc: () =>
175180 }
176181}
177182
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+
178212/** 浅克隆公开仓库到临时目录(不鉴权、超时、不执行任何仓库代码) */
179213function cloneRepo ( url : string , dir : string ) : Promise < void > {
180214 return new Promise ( ( res , rej ) => {
@@ -224,6 +258,7 @@ function formPage(local: boolean): string {
224258 <p class="sub">${ local ? '选项目文件夹或贴公开仓库链接' : '贴公开仓库链接' } ,30 秒查出数据出境 / 硬编码密钥 / 个人信息暴露等中国合规红线。</p>
225259 ${ uploadForm }
226260 ${ urlForm }
261+ <p class="demo">🤔 觉得"秒出"不真? <a href="/demo">▶ 看一个含风险的示例报告</a>(同样秒出,但满屏发现 + 行号)</p>
227262 <p class="foot">网安法 2026 · PIPL · 等保2.0 · 数据出境 · AI标识 | 零依赖 · 开源 ·
228263 <a href="https://github.com/jnMetaCode/shellward">GitHub ⭐</a></p>
229264 </div>
@@ -301,6 +336,7 @@ button:disabled{background:#94a3b8;cursor:default}
301336form{margin:0 0 14px}.or{text-align:center;color:#94a3b8;font-size:13px;margin:6px 0 14px}
302337.status{display:none;margin:10px 0 0;padding:10px 14px;border-radius:8px;background:#f1f5f9;
303338color:#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}
304340.foot{margin:24px 0 0;font-size:12.5px;color:#94a3b8}.foot a,.back{color:#cb0000;text-decoration:none}
305341.back{font-weight:600}
306342</style></head><body>${ body } </body></html>`
0 commit comments