This document describes the trust boundaries implemented in the current source. It is an engineering review aid, not a penetration test, certification, or claim that Merconiq is critical infrastructure.
The protected assets are tenant inventory, stock balances and transactions, purchase orders, user identities and roles, webhook secrets, JWT signing material, cookie/data protection keys, and operational logs. Actors are anonymous callers, authenticated browser users, bearer-token API clients, configured hosts/proxies, the database, the forecast worker, and the webhook worker.
The primary trust boundary is the configured host-to-tenant mapping. A request must first
resolve a tenant in TenantContext; cookie and bearer authentication then compare their
tenant_id claim with that context. The database applies tenant query filters and the
forecast cache keys include the tenant ID. Workers do not inherit an HTTP request: the
forecast runner selects a tenant before resolving tenant-scoped services. The webhook
delivery worker must first claim pending deliveries with query filters bypassed and
explicit tenant predicates, then set TenantContext before loading the subscription and
completing the delivery.
| Operation | Anonymous | Admin | Manager | Staff | Evidence and status |
|---|---|---|---|---|---|
| Item catalog reads and writes | Denied by Api policy |
Allowed | Allowed | Allowed | src/Merconiq.Web/Controllers/Api/V1/ItemsController.cs; tests/Merconiq.Tests/Integration/TenantAuthenticationTests.cs; implemented, API-tested |
| Stock/in-hand and transaction reads | Denied by Api policy |
Allowed | Allowed | Allowed | src/Merconiq.Web/Controllers/Api/V1/StockController.cs; tenant query filters; implemented, existing API/tenant tests |
| Receive, transfer, and sell | Denied by Api policy |
Allowed | Allowed | Allowed | src/Merconiq.Web/Controllers/Api/V1/StockController.cs role attributes; tests/Merconiq.Tests/Web/Controllers/TransferStockIdempotencyTests.cs and tests/Merconiq.Tests/Web/Controllers/SellStockIdempotencyTests.cs; implemented, unit-tested; PostgreSQL execution is separately recorded by CI |
| User creation and role changes | Denied by Api policy |
Allowed | Denied | Denied | src/Merconiq.Web/Components/Pages/Users/UserIndex.razor; Admin-only UI authorization and implementation; no dedicated component test located |
| Webhook list/create/update/delete | Denied by Api policy |
Allowed | Allowed | Denied | src/Merconiq.Web/Configuration/EndpointExtensions.cs role policies; tests/Merconiq.Tests/Infrastructure/WebhookDispatcherTests.cs; implemented, service-tested |
| Forecasts and anomaly detection | Denied by Api policy |
Allowed | Allowed | Allowed | src/Merconiq.Web/Configuration/EndpointExtensions.cs API group and tenant cache keys; tests/Merconiq.Tests/Integration/AiEndpointTests.cs; implemented, API-tested |
| Browser login/logout | Login is public; logout requires a valid cookie | Authenticated user | Authenticated user | Authenticated user | src/Merconiq.Web/Controllers/AccountController.cs; antiforgery on browser mutations; implemented |
| Health and culture selection | Health is public; invalid culture is rejected | Public | Public | Public | src/Merconiq.Web/Configuration/EndpointExtensions.cs; operational endpoint, no tenant data |
The API uses bearer authentication through the Api policy. Browser cookie authentication
is used by MVC/Razor UI routes. The two surfaces must not be treated as interchangeable:
the bearer policy does not authorize from an ambient cookie, while browser mutations keep
ASP.NET Core antiforgery validation.
src/Merconiq.Web/Configuration/IdentityExtensions.cs rejects a cookie or JWT whose tenant_id does not equal the resolved
host tenant, and rejects requests when no tenant is resolved. tests/Merconiq.Tests/Integration/TenantAuthenticationTests.cs
covers wrong-host and mismatched-claim rejection. tests/Merconiq.Tests/Infrastructure/TenantIsolationTests.cs and the filtered
src/Merconiq.Infrastructure/Data/InventoryDbContext.cs cover tenant-scoped reads; src/Merconiq.Core/Interfaces/TenantCacheKeys.cs is used by forecast
endpoints so equal item IDs in two tenants do not share a cache entry.
auth/token is intentionally anonymous only long enough to verify credentials. It still
requires a resolved host tenant and only issues a token for a user in that tenant. The
endpoint calls CheckPasswordSignInAsync with lockoutOnFailure: false; API rate limiting
is configured, but this repository has no direct token-login lockout or rate-limit test
evidence. SECURITY.md documents disclosure and deployment practices, not implemented
login-abuse controls. Token-login abuse protection therefore remains an unresolved risk.
src/Merconiq.Web/Controllers/AccountController retains [ValidateAntiForgeryToken] on login and logout. The stock
transfer and sell actions are bearer-only API actions; their explicit antiforgery marker
is accompanied by [IgnoreAntiforgeryToken] because no ambient cookie authenticates them.
This is a source-analysis annotation for the supported authentication boundary, not a
replacement for bearer validation. Any change to the authentication schemes must add an
integration test before changing this decision.
src/Merconiq.Web/BackgroundServices/ForecastBackgroundService.cs enumerates configured tenant IDs and invokes
src/Merconiq.Web/BackgroundServices/TenantForecastRunner.cs, which sets the tenant before creating the forecast service.
src/Merconiq.Web/BackgroundServices/WebhookDeliveryBackgroundService.cs loads the delivery and subscription using the delivery
tenant, sets the tenant context, then completes the record under that tenant. These paths
are isolated from HTTP middleware. The delivery worker intentionally claims the next
delivery with IgnoreQueryFilters() and explicit delivery/subscription tenant predicates
before setting the tenant context. tests/Merconiq.Tests/Integration/TenantBackgroundProcessingTests.cs
tests forecast tenant selection; tests/Merconiq.Tests/Infrastructure/WebhookDispatcherTests.cs
tests direct dispatch, but no test exercises WebhookDeliveryBackgroundService itself.
Production multi-tenant scheduling and failed-worker recovery remain configured-not-exercised here.
src/Merconiq.Web/Security/WebhookUrlValidator.cs rejects unsupported schemes, loopback/private/link-local addresses
and unsafe DNS results on the durable background-delivery path. The synchronous
src/Merconiq.Infrastructure/Services/WebhookDispatcher.cs path currently sends its stored
subscription URL without calling the validator, so this claim does not cover every send
path. The validator also does not reject every possible non-global IPv4 range (for example,
its default IPv4 branch permits multicast), so its protection is not a complete global
address classification. Redirect handling, DNS rebinding between validation and send,
and operator-approved public destination ownership are residual risks; they are not
claimed as fully verified by this document. Secrets are omitted from API responses, but
operators must still protect storage and logs.
| Claim | Classification | Evidence or limitation |
|---|---|---|
| Host, cookie, and JWT tenant agreement | Implemented-and-tested | IdentityExtensions, TenantAuthenticationTests |
| Tenant-filtered persistence and forecast cache separation | Implemented-and-tested | InventoryDbContext, TenantIsolationTests, cache-key tests |
| Role restrictions for webhook management and stock mutation | Implemented-and-tested | Endpoint/controller attributes and API tests |
| Background worker tenant selection | Forecast runner implemented-and-tested; webhook delivery worker configured-not-exercised | Forecast tests use synthetic tenants; no service-level webhook worker test or production scheduler observation |
| Browser CSRF protection | Implemented-and-tested for MVC mutations | API transfer/sell remain bearer-only by contract |
| Redirect/DNS-rebinding resistance for outbound webhooks | Configured-not-exercised | No private-infrastructure requests were made |
| Production proxy/header configuration and real tenant inventory | Unresolved | Requires deployment-owner validation |
When a new endpoint or worker is added, record its persona, tenant source, authentication
scheme, cache key, persistence filter, and test in this matrix. Do not infer security or
grant eligibility from the existence of this file. Real vulnerabilities belong in the
private disclosure channel described by SECURITY.md.