CORS Mistakes and Request Validation: The Blind Spots in API Security

CORS misconfiguration and missing request validation are two of the most underestimated vulnerabilities in modern APIs. A practical guide to fixing them.

Jean-Pierre Broeders

Freelance DevOps Engineer

March 1, 20265 min. read

CORS Mistakes and Request Validation: The Blind Spots in API Security

Almost every API security article focuses on authentication and rate limiting. Fair enough, but there are two gaps in most applications that rarely get attention: CORS misconfiguration and poor request validation. Both are trivial to exploit and surprisingly easy to prevent.

Why CORS Misconfiguration Is Dangerous

CORS (Cross-Origin Resource Sharing) controls which domains can make requests to an API. During development, this is often wide open — Access-Control-Allow-Origin: * — and that's fine. The problem starts when that wildcard ends up in production. Or worse: when the origin header gets dynamically mirrored without validation.

A typical scenario. A .NET API that simply reflects the origin header back:

// WRONG - never do this
app.UseCors(policy => policy
    .SetIsOriginAllowed(_ => true)
    .AllowAnyHeader()
    .AllowAnyMethod()
    .AllowCredentials());

This allows literally any website to make authenticated requests on behalf of the user. An attacker just needs to create a page that fetches from the API, and the browser sends cookies along as if it were a legitimate request.

The fix is simple but requires discipline:

app.UseCors(policy => policy
    .WithOrigins(
        "https://freelyit.nl",
        "https://app.freelyit.nl"
    )
    .WithMethods("GET", "POST", "PUT", "DELETE")
    .WithHeaders("Authorization", "Content-Type")
    .AllowCredentials());

Explicit. No wildcards with credentials. Every origin that needs access gets added by hand.

The Subtler Variant: Regex Origins

Some teams use regex to match origins, for instance for staging environments:

// Dangerous if the regex isn't tight
.SetIsOriginAllowed(origin => 
    origin.EndsWith(".freelyit.nl"))

This also matches evil-freelyit.nl. A dot is missing:

.SetIsOriginAllowed(origin => 
{
    var uri = new Uri(origin);
    return uri.Host == "freelyit.nl" 
        || uri.Host.EndsWith(".freelyit.nl");
})

Better, but this kind of logic really doesn't belong in application code. Use an allowlist from configuration and keep it straightforward.

Request Validation: Trust Nothing

The second blind spot. Many APIs validate input only at the type level — is it a string, is it a number — but not the content itself. That opens the door to injection, oversized payloads, and data that makes no logical sense.

An example with FluentValidation in .NET:

public class CreateProjectValidator : AbstractValidator<CreateProjectRequest>
{
    public CreateProjectValidator()
    {
        RuleFor(x => x.Name)
            .NotEmpty()
            .MaximumLength(200)
            .Matches(@"^[\w\s\-\.]+$")
            .WithMessage("Project name contains invalid characters");

        RuleFor(x => x.Description)
            .MaximumLength(5000);

        RuleFor(x => x.Budget)
            .GreaterThan(0)
            .LessThanOrEqualTo(10_000_000);

        RuleFor(x => x.StartDate)
            .GreaterThanOrEqualTo(DateTime.UtcNow.Date)
            .When(x => x.StartDate.HasValue);
    }
}

This goes beyond "is it a string". The name can't contain weird characters. The budget can't be negative or absurdly high. Start date can't be in the past. Each of these checks prevents logical attacks.

Payload Size Limits

Request body limits are often forgotten until someone sends a 500MB JSON blob to the API and the server falls over. In .NET:

builder.WebHost.ConfigureKestrel(options =>
{
    options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10MB
});

// Or per endpoint with an attribute
[RequestSizeLimit(1_048_576)] // 1MB
[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file) { }

At the API gateway level — if there's an NGINX or Azure API Management in front — set the same limits. Defense in depth.

Content-Type Enforcement

A lesser-known attack vector: requests without or with an incorrect Content-Type header. Some frameworks parse the body anyway, leading to unexpected behavior.

app.Use(async (context, next) =>
{
    if (context.Request.Method is "POST" or "PUT" or "PATCH")
    {
        var contentType = context.Request.ContentType;
        if (string.IsNullOrEmpty(contentType) || 
            !contentType.StartsWith("application/json"))
        {
            context.Response.StatusCode = 415;
            await context.Response.WriteAsJsonAsync(new 
            { 
                error = "Unsupported Media Type" 
            });
            return;
        }
    }
    await next();
});

Strict, but it prevents an entire category of attacks where browsers send requests with text/plain to bypass CORS preflight checks.

Checklist

For anyone wanting to apply this right away:

  • CORS origins specified explicitly, no wildcards with credentials
  • Regex matching avoided for origins, or tested very carefully
  • Input validation at the content level, not just types
  • Payload limits at both application and gateway level
  • Content-Type enforced for mutating requests
  • Preflight caching configured to reduce unnecessary OPTIONS requests

None of these measures are complex. Maybe an hour to implement all of them. But without them, an API is more vulnerable than most teams realize.

Want to stay updated?

Subscribe to my newsletter or get in touch for freelance projects.

Get in Touch