Commit Graph

6 Commits

Author SHA1 Message Date
Oliver Kieselbach c18de5d76c fix(security): drop client-writable certificate header; bootstrap routes token-only, fail-closed
SecurityValidator accepted certificate bytes from X-Client-Certificate when X-ARR-ClientCert was
absent. That header is client-writable, and certificate content alone carries no proof of private-key
possession — the TLS handshake does. On platform-cert-enforced routes the fallback was unreachable,
but /api/bootstrap/* is excluded from the platform cert requirement and an EMPTY X-Bootstrap-Token
passed the function gate (Headers.Contains) while the validator treated it as no token and fell
through to the certificate stage, where the fallback header was parsed (live-verified 2026-08-29).

- Only X-ARR-ClientCert is read; the platform strips inbound copies even on excluded paths.
- IsBootstrapRoute (path prefix mirroring the exclusion entry): missing/empty token or unwired
  BootstrapSessionService => 401 before the certificate stage.
- GetBootstrapToken treats an empty header as absent; the three bootstrap functions use it.
- Tests: BootstrapRouteFailClosedTests. Docs: client-cert-tenant-binding.md transport section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 20:04:04 +02:00
Oliver Kieselbach a70c5de667 feat(plan): rename Enterprise plan to Pro; Pro includes Bootstrap by default and Unrestricted Mode on request
- FeatureEntitlementCatalog: TenantEdition.Pro, canonical tier "pro", legacy
  stored "enterprise" stays readable as Pro (no data migration); new
  entitlements BootstrapIncluded + UnrestrictedModeAvailable
- TenantEntitlementService: IsBootstrapEnabled (Pro-included OR additive GA
  flag) and IsUnrestrictedModeActive (Pro AND GA gate AND tenant toggle,
  read-time - trial expiry/downgrade re-arms guardrails fail-closed)
- All bootstrap endpoint gates + SecurityValidator use the effective check;
  /auth/me and feature-flags expose effective availability values
- PlanManagement accepts community|pro; AlreadyPro; Pro trial wording
- Web: edition.ts accepts legacy "enterprise" (deploy-order safety), Pro
  labels/cards + new Pro feature bullets, effective section gating,
  terms/privacy renamed with Last-updated bump
- Tests: catalog matrix, effective-gate payload tests, helper tests incl.
  trial-expiry re-gate (backend 3692 green, web 612 green)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 13:04:47 +02:00
Oliver Kieselbach 4a33b96db2 feat(ratelimit): unify device+user limits as global default + per-tenant override
Replace the denormalized per-tenant RateLimitRequestsPerMinute mirror (and its
SaveConfigurationAsync sync job) with compute-at-read resolution: effective limit =
per-tenant override ?? global default. Fixes the clobbering bug where a per-tenant
edit to the base field was overwritten on the next global config save.

- Model: drop TenantConfiguration.RateLimitRequestsPerMinute; add nullable
  CustomUserRateLimitRequestsPerMinute (device override CustomRateLimit... kept).
- RateLimitResolver centralizes precedence (device + user); both paths call it.
- SecurityValidator reads the global default (AdminConfigurationService threaded
  through ValidateSecurityAsync + all device-auth callers); 5-min cached.
- UserRateLimitMiddleware: per-tenant user override for tenant-scoped callers only
  (skipped for HasGlobalScope = GlobalAdmin|GlobalReader); GA -> GA budget, others ->
  user default. Uses GetConfigurationIfExistsAsync so an unregistered-tenant JWT can
  no longer auto-create a config row via read endpoints.
- Validation: reject rate limits < 1 at the write functions; RateLimitService clamps
  < 1 to 1 as a safety net (avoids Min()-on-empty / throttle-everything).
- Web: Global Settings exposes User + GlobalAdmin limits; tenant modal writes the
  override fields (blank = inherit); report + usage-plans updated.

Legacy RateLimitRequestsPerMinute Table column is ignored on read and dropped on the
next Replace save (self-cleaning; no migration).

Backend 2577 xUnit green, tsc clean, web 506 vitest green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 11:26:29 +02:00
Oliver Kieselbach 54c2c80904 fix: wire DeviceAssociationValidator through ValidateSecurityAsync
The DevPrep shadow validator was never executed because the
ValidateSecurityAsync extension constructed SecurityValidator without
passing DeviceAssociationValidator, so _deviceAssociationValidator was
always null and the gate at SecurityValidator.cs:370 silently skipped
the lookup — even with ValidateDeviceAssociation=true on the tenant.

Inject the validator via DI into all 9 agent-facing functions
(RegisterSession, IngestEvents, GetAgentConfig, ReportAgentError,
GetDiagnosticsUploadUrl + 4 Bootstrap variants) and thread it through
the extension to SecurityValidator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:40:45 +02:00
Oliver Kieselbach c91fd1a43e massive re-org and re-structuring 2026-03-06 03:02:19 +01:00
Oliver Kieselbach d532ffd7a4 massive re-org and re-structuring 2026-03-06 03:01:24 +01:00