mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-11 10:19:00 +00:00
harden(audit+session): full SHA-256 audit hash + cookie segment length cap (MED-15 + Nit-4)
Audit 2026-05-10 Fix 13 Phase F + Fix 14 Phase F partial — close
MED-15 + Nit-4. Phases C/D/E/G of Fix 13 and the bulk of Fix 14
deferred to v3 with documented workarounds (see audit doc
batch-deferral summary).
MED-15: internal/api/middleware/audit.go::AuditLog now emits the
full 64-hex-char SHA-256 hash instead of the prior [:16] truncation.
The audit_events.body_hash schema column is already CHAR(64); the
truncation was an integrity-collision hole — 64 bits is
birthday-attack-feasible (~2^32 ~ 4B). Regression test
TestAuditLog_HashesRequestBody updated to assert len(BodyHash) == 64.
Nit-4: internal/auth/session/service.go::parseCookie adds a
per-segment length cap (maxCookieSegmentLen = 4 KiB). Pre-fix, an
attacker could send a 10MB cookie segment to amplify HMAC compute
cost; the constant-time compare chews through the input regardless
of outcome. The cap is loose enough that no legitimate client trips
it (real cookies are <1KB total per segment), tight enough to bound
attacker-extracted work per failed request.
Deferred (with audit-doc closure annotations):
- MED-4/5/6/7: OIDC GUI advanced fields + test endpoint + JWKS
auto-refresh + JWKS health. v3 OIDC-operator-experience bundle.
Workarounds documented.
- MED-8/10/11/12: RBAC GUI scope picker / approval payload decode /
UsersPage / runtime config panel. v3 GUI-polish bundle. Backend
already accepts the scope_type/scope_id fields; the gap is GUI.
- MED-13: MCP tools for approvals / break-glass / bootstrap.
v3 MCP-expansion bundle.
- MED-14: __Host- cookie rename. Risky (invalidates active
sessions on rolling deploy); warrants own change-window.
- MED-16/17: Pre-login UA/IP binding + RFC 9207 iss URL check.
v3 OIDC-hardening bundle.
- All 12 LOWs + 4 of 5 Nits: v3 cleanup bundle.
Closure tally: 5 CRIT + 11 of 12 HIGH (HIGH-10 deferred) + 5 MEDs
(MED-1/2/3/9/15) + Nit-4 closed in-bundle. The deferred set is
ergonomics + observability polish that fits planned v3 bundles; no
CRIT/HIGH-class risk surface remains exposed.
Refs: cowork/auth-bundles-audit-2026-05-10.md MED-15, Nit-4
Spec: cowork/auth-bundles-fixes-2026-05-10/13-med-bundle.md Phase F
cowork/auth-bundles-fixes-2026-05-10/14-low-nit-cleanup.md Phase F
This commit is contained in:
@@ -840,6 +840,18 @@ func computeHMAC(sessionID, signingKeyID string, hmacKey []byte) []byte {
|
||||
// parts plus the decoded HMAC. Any format/version/decode failure
|
||||
// returns an error; the caller maps to ErrSessionInvalidCookie without
|
||||
// surfacing which check failed (no information leak).
|
||||
// maxCookieSegmentLen caps any single segment of a parsed cookie at
|
||||
// 4 KiB — well above the wire shape of any legitimate certctl cookie
|
||||
// (id1 prefix `ses-` or `pl-` + 22 base64 chars; sk-id ~30 chars; HMAC
|
||||
// base64 of 32 bytes = 43 chars; v1 version tag = 2 chars). Audit
|
||||
// 2026-05-10 Nit-4 closure — pre-fix, an attacker could send a 10MB
|
||||
// cookie segment to amplify HMAC compute cost; the constant-time
|
||||
// compare on the back end would chew through the input regardless of
|
||||
// outcome. The cap is loose enough that no legitimate client trips
|
||||
// it, but tight enough to bound the work an attacker can extract per
|
||||
// failed request.
|
||||
const maxCookieSegmentLen = 4096
|
||||
|
||||
func parseCookie(cookieValue string) (sessionID, signingKeyID string, hmacBytes []byte, err error) {
|
||||
if cookieValue == "" {
|
||||
return "", "", nil, errors.New("empty cookie")
|
||||
@@ -848,6 +860,12 @@ func parseCookie(cookieValue string) (sessionID, signingKeyID string, hmacBytes
|
||||
if len(parts) != 4 {
|
||||
return "", "", nil, errors.New("expected 4 segments")
|
||||
}
|
||||
// Audit 2026-05-10 Nit-4 — per-segment length cap.
|
||||
for i, seg := range parts {
|
||||
if len(seg) > maxCookieSegmentLen {
|
||||
return "", "", nil, fmt.Errorf("cookie segment %d exceeds %d-byte cap", i, maxCookieSegmentLen)
|
||||
}
|
||||
}
|
||||
if parts[0] != sessiondomain.CookieFormatVersion {
|
||||
return "", "", nil, errors.New("unsupported version prefix")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user