# Web Security Reviewer

> 對使用者自己的程式碼做防禦性安全審查，輸出依嚴重度排序的風險報告與修正後程式碼。當使用者要找漏洞、加固、擔心被攻擊或個資外洩，或貼上一段 AI 生成的程式碼要人幫忙看安不安全時觸發。也是 agentic-dev-loop Verify 雙閘的安全閘，供其他 skill 呼叫。

- Skill: `goingli0324/web-security-reviewer` (Agent Skill, multi-file: 11 files)
- Install (CLI): `npx skillmds@latest add goingli0324/web-security-reviewer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/goingli0324/web-security-reviewer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security, Web & Frontend
- Author: goingli0324 (https://skillmd.com/u/goingli0324)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/goingli0324/web-security-reviewer

---


# 網頁程式碼安全檢測（Web Security Reviewer）

對使用者提供的程式碼做有系統的防禦性審查，輸出一份結構化風險報告與可直接套用的修正版程式碼。目標是「找出問題 → 解釋為什麼危險 → 給可調整的修補方向」，而不是只丟一句「有漏洞」。

## 核心原則

1. **嚴重度看脈絡，不確定就先問。** 同一段程式碼，部署成「只有自己能用的內部工具」和「對外公開的網路服務」，嚴重度天差地遠。動手前先確認：這段程式碼的用途、執行環境、是否處理個資、怎麼部署、誰會存取。脈絡不清時，先問一兩個關鍵問題再審查，不要自己腦補成「比較安全」的版本。

2. **誠實標註限制。** 靜態讀程式碼 ≠ 滲透測試或資安稽核，一定有抓不到的東西（商業邏輯漏洞、執行期才暴露的問題）。報告「乾淨」不等於系統安全。每份報告都要有「未能評估的部分」這一段。

3. **套件漏洞不要憑記憶背。** 相依套件的 CVE 依賴當前漏洞資料庫與 lockfile，模型的知識有時間截點。要明確要求使用者跑 `npm audit` / `pip-audit` / `osv-scanner`，不要列出可能過時的 CVE 編號當定論。判斷該專案是否適用、選哪個工具、怎麼讀結果，見 `references/dependency-scanning.md`（純 GAS 線上專案通常不適用，要特別留意）。

4. **能直接修的就直接修，不能替使用者決定的才用列的。** 把每個發現歸成三類並標記：
   - **✅ 已直接修正**：機械式、低風險、不改變預期功能的修正（輸入驗證、輸出跳脫、公式注入防護、參數化查詢、加鎖、log 遮蔽、把硬寫金鑰改成從設定讀取、分頁上限、CORS／標頭設定）→ 直接套進輸出的修正版程式碼，使用者貼上即可用。
   - **⚠️ 需你決定**：牽涉產品取捨或動到使用者體驗的改動（改回傳格式、改執行身分、停用功能）→ 給建議與取捨，保留決定權。
   - **🔧 需你操作**：skill 動不了的環境層面（部署設定、作廢金鑰、寫入真實祕密值、更新套件、跑 audit／壓測、合規確認）→ 列成待辦。
   邊界：skill 只在「輸出的程式碼」裡套用修正，**絕不**代替使用者操作其環境（不改部署、不動分享權限、不寫入真實金鑰、不執行交易）。

5. **要詳盡。** 每個發現不要只說「有漏洞」。要寫到：問題機制（為什麼會發生）、一個具體但不武器化的攻擊情境示例（讓使用者真的看懂風險）、修補程式碼、以及驗證方式（修完怎麼確認有效）。寧可細，不要含糊。

6. **給方向、標取捨。** 「可選」建議要說明採納好處、不採納後果與取捨（例如為了效能快取資料，反而可能放大外洩面）。

7. **防禦導向。** 描述漏洞時，講清楚到「足以理解並修補」即可，不要寫成可直接複製拿去攻擊的武器化 payload。

8. **審「真正被送出去的東西」，不是只審原始碼與版控。** 「有沒有進 git」和「有沒有被發佈到線上」是**兩件事**，只查前者會給出錯誤的安心。最常見的陷阱：一份被 `.gitignore` 擋住的資料檔，被程式 `import` 進來，打包時整份塞進 bundle——版控乾淨，線上全公開。**外洩與否的判準永遠是「匿名的人實際抓得到什麼」**，所以第 4 步是必做，不可因為「原始碼看起來沒問題」而略過。

## 工作流程

### 第 0 步：釐清範圍
確認受檢程式碼、執行環境、是否處理個資、部署方式、預期使用者規模。若關鍵脈絡缺失（尤其「是否對外公開」與「是否碰個資」），先問再審。

### 第 1 步：偵測技術棧 → 載入對應清單
依程式碼語言／框架，讀取需要的 reference（只讀相關的，不要全載）：
- Google Apps Script（`.gs`、`doGet`/`doPost`、`SpreadsheetApp`、`appsscript.json`）→ `references/security-apps-script.md`
- 前端 HTML / CSS / JS（瀏覽器端執行的程式）→ `references/security-frontend.md`
- 後端 API（Node / Python / PHP / Go 等伺服器端）→ `references/security-backend.md`
- **一律加做**（不分技術棧）→ `references/data-leakage.md`
- **若專案有相依套件**（`package.json` / lockfile / `requirements.txt` 等）→ `references/dependency-scanning.md`（先判斷適用性與選工具；純 GAS 線上專案通常跳過）
- **若程式碼是 AI 生成 / vibe coding 寫的**（使用者明說，或從風格判斷）→ 先讀 `references/ai-generated-code.md` 做優先快掃。這類碼有特定弱點樣態（殘留金鑰、字串拼接、為了能跑而放寬設定、虛構套件），且使用者未必讀懂每行，要更傾向「直接修好＋白話解釋」。

**快速加固模式**：使用者剛貼上一段新生成的碼、只想要快速回饋時，用簡版輸出（見 report-format.md 簡版模式）——以 AI 生成弱點清單快掃、直接給修正版，省略完整壓測／優化段，但保留「限制」與「需你處理」。

### 第 2 步：安全審查
對照載入的清單逐類檢查。優先順序大致是：注入（injection）> 認證／授權 > 資料外洩 > 設定與標頭 > 其他。把每個發現記下「位置、問題、影響、修補」。

### 第 3 步：資料外洩專項
依 `data-leakage.md` 走一遍——硬寫的金鑰、過度詳細的錯誤訊息、log 寫入 PII、URL 帶敏感參數、source map 暴露、CORS 過寬等。處理真實個資時，提醒這牽涉《個人資料保護法》，但只做提醒、不下法律判斷，建議使用者諮詢權責單位。

### 第 4 步：部署產物、環境變數與硬編值（必做，不可略過）

前三步看的是**原始碼**。這一步看的是**真正被送到使用者手上的東西**。三者一起做，因為它們回答的是同一個問題：**哪些敏感值存在，其中哪些會抵達客戶端。**

#### 4-1 部署產物掃描

先確認產物在哪（`dist/`、`out/`、`build/`、`public/`，或 GAS 的線上版本），然後掃它、不是掃 `src/`：

- **被 `.gitignore` 擋住的資料檔有沒有進產物** —— 這是最常漏的一種。`import data from './real-data.json'` 會讓打包工具把整份 JSON 內嵌進 bundle：版控乾淨、線上全公開。做法：列出「被忽略但存在的資料檔」，各取一個具辨識度的字串，`grep -rF` 產物目錄。
- **金鑰與個資樣式**：`AIza…`、`sk-…`、`AKIA…`、`-----BEGIN … PRIVATE KEY`、真實 email、身分證字號、手機號碼。
- **範本／範例／種子資料**：`TEMPLATE_EXAMPLES`、`SAMPLE_`、`seed`、測試 fixture——很常有人為了省事直接複製一列真實資料進去。判準：同一組範例裡，有沒有哪一筆的格式跟其他筆不一樣（其他用「王小明／A123456789」，唯獨一筆是真名真號）。
- **靜態託管沒有認證層**：Firebase Hosting／GitHub Pages／S3 等會把檔案原樣送出，**app 內的登入閘擋不住直接抓檔**。所以「要登入才看得到」不是防護。

**完成條件**：對**已部署的網址**實際發過請求（`curl -sL <url> | grep -c <指紋>`），並貼出指令與輸出。只在本機掃 `dist/` 不算——本機產物可能與線上不同版。若尚未部署，明確標註「僅掃本機產物，線上未驗證」。

#### 4-2 環境變數掃描

- **列出所有 `.env*` 檔的變數名稱**（只列名稱，不印值），確認 `.gitignore` 有擋、且 `git log --all -- .env*` 為空（曾經 commit 過就等於已外洩，見下方鐵則）。
- **辨識「會被打進前端」的前綴**：`NEXT_PUBLIC_*`、`VITE_*`、`REACT_APP_*`、`PUBLIC_*`、`EXPO_PUBLIC_*` —— 這些**依設計就會內嵌進客戶端 bundle**。凡是掛這種前綴的值，一律當作公開資訊看待。發現真正的機密掛了公開前綴 → **Critical**。
- **反過來查**：`.env` 裡的機密有沒有出現在產物裡（拿值去 `grep` 產物）。
- **判斷哪些「公開值」不是漏洞**：Firebase web config（`apiKey`、`authDomain`、`projectId`…）、OAuth client ID、GA 測量 ID、公開端點網址——這些**本來就是公開值**，真正的保護在 security rules／後端授權。不要把它們報成漏洞，那會稀釋真正的發現；但要順手確認「保護真的存在」。

#### 4-3 硬編值檢視

掃原始碼裡寫死的值，分四類處置：

| 類型 | 例 | 處置 |
|---|---|---|
| **真機密** | API key、密碼、token、私鑰、webhook secret | **Critical**，且**先作廢輪替、再清紀錄**（清了 git 歷史不等於沒外洩過） |
| **個資** | 真實姓名、email、身分證、電話 | 依情境定級；範例資料一律換成佔位符 |
| **識別碼** | Sheet ID、Drive 檔案 ID、專案 ID、部署網址 | 多半非機密，但屬非必要暴露；註解裡尤其該拿掉 |
| **設定值** | 管理者 email、對外網址、常數門檻 | 建議移到設定層（Script Properties／env），理由是可維護性而非安全 |

特別留意**自我參照的網址常數**（如 `WEB_APP_URL` 寫死自己的部署網址）：重新部署會產生新網址，忘了同步就會「看起來成功、實際打到舊端點」。

### 第 5 步：壓力測試與效能 → `references/stress-and-performance.md`
分兩件事：(a) 靜態找出風險點（無上限迴圈、缺分頁、缺限流、N+1、Apps Script 配額／執行時間）；(b) 產出一份「壓測計畫」（工具、場景、指標、通過門檻）。明講：這是計畫，不是結果；真正壓測要對著已部署的非正式環境跑。

### 第 6 步：程式品質與優化
可讀性、錯誤處理、結構、重複碼、命名。這一段的建議**預設標為「可選」**，除非某個寫法本身是安全問題。

### 第 7 步：彙整輸出 → `references/report-format.md`
依該模板產出詳盡報告：每個發現含機制／攻擊情境／修補／驗證方式並標 ✅/⚠️/🔧；把所有 ✅ 整合成「可直接貼上的修正版程式碼」；把 ⚠️ 與 🔧 整理成「需你親自處理」的勾選清單。

## 嚴重度分級

用「可能性 × 影響」概估（OWASP 風險評等的精神），需要更嚴謹可改用 CVSS 並註明。

| 等級 | 意義 | 處置 |
|---|---|---|
| **Critical** | 直接可被利用、導致資料外洩／系統被接管／遠端執行 | 立即修，未修不應上線 |
| **High** | 在常見條件下可被利用，影響嚴重 | 上線／下次部署前修 |
| **Medium** | 真實風險，但需特定條件或影響有限 | 排程儘快修 |
| **Low** | 輕微、屬縱深防禦 | 有空再修 |
| **Info** | 非漏洞，是強化建議或最佳實務 | 視情況採納 |

## 一定要對使用者講清楚的限制

- 這是靜態審查，不是滲透測試，會有 false negative。
- 相依套件漏洞請以 `npm audit` / `pip-audit` / `osv-scanner` 等工具的即時結果為準（適用性與選用見 `references/dependency-scanning.md`）。
- 壓測產出的是計畫不是結果。
- 涉及個資合規（PDPA）只做提醒，非法律意見。
- 「產物掃描乾淨」的前提是**已對線上網址實際發過請求**；若只掃本機產物，報告必須明說線上未驗證。
- 掃描抓得到有格式的東西（金鑰樣式、身分證、email、電話），**抓不到純中文姓名**——沒有可辨識格式。含人名的資料要靠人工判讀。

## 防禦邊界

本 skill 只做「審查自己程式碼、找漏洞、給修補」。若請求變成「寫一個能攻擊／繞過某系統的程式」「幫我利用這個漏洞」，那超出範圍，應婉拒並把方向拉回防禦與修補。

