Skip to content

harden: gate WebSocket command plane, throttle login, lock down Modbus - #1

Open
kevin00156 wants to merge 1 commit into
mainfrom
claude/project-security-review-xth6y2
Open

harden: gate WebSocket command plane, throttle login, lock down Modbus#1
kevin00156 wants to merge 1 commit into
mainfrom
claude/project-security-review-xth6y2

Conversation

@kevin00156

@kevin00156 kevin00156 commented Jun 12, 2026

Copy link
Copy Markdown
Owner

背景

安全檢視發現:role-password 認證系統其實只擋了 HTTP 路由,但唯一存在的寫入面是 WebSocket 指令面(machine/axis/production)。在修補前,任何能連到該 socket 的主機都能下達機器控制指令,無視登入狀態——前端用 auth.satisfies() 鎖分頁只是 UI 障眼法。本 PR 補上這道牆並一併加固周邊弱點。

變更內容

🔴 核心漏洞修補 — WebSocket 指令面真正受門禁保護

  • wsserver.Server 新增 Authorizer,每條指令訊息依「最低角色」檢查:machine/production → operator、axis → tuner(commandRole,可依部署調整)。遙測(讀)維持常開,指令(寫)必須過牆。
  • auth 新增 Allows() / SessionRole(),並匯出 Role.Satisfies(),讓非 HTTP 路徑套用與 Wrap 完全相同的政策(含 operator 未設定時的 back-compat 放行)。
  • 每筆指令留下稽核日誌(來源、角色、型別、結果)。
  • 軸索引越界由「靜默回 OK」改為明確回錯。

🟡 登入加固

  • 以來源 IP 為單位的失敗鎖定,堵住 bcrypt 暴力破解與「少量請求放大成 CPU 耗盡」。
  • http.MaxBytesReader 限制登入 body 大小。

🟡 傳輸 / Server

  • 改用顯式 http.Server + ReadHeaderTimeout / IdleTimeout(擋 Slowloris;刻意不設 Read/WriteTimeout 以免砍掉長連線 WebSocket)。
  • 安全標頭(CSP、nosniff、X-Frame-Options DENY、Referrer-Policy;TLS 下才送 HSTS)。
  • 純 HTTP 啟動時警告密碼/cookie 明文傳輸。

🟡 Modbus(協定本身無認證)

  • 新增 --modbus-allow IP/CIDR allowlist,把無認證的 Modbus 面釘在已知 SCADA 主機;空值維持原本「允許所有」以不破壞既定 LAN 拓樸。
  • 閒置連線逾時回收 + 並發連線數上限。

🟢 前端

  • wsonmessagetry/catch,畸形封包不再讓 handler 拋例外。

測試

  • 新增:WS 真實 round-trip 門禁測試、登入鎖定、Allows 政策、Modbus allowlist。
  • go vet ./backend/... 乾淨;go test ./backend/... 全綠;GOOS=linux GOARCH=amd64 交叉編譯通過;前端 npm ci && npm run build 通過。

待決策(非阻擋)

  1. Modbus 預設仍綁所有介面(README 拓樸顯示外部 SCADA 連 :5020)。正式環境建議用 --modbus-allow 或網段層限制。
  2. CSP 含 style-src 'unsafe-inline',為相容 Svelte 注入的 scoped style 而保留;若要收緊成 nonce-based 需動到前端 build。

https://claude.ai/code/session_017Ku75qTnU5fdUeAyKCsYv1

The role-password auth only ever guarded HTTP routes, but the one write
surface that exists is the WebSocket command plane (machine/axis/production)
— so any host that could reach the socket could command the machine
regardless of login state. Several supporting weaknesses compounded it.

Auth / command plane:
- Enforce the login wall on the WS command plane: wsserver.Server now holds
  an Authorizer and checks a per-command minimum role (machine/production →
  operator, axis → tuner) on every message. Telemetry stays open; commands
  do not. auth gains Allows()/SessionRole() and exports Role.Satisfies().
- Audit-log every command attempt (peer, role, type, outcome).
- Reject out-of-range axis index instead of silently acking OK.

Login hardening:
- Per-IP failed-login throttle (lockout after repeated wrong passwords) so
  bcrypt can't be brute-forced or amplified into CPU exhaustion.
- Cap the login body with MaxBytesReader.

Transport / server:
- Explicit http.Server with ReadHeaderTimeout + IdleTimeout (Slowloris).
- Conservative security headers (CSP, nosniff, frame-deny, HSTS under TLS).
- Warn loudly when serving plain HTTP (password/cookie in clear).

Modbus (auth-less by protocol):
- Optional --modbus-allow IP/CIDR allowlist to pin the surface to known
  SCADA hosts; idle-connection reaping and a concurrent-connection cap.

Frontend:
- Guard ws onmessage JSON.parse against malformed frames.

Adds tests for the WS gate (real round-trip), login throttle, Allows policy,
and the Modbus allowlist. Docs updated (backend README security section,
systemd unit note).

https://claude.ai/code/session_017Ku75qTnU5fdUeAyKCsYv1
@kevin00156
kevin00156 enabled auto-merge (rebase) July 20, 2026 06:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants