docs(audit): close SEC-08, SEC-09 and SEC-26 - #15
Conversation
…e verified Marks the three IDs Done and records what actually shipped, in the dependency order that mattered: SEC-26 first, because the other two key on a client address that was forgeable until it landed. SEC-26 (PR #13). ServerConfig gains trustedProxies and trustedPlatform, applied in NewServer before any middleware. An unparseable CIDR fails startup rather than quietly dropping the trust boundary. The default is the loopback, RFC1918, IPv6 unique-local and link-local ranges rather than Gin's trust-everything default, and trustedPlatform defaults to empty on purpose - an edge that appends instead of overwriting would hand the header back to the caller. SEC-09 (PR #13). isCredentialLocked now takes the client address and applies two windows: principal-and-address for one source grinding on one account, and address-across-all-principals for credential stuffing. The four existing lockout tests failed when the key changed, because they seeded credential logs without a client address - they encoded the vulnerable semantics. That failure is the evidence the fix is real. SEC-08 (PR #14). Six public routes limited, with the channel webhooks, websockets, HMAC webhooks and the whole dashboard deliberately exempt; a test fires 1000 requests at five of those routes and fails on any 429, because a platform that gets throttled on its webhook eventually disables the delivery and takes a channel offline. Also recorded, because leaving them out would make the document read better than the work was: - Retry-After shipped as int(seconds)+1 at first, which returns 61 for a 60 second window whenever the clock has not ticked between requests. My own test caught it. - The limiter could not be validated under -race. This repository builds with CGO disabled and go test -race requires cgo. The concurrency test still has teeth, since an unguarded concurrent map write panics rather than merely miscounting, but it is not a substitute for the race detector. - SEC-26, SEC-09 and SEC-08 have not been ported upstream. They cannot be replayed the way the upload fix was: config.go on dev carries fork-only fields, and server.go on dev already contains the storage hardening still sitting in the open upstream PR huabeitech#40. - A second git incident. A file showed as modified in git status while git diff was empty - a stat-cache artifact after gofmt then restore - and it blocked a fast-forward. Resolved by proving the content identical with git diff --exit-code HEAD, then git add --renormalize, then confirming the staged diff was empty before continuing. Remaining count moves from 89 to 86. Every declared number in the document is cross-checked against the register tables by script across 18 sites plus the per-prefix stats table and its total row; the check passes.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_2cff6545-1bbb-4cac-9141-e31751a1fdd3) |
There was a problem hiding this comment.
Code Review
This pull request updates the audit and issue register documentation in docs/CROVE_DESK_AUDIT.html to record the resolution of security issues SEC-08 (rate limiting), SEC-09 (credential lockout), and SEC-26 (trusted proxies). The feedback suggests a minor improvement to maintain language consistency in the Vietnamese documentation by replacing 'with' with 'với' in the CGO build description.
| <tr><td class="id">SEC-07 <span class="badge b-good">Done</span></td><td><span class="badge b-high">High</span></td><td>WebSocket bypass permission <code>conversation.view</code>. REST gate ở 5 endpoint; WS trả <code>true</code> cho mọi admin-role session. Payload chứa <strong>full nội dung tin nhắn</strong>. ID tuần tự → enumerate được.</td><td class="loc">services/ws_service.go:624-628, 301-317 · vs handlers/dashboard/conversation_handler.go:24,75,108,127,287</td><td class="v v-y">✓</td></tr> | ||
| <tr><td class="id">SEC-08</td><td><span class="badge b-med">Med</span></td><td>Không có rate limiting ở bất kỳ đâu (grep 0 kết quả). Khuếch đại SEC-01 (flood 20MB), SEC-09, spam register, và DoS tốn tiền LLM qua flood tin nhắn.</td><td class="loc">toàn bộ internal/</td><td class="v v-y">✓</td></tr> | ||
| <tr><td class="id">SEC-09</td><td><span class="badge b-med">Med</span></td><td>Credential lockout key theo <strong>username, không theo IP</strong> → attacker biết username (vd <code>admin</code>) khóa tài khoản thật 15 phút, lặp vô hạn = DoS vĩnh viễn.</td><td class="loc">services/auth_service.go:465-479</td><td class="v v-y">✓</td></tr> | ||
| <tr><td class="id">SEC-08 <span class="badge b-good">Done</span></td><td><span class="badge b-med">Med</span></td><td>Không có rate limiting ở bất kỳ đâu (grep 0 kết quả). Khuếch đại SEC-01 (flood 20MB), SEC-09, spam register, và DoS tốn tiền LLM qua flood tin nhắn.<br><b>Đã đóng (PR #14, 2026-09-13).</b> Package <code>internal/pkg/ratelimit</code> fixed-window in-process + middleware, gắn vào 6 route public: login 20 · register 10 · session_exchange <b>120</b> (phải chịu được cả văn phòng sau một NAT) · upload attachment+image 30 dùng chung · doc feedback 20; window 60s, bật/tắt và window đều config được. Trả <b>429 thật</b> + <code>Retry-After</code> (làm tròn <em>lên</em>) + body <code>JsonResult</code> — đã đọc <code>web/lib/api/client.ts:54-66</code> để xác nhận frontend parse body <em>trước</em> rồi mới check <code>response.ok</code>, nên message đã dịch vẫn hiện, và <code>im.ts</code> dùng chung <code>request</code> đó. <b>Cố ý KHÔNG limit</b> <code>/api/third/*</code> (platform nhận 429 sẽ ngừng retry rồi <em>tắt luôn webhook</em> = mất cả kênh), <code>/api/ws/*</code>, <code>/api/webhooks/*</code> (đã HMAC), <code>/api/dashboard/*</code> (đã auth, staff chung IP sẽ khoá lẫn nhau) — và có test bắn <b>1000 request vào 5 route đó</b>, fail nếu ra bất kỳ 429 nào. 15 test mới, gồm test 16 goroutine × 3200 call đòi đúng 1000 được phép. <b>Giới hạn:</b> counter per-process nên chạy nhiều replica thì bound yếu đi theo số replica; và <b>không chạy được <code>-race</code></b> vì repo build với CGO tắt.</td><td class="loc">toàn bộ internal/</td><td class="v v-y">✓</td></tr> |
There was a problem hiding this comment.
There is a minor language inconsistency in this line. The English word 'with' is used in 'vì repo build with CGO tắt', whereas on line 612, the Vietnamese equivalent 'với' is used ('vì repo build với CGO tắt'). Consider changing 'with' to 'với' to maintain consistency in the Vietnamese documentation.
<tr><td class="id">SEC-08 <span class="badge b-good">Done</span></td><td><span class="badge b-med">Med</span></td><td>Không có rate limiting ở bất kỳ đâu (grep 0 kết quả). Khuếch đại SEC-01 (flood 20MB), SEC-09, spam register, và DoS tốn tiền LLM qua flood tin nhắn.<br><b>Đã đóng (PR #14, 2026-09-13).</b> Package <code>internal/pkg/ratelimit</code> fixed-window in-process + middleware, gắn vào 6 route public: login 20 · register 10 · session_exchange <b>120</b> (phải chịu được cả văn phòng sau một NAT) · upload attachment+image 30 dùng chung · doc feedback 20; window 60s, bật/tắt và window đều config được. Trả <b>429 thật</b> + <code>Retry-After</code> (làm tròn <em>lên</em>) + body <code>JsonResult</code> — đã đọc <code>web/lib/api/client.ts:54-66</code> để xác nhận frontend parse body <em>trước</em> rồi mới check <code>response.ok</code>, nên message đã dịch vẫn hiện, và <code>im.ts</code> dùng chung <code>request</code> đó. <b>Cố ý KHÔNG limit</b> <code>/api/third/*</code> (platform nhận 429 sẽ ngừng retry rồi <em>tắt luôn webhook</em> = mất cả kênh), <code>/api/ws/*</code>, <code>/api/webhooks/*</code> (đã HMAC), <code>/api/dashboard/*</code> (đã auth, staff chung IP sẽ khoá lẫn nhau) — và có test bắn <b>1000 request vào 5 route đó</b>, fail nếu ra bất kỳ 429 nào. 15 test mới, gồm test 16 goroutine × 3200 call đòi đúng 1000 được phép. <b>Giới hạn:</b> counter per-process nên chạy nhiều replica thì bound yếu đi theo số replica; và <b>không chạy được <code>-race</code></b> vì repo build với CGO tắt.</td><td class=
Documentation only - one file. CI skips it because
docs/is in thepaths-ignorelist.Marks the three IDs Done and records what shipped, in the dependency order that mattered: SEC-26 first, because SEC-09 and SEC-08 both key on a client address that was forgeable until it landed. Building them in the other order would have produced a control an attacker could lift off with one header.
trustedProxiesandtrustedPlatformapplied inNewServerbefore any middleware. An unparseable CIDR fails startup rather than quietly dropping the trust boundary. The default is the loopback / RFC1918 / IPv6 unique-local / link-local ranges, not Gin's trust-everything default.trustedPlatformdefaults to empty on purpose: an edge that appends instead of overwriting would hand the header straight back to the caller.isCredentialLockednow takes the client address and applies two windows: principal-and-address for one source grinding on one account, and address-across-all-principals for credential stuffing. An address the server cannot determine normalises to oneunknownbucket and is excluded from the per-address window, so a missing IP cannot pool every such caller into a single lockout.Also recorded, because leaving these out would make the document read better than the work was
int(seconds)+1returns 61 for a 60 second window whenever the clock has not ticked between requests - which on Windows means three requests in a row. My own test caught it; it now rounds up properly.-race. This repository builds with CGO disabled andgo test -racerequires cgo. The concurrency test still has teeth, because an unguarded concurrent map write panics rather than merely miscounting, but it is not a substitute for the race detector.internal/pkg/config/config.goondevcarries fork-only fields, andinternal/bootstrap/server.goondevalready contains the storage hardening still sitting in the open upstream PR fix(storage): stop guest uploads from becoming same-origin script execution huabeitech/agent-desk#40. Copying either file across would ship fork configuration upstream and duplicate an open pull request.git statuswhilegit diffwas empty - a stat-cache artifact aftergofmt -wthengit restore- and it blocked a fast-forward merge. Resolved by proving the content identical withgit diff --exit-code HEAD, thengit add --renormalize, then confirming the staged diff was empty before continuing. No content byte changed.Register arithmetic
Remaining moves from 89 to 86. All six P0 IDs are now addressed - four Done, two Partial, none open.
Every declared number in the document is cross-checked against the register tables by script: 18 prose and stat-card sites, plus the per-prefix stats table and its total row. The check passes, along with tag balance and a sweep for stale markers such as the previous remaining-count and the superseded Round 5 header.
Note
Low Risk
Single HTML audit file only; no runtime, auth, or deployment behavior changes.
Overview
Documentation-only update to
docs/CROVE_DESK_AUDIT.html(v5 verification log + register rows). No application code;docs/**is in CIpaths-ignore, so this PR does not run the test workflow.The audit register now records SEC-26, SEC-09, and SEC-08 as Done, with closure tied to PR #13 (trusted proxy / client IP, then IP-aware credential lockout) and PR #14 (public-route rate limiting). Prose stresses dependency order: SEC-26 had to land before IP-based lockout and limits, or those controls could be bypassed via forged headers.
Counts and status narrative move from 21 → 24 closed IDs and 89 → 86 remaining; Round 5 header states SEC-26 was introduced and closed in the same round. The “what’s left” section now says all six P0 items are addressed (four Done, two Partial) and replaces “do SEC-26 next” with “that chain is closed,” listing follow-ups such as SEC-02 plaintext password, SEC-10/12/13, and vi-VN strings.
The v5 (2026-09-13) block in the verification log adds merge notes for PRs #12–#14, the Retry-After ceiling fix, honest limits (no
-raceon the limiter, no upstream port yet), local/CI test results, a git stat-cache/CRLF merge unblock, and AGENTS.md rules via PR #10.Reviewed by Cursor Bugbot for commit 31820b5. Configure here.