Summary
McpTokenController.CreateToken does not enforce a limit on the number of active MCP API tokens a single user can hold. Each token gets its own independent rate-limit bucket (mcp-user:{userId} keyed on the authenticated user's ID, not the token), but a malicious user could create many tokens and use them from different clients simultaneously to multiply their effective request rate.
Affected Code
EssentialCSharp.Web/Controllers/McpTokenController.cs — CreateToken:
// No check on how many tokens the user already has
var (rawToken, entity) = await tokenService.CreateTokenAsync(userId, name, expiresAt, ...);
EssentialCSharp.Web/Services/McpRateLimiterPolicy.cs — rate limiting is keyed by userId:
string userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? ...;
return RateLimitPartition.GetTokenBucketLimiter(partitionKey: $"mcp-user:{userId}", ...);
Since the rate limiter already uses userId as the partition key (not the individual token), this specific vector is lower severity than it initially appears — all tokens for a user share the same bucket. However, there remain two related concerns:
- Unlimited token accumulation: No cleanup policy means the database grows unboundedly.
- No token count visibility: There is no way to detect a user who has created an unusual number of tokens (a potential indicator of account compromise or abuse preparation).
Risk
OWASP AI Agent Security — §6 Monitoring & Observability / Denial of Wallet (DoW)
- Unbounded token creation is a housekeeping and abuse-signal gap.
- In a future scenario where rate limiting is refactored to be per-token rather than per-user, the lack of a count cap would immediately enable rate-limit bypass.
Recommended Mitigations
- Enforce a per-user active token cap (e.g., max 10 non-revoked, non-expired tokens):
int activeCount = await db.McpApiTokens
.CountAsync(t => t.UserId == userId && t.RevokedAt == null
&& (t.ExpiresAt == null || t.ExpiresAt > DateTime.UtcNow), cancellationToken);
if (activeCount >= MaxTokensPerUser)
return BadRequest(new { Error = $"Maximum of {MaxTokensPerUser} active tokens allowed." });
- Surface active token count to users in the MCP setup UI so they can manage their tokens.
- Add a background job to purge expired/revoked tokens older than a retention window.
References
Summary
McpTokenController.CreateTokendoes not enforce a limit on the number of active MCP API tokens a single user can hold. Each token gets its own independent rate-limit bucket (mcp-user:{userId}keyed on the authenticated user's ID, not the token), but a malicious user could create many tokens and use them from different clients simultaneously to multiply their effective request rate.Affected Code
EssentialCSharp.Web/Controllers/McpTokenController.cs—CreateToken:EssentialCSharp.Web/Services/McpRateLimiterPolicy.cs— rate limiting is keyed byuserId:Since the rate limiter already uses
userIdas the partition key (not the individual token), this specific vector is lower severity than it initially appears — all tokens for a user share the same bucket. However, there remain two related concerns:Risk
OWASP AI Agent Security — §6 Monitoring & Observability / Denial of Wallet (DoW)
Recommended Mitigations
References