Merge Fix 06 (HIGH A-6): strict UA/IP binding — close request-empty bypass in MED-16

# Conflicts:
#	CHANGELOG.md
#	internal/api/handler/auth_session_oidc.go
#	internal/api/handler/auth_session_oidc_test.go
This commit is contained in:
shankar0123
2026-05-11 11:19:04 +00:00
5 changed files with 209 additions and 2 deletions
@@ -1275,6 +1275,14 @@ func classifyOIDCFailure(err error) string {
// detail subject to change.
case errors.Is(err, oidcsvc.ErrUserDeactivated):
return "user_deactivated"
// Audit 2026-05-11 A-6 — strict-when-stored. Distinguishes the
// new "request omitted the bound header" reject path from the
// existing "header was supplied but didn't match" path so SIEM
// rules can alert specifically on attempted bypasses.
case errors.Is(err, oidcsvc.ErrPreLoginUAMissing):
return "prelogin_ua_missing"
case errors.Is(err, oidcsvc.ErrPreLoginIPMissing):
return "prelogin_ip_missing"
}
msg := strings.ToLower(err.Error())
switch {
@@ -1222,6 +1222,17 @@ func TestClassifyOIDCFailure(t *testing.T) {
// round-trip).
{oidcsvc.ErrUserDeactivated, "user_deactivated"},
{fmt.Errorf("upstream: %w", oidcsvc.ErrUserDeactivated), "user_deactivated"},
// Audit 2026-05-11 A-6 — strict-when-stored. Distinguishes the
// new request-omitted-binding reject path from the existing
// mismatch leg. Wrapped variants must round-trip through
// errors.Is so the audit category remains stable even when
// the service layer adds context wrapping.
{oidcsvc.ErrPreLoginUAMismatch, "prelogin_ua_mismatch"},
{oidcsvc.ErrPreLoginIPMismatch, "prelogin_ip_mismatch"},
{oidcsvc.ErrPreLoginUAMissing, "prelogin_ua_missing"},
{oidcsvc.ErrPreLoginIPMissing, "prelogin_ip_missing"},
{fmt.Errorf("upstream: %w", oidcsvc.ErrPreLoginUAMissing), "prelogin_ua_missing"},
{fmt.Errorf("upstream: %w", oidcsvc.ErrPreLoginIPMissing), "prelogin_ip_missing"},
{errors.New("some other error"), "unspecified"},
}
for _, tc := range cases {