Skip to content

Evaluation API returns 403 for cross-origin browser requests despite authentication.exclude.evaluation: true #6566

Closed

Description

@wgg-max

We are running Flipt v2 behind Traefik and want to evaluate feature flags directly from browser applications.

Our Flipt configuration has authentication enabled globally, but explicitly excludes the evaluation API from authentication:

authentication:
  required: true
  exclude:
    evaluation: true

CORS is handled by Traefik rather than by Flipt:

cors:
  enabled: false

Traefik allows our frontend origins using a regex-based CORS configuration, because we have many frontend URLs and cannot maintain a list of individual origins:

accessControlAllowOriginListRegex:
  - "^https://(.*\\.)?example\\.(com|de|es|at|nl)$"
  - "^https://(.*\\.)?anotherexample\\.(com|de)$"

We then call the evaluation endpoint directly from the browser on one of those allowed URLs (e.g. https://myfrontend.subdomain.example.com):

fetch('https://flipt.example.com/api/v1/evaluate/boolean', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    namespaceKey: 'default',
    flagKey: 'some-flag',
    entityId: 'test-user',
    context: {},
  }),
  credentials: 'omit'
})

The request is rejected with:

403 cross-origin request detected from Sec-Fetch-Site header

The browser sends:

Sec-Fetch-Site: same-site

which is expected because our frontend and Flipt are under the same registrable domain but have different origins.

Why this is a problem

We understand and support CSRF/cross-origin protection for authenticated and state-changing API endpoints.

However, the evaluation API is explicitly configured as unauthenticated via:

authentication.exclude.evaluation: true

For a browser-based feature-flag use case, cross-origin access to the evaluation endpoint is expected.

Our reverse proxy already performs origin validation and CORS handling, including regex/wildcard-style matching. Requiring Flipt's trusted_origins to contain every individual frontend origin is not practical for our setup.

In other words, we would like to be able to express a policy such as:

Allow evaluation requests from:
https://*.example.com
https://*.example.de
https://*.example.es
...

without having to enumerate thousands of exact origins.

Current workaround attempts

We tested modifying/removing Sec-Fetch-Site in Traefik only for /api/v1/evaluate/*.

For example:

customRequestHeaders:
  Sec-Fetch-Site: "same-origin"

This works with an explicit list of trusted_origins but rewriting the header does not seem like a good solution.

We do not consider rewriting browser security headers at the reverse proxy to be a desirable production solution.

Expected behavior

When:

authentication:
  required: true
  exclude:
    evaluation: true

is configured, we would expect the evaluation endpoint to be usable from browser clients when CORS/origin authorization is explicitly handled by the deployment.

Possible solutions could be:

  1. Respect authentication.exclude.evaluation for the cross-origin protection as well, or
  2. Provide a configurable origin pattern/regex mechanism for the evaluation endpoint, or
  3. Provide a dedicated configuration option to disable cross-origin protection specifically for evaluation requests.

The important part is that this should be possible without disabling cross-origin protection globally and without maintaining thousands of exact trusted origins.

Environment

  • Flipt: v2
  • Deployment: Helm (flipt-v2 chart from https://helm.flipt.io/)
  • Reverse proxy: Traefik
  • Browser: Standard modern browser
  • CORS: Handled by Traefik
  • Evaluation endpoint: POST /api/v1/evaluate

Question

Is this behavior intentional? If so, what is the recommended configuration for allowing browser-based cross-origin evaluation when authentication.exclude.evaluation: true is enabled and the allowed origins are managed externally by a reverse proxy?

Activity

  1. github-actions commented on Sep 22, 2026

    @github-actions
    Contributor

    Issue Health Check: Well scoped

    Type: Bug report

    This is a well-structured issue reporting that cross-origin browser requests to the evaluation endpoint return 403 even when authentication is explicitly excluded for evaluation. It includes complete reproduction steps, configuration details, expected behavior, and proposed solutions.

    Thanks — this issue looks well scoped and ready for the maintainers to evaluate.

    Need help with Flipt? Support the project through GitHub Sponsors or learn about Flipt Pro.

  2. erka commented on Sep 22, 2026

    @erka
    Contributor

    Hey @wgg-max. Thanks for your report.

    I think that CORS is not directly related to authentication and authentication.exclude.evaluation: true has no impact on CORS.

    In your example the url is https://flipt.example.com/api/v1/evaluate/boolean but it's usually https://flipt.example.com/evaluate/v1/boolean. Are you rewriting it in traefik? What happens if you set cors.enable: true in Flipt?

  3. wgg-max commented on Sep 23, 2026

    @wgg-max
    Author

    Hey @erka! Thanks for the suggestions.

    I tried both of your suggestions: Using https://flipt.example.com/evaluate/v1/boolean instead of /api/v1/evaluate/boolean, and enabling Flipt CORS with allowed_origins: "*". Both individually and in combination. Unfortunately, the behavior remains the same.

    I would like to clarify one point, though: the 403 cross-origin request detected from Sec-Fetch-Site header response does not appear to be related to the CORS configuration.

    This is not a typical browser-side CORS rejection. The request reaches Flipt and Flipt itself returns an HTTP 403 response based on the Sec-Fetch-Site header:

    403 cross-origin request detected from Sec-Fetch-Site header
    

    I traced the behavior further and found that it is controlled by authentication.session.csrf. When I disable the CSRF protection there, the evaluation request succeeds and I get the expected non-403 response.

    The problem is that we do not want to disable CSRF protection globally. We still need the admin UI and the other authenticated/state-changing endpoints to remain protected.

    So the question is whether there is a way to keep authentication.session.csrf enabled for the admin and other protected endpoints while exempting the unauthenticated evaluation endpoints (authentication.exclude.evaluation: true) from this particular cross-origin/CSRF check.

    That would allow us to keep the rest of Flipt secured while using the evaluation API from browser applications whose origins are already validated and handled by our reverse proxy.

  4. wgg-max commented on Sep 23, 2026

    @wgg-max
    Author

    As far as I can tell, this is the behavior of Go's standard-library net/http.CrossOriginProtection. It checks Sec-Fetch-Site for non-safe HTTP methods and rejects cross-origin requests unless the origin is explicitly trusted. In our case, the browser correctly sends:

    Sec-Fetch-Site: same-site
    

    because the frontend and Flipt are on different origins but the same registrable domain.

    This also explains why the behavior is specific to the evaluation endpoint: the evaluation API requires a POST request, so the request is subject to this cross-origin/CSRF protection. GET, HEAD, and OPTIONS are treated as safe methods and are not rejected by this particular check.

  5. erka commented on Sep 23, 2026

    @erka
    Contributor

    Yeah, you're right. We probably need to rework how the CSRF middleware is applied and take the auth excludes into account to make it work as you want.

  6. pradhankukiran commented on Sep 23, 2026

    @pradhankukiran

    The guard is global in internal/cmd/http.go:191. The open setting only skips login in internal/cmd/grpc.go:347. So public flag checks still hit the guard. CORS and guard are separate and only exact origins pass. Would you accept scoping the guard by route so private calls stay guarded and public evaluation passes when the open setting is true.

  7. added a commit that references this issue on Sep 27, 2026
    7925e5b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    • Status
      Done
    • Status
      Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions