RiskControl evaluates a login environment and recommends None, Captcha, MFA, StepUp, or Block. It raises the cost of automated and anomalous access, but it does not replace password checks, account status, authorization, rate limiting, or audit.
1. Capability map
| Layer | Main types | Responsibility |
|---|---|---|
| Contracts | RiskAssessment, RiskPolicy, IRiskFactor, captcha ports | Stable models, narrow ports, and AOT JSON |
| Application | rule engine, four factors, policy provider, persistence pipeline | Evaluation, tenant policy, and post-transaction recording |
| Infrastructure | RiskAssessmentStore | Unified aggregate writes and QueryShape reads |
| Identity | login-history provider, LoginFlow, risk-driven MFA | Provides facts and executes challenges |
| API and Website | captcha filter and Website captcha endpoint | HTTP adaptation and anonymous form protection |
There is no rule-management endpoint, machine-learning model, geo-anomaly factor, or weak-password factor in the current module. It is an extensible deterministic rule engine.
2. Login path
Login evaluates before password verification. Block avoids BCrypt work; Captcha returns a challenge and waits for a second submission; MFA and StepUp both enter the existing MFA path; None continues normal authentication.
3. Built-in factors
| Factor | Weight | Trigger | Current evidence |
|---|---|---|---|
| NewIp | 20 | non-empty IP absent from the recent set | raw IP and known count |
| NewDevice | 15 | device info or user agent absent from known devices | raw fingerprint and known count |
| HighFrequency | 40 | at least three failures in five minutes | failure count and threshold |
| OffHours | 10 | business-local time from 00:00 to 06:00 | hour and full business time |
All four total 85. The default Block range starts at 91, so built-in factors alone cannot reach Block. A tenant must lower the threshold or an extension must add another valid factor.
Read Rule engine and risk factors.
4. Default policy
| Score | Level | Challenge | Login behavior |
|---|---|---|---|
| 0–30 | Low | None | continue password checks |
| 31–50 | Medium | Captcha | issue or verify image captcha |
| 51–75 | High | MFA | require MFA after password |
| 76–90 | Critical | StepUp | currently enters the same MFA path |
| 91–100 | Block | Block | reject before password |
Tenants override the complete mapping through riskcontrol.policy. Read, JSON, or policy-validation failure falls back to the default and logs a warning; caller cancellation still propagates.
Read Tenant policy and evidence persistence.
5. Captcha capability versus wiring
The module implements ImageCode, Slider, PointSelect, and optional Behavior providers. In-house providers use five-minute tickets, random nonces, salted SHA-256 values, and Redis GETDEL.
Actual business wiring is narrower:
- Login and Website call
ResolveDefault()and therefore use ImageCode. - Unconfigured Behavior silently resolves to ImageCode.
- Slider and PointSelect are registered, but no business endpoint selects them.
RequireCaptchaEndpointFilteris attached nowhere.- The filter rejects Result failure but lets
Success(Passed=false)continue. - Login does not validate that a submitted ChallengeId belongs to the current tenant and pseudo-session; Website does.
Read Captcha providers and one-time tickets.
6. Independent assessment recording
Login.Command implements IRiskAssessedRequest. The engine captures one successful assessment in scoped execution context. An outer pipeline records it with a fresh unit of work after the business transaction succeeds, returns failure, or throws.
try{ // The inner path contains the login transaction and produces its own outcome. return await next(message, cancellationToken);}finally{ // Evidence uses an independent non-cancelled unit of work after business completion. await PersistPendingAssessmentAsync();}Persistence failure logs an error and preserves the original outcome. That protects availability but can lose security evidence; production needs metrics, alerts, and possibly durable compensation.
7. Query, tenancy, and privacy
GET /api/risk/assessments requires risk-control.assessment.view. The handler takes TenantId from CurrentUser and the Store adds an explicit tenant predicate before provider-neutral paging.
Two boundaries remain:
- during formal tenant impersonation, CurrentUser may retain the operator tenant while Effective CurrentTenant is the target;
- response items expose complete factors containing raw IP, device fingerprint, and business time without a redacted projection.
Read Identity, Website, and query integration.
8. Failure policy by stage
| Failure point | Current policy |
|---|---|
| history, factor, or engine exception | Engine returns failure; Login warns and continues without risk challenge |
| unreadable or invalid setting | default policy |
| Login captcha generation/provider failure | login fails |
| wrong captcha answer | Login and Website deny; general filter currently allows |
| no Redis registration | no-op ticket store; generation appears successful and every verification fails |
| assessment record failure | roll back evidence transaction, log, preserve business outcome |
| damaged assessment snapshot | query returns stable failure |
It is inaccurate to describe the entire module as simply fail-open or fail-closed.
9. Chapter map
| Goal | Page |
|---|---|
| Understand snapshots, parallel factors, scoring, and reachability | Decision engine |
| Generate, bind, and consume four challenge types safely | Captcha providers |
| Configure tenant policy and record immutable evidence | Policy and persistence |
| Integrate Login, MFA, Website, and query API | Integration boundaries |
| Establish security, privacy, fault, and production gates | Testing and operations |
10. Source review
# Review engine, factors, and the actual Login consumer together.rg -n "RuleBasedRiskEngine|IRiskFactor|RequiredChallenge|AssessAsync" \ src/Platform/RiskControl src/Platform/Identity -g '*.cs'
# Locate every challenge-generation, verification, and endpoint-wiring path.rg -n "GenerateAsync|VerifyAsync|RequireCaptchaEndpointFilter|ResolveDefault" \ src/Platform/RiskControl src/Platform/Identity src/Platform/Website src/Hosts -g '*.cs'
# The current expected result has no endpoint attachment and no Passed check in the filter.rg -n "AddEndpointFilter<RequireCaptchaEndpointFilter>|GetValueOrThrow\\(\\)\\.Passed" \ src/Hosts/BitzOrcas.Api -g '*.cs'11. Minimum GA evidence
- The general filter checks Passed and is attached to every intended endpoint.
- Login, Website, and filter validate tenant, session, and scenario binding.
- Every in-house provider, resolver rule, Redis failure, and vendor adapter has contract tests.
- Vendor secrets are not sent in URLs; provider, scene, Randstr, IP, and response protocol are validated.
- Engine fail-open has explicit risk acceptance, metrics, alerts, and high-value exceptions.
- Assessment query uses EffectiveTenantId and redacts sensitive evidence.
- Evidence persistence failure has durable alerting or compensation.
Next: Rule engine and risk factors · Back to platform modules