# Bilko CIAM abuse-gate fix — checkBefore moved outside SERIALIZABLE tx (MC #104069, root-cause of #103245)

# Bilko CIAM abuse-gate fix — checkBefore moved outside SERIALIZABLE tx

**MC #104069** | Root-cause of **MC #103245** | Fixed 2026-06-20

## 1. THE BUG (root cause)

MC #103245 \[H1-PRE-PUBLIC-LAUNCH\] CIAM abuse gate was marked done 2026-06-09 16:35, but the actual fix was committed 17:27 and **NEVER merged**. In `origin/main`, `CiamAbuseGate.checkBefore()` ran ONLY inside `UserProvisioningService.kt` (called from inside the SERIALIZABLE `transaction{}` block in `AuthService.createSessionFromEntraIdToken`, line 334).

Exposed's SERIALIZABLE transaction retry handler caught/swallowed `DisposableEmailException` and `TooManyRequestsException` → disposable-email accounts (e.g. guerrillamailblock.com) and over-rate-limit requests got provisioned with HTTP 200 despite the gate. **The disposable-email + rate-limit abuse gate was effectively defeated on the JIT/Entra provisioning path.**

## 2. THE FIX

**Commit:** `a862355a` → rebased `deb1621d`  
**Branch:** `fix/abuse-gate-tx-swallow-103245`  
**PR:** #3

### Changes:

- **FIX1:** `AuthService.kt` (~line 329) — `CiamAbuseGate.checkBefore(email)` now called **BEFORE** the SERIALIZABLE `transaction{}` opens; exceptions propagate directly to StatusPages with no retry/swallow.
- **FIX2:** `routes/AuthRoutes.kt` (lines 379-388) — explicit re-throw catches for `DisposableEmailException` and `TooManyRequestsException` before the broad `catch(Exception)`.
- `StatusPages.kt` (111-129): `DisposableEmailException` → HTTP 422 (VAL\_002); `TooManyRequestsException` → HTTP 429 (INFRA\_002).
- **New test:** `CiamAbuseGateTransactionPathTest.kt` (+223 lines) — TX1/TX2/TX3 exercising the full `createSessionFromEntraIdToken` path through the SERIALIZABLE tx wrapper (the gap prior unit tests missed).

## 3. VERIFICATION

- **Manual branch build #44** (Azure DevOps Bilko-CI-CD): `CI_Gates 8/8 PASS` + `Build` + `Flyway` + `Deploy_Stage` SUCCEEDED on commit `deb1621d`.  
    URL: [https://dev.azure.com/alai-holding/Bilko/\_build/results?buildId=44](https://dev.azure.com/alai-holding/Bilko/_build/results?buildId=44)
- **Proveo integration tests** (real PostgreSQL/Testcontainers): `CiamAbuseGateTransactionPathTest 3/3 PASS`, `CiamAbuseGateTest 4/4 PASS`.
- **Live stage** confirmed serving the fix; gate sits behind Entra JWT signature verification (security boundary — a fully external HTTP 422 probe is not reachable without a tenant-signed Entra token, which is itself a positive security property).
- **Evidence bundle:** `/tmp/evidence-104069/` (proveo-abuse-probe/VERDICT.md, test XMLs, probe captures; build-43/44 logs).

## 4. PROCESS LESSON

**A parent MC was closed before its fix was merged → the fix sat unmerged in a worktree for ~11 days.**

**Lesson:** Do not mark a security MC done until the fix is verified merged on main. Link this to the broader "no fake DONE" rule.

## References

- MC #104069 (parent security fix task)
- MC #103245 (original abuse gate task)
- Evidence bundle: `/tmp/evidence-104069/`
- PR #3: `fix/abuse-gate-tx-swallow-103245`