Skip to content

fix(oagw): handle CORS preflight without tenant context#1423

Open
asmith987 wants to merge 1 commit intocyberfabric:mainfrom
asmith987:fix/oagw-cors-preflight
Open

fix(oagw): handle CORS preflight without tenant context#1423
asmith987 wants to merge 1 commit intocyberfabric:mainfrom
asmith987:fix/oagw-cors-preflight

Conversation

@asmith987
Copy link
Copy Markdown
Contributor

@asmith987 asmith987 commented Apr 2, 2026

Browser preflight (OPTIONS) requests carry no credentials, so upstream resolution fails. Preflight now returns a permissive 204 at the handler level; origin and method enforcement moves to the actual request path after upstream resolution. Removes allowed_headers and max_age from CorsConfig (max_age hardcoded to 86400, header validation unnecessary with permissive preflight).

Summary by CodeRabbit

  • New Features

    • Authentication enabled by default for the API gateway.
    • Preflight (OPTIONS) handled at the handler, returning permissive 204 that echoes request CORS headers.
  • Bug Fixes

    • CORS origin and method validation now run on actual requests and reject disallowed origins/methods with 403 before proxying.
  • Refactor

    • Removed CORS config options allowed_headers and max_age; public models and API schemas updated.
  • Documentation

    • ADRs, design docs, examples and scenarios updated for the new CORS flow.
  • Tests / Chores

    • E2E fixtures always send Authorization (default test tokens); tests adjusted; tenant header removed.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Preflight handling moved to the proxy handler: OPTIONS preflight returns a permissive 204 without upstream/tenant resolution. Actual requests enforce origin and method after upstream resolution and may return 403 before forwarding. CORS fields allowed_headers and max_age were removed across models/DTOs/schemas. Auth middleware injects a SecurityContext::anonymous() for preflight.

Changes

Cohort / File(s) Summary
Config & Auth
config/e2e-local.yaml, modules/system/api-gateway/src/middleware/auth.rs
API-gateway auth enabled in config; auth middleware now inserts SecurityContext::anonymous() on OPTIONS preflight requests.
Handler-level Preflight
modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs
Handler short-circuits OPTIONS+Origin+Access-Control-Request-* and returns permissive 204 with echoed CORS headers (bypasses upstream/tenant resolution).
Proxy Enforcement
modules/system/oagw/oagw/src/infra/proxy/service.rs, modules/system/oagw/oagw/src/api/rest/error.rs
Removed proxy-level preflight short-circuit; proxy now uses actual HTTP method for resolution and validates origin & method on actual requests, returning 403 for disallowed values; removed CorsHeaderNotAllowed handling.
Domain & Models
modules/system/oagw/oagw/src/domain/cors.rs, modules/system/oagw/oagw/src/domain/model.rs, modules/system/oagw/oagw-sdk/src/models.rs
Deleted preflight helpers and removed allowed_headers/max_age from public models; added is_method_allowed() and made is_origin_allowed() public.
DTOs, Converters & Provisioning
modules/system/oagw/oagw/src/api/rest/dto.rs, modules/system/oagw/oagw/src/infra/type_provisioning.rs, modules/system/oagw/oagw/src/domain/services/*.rs
Removed allowed_headers/max_age from REST DTOs, conversions, provisioning and domain↔SDK mappings.
Schemas & API Docs
modules/system/oagw/docs/schemas/*, docs/api/api.json, modules/system/oagw/docs/ADR/0006-cors.md, modules/system/oagw/docs/adr-cors.md, modules/system/oagw/docs/DESIGN.md
Removed allowed_headers/max_age from schemas/docs; added constraint disallowing "*" with allow_credentials=true; documentation updated to describe handler-level preflight and actual-request origin/method enforcement.
Docs & Scenarios
modules/system/oagw/docs/features/*, modules/system/oagw/scenarios/*, modules/system/oagw/scenarios/plugins/*
Updated features, scenarios, and plugin-execution flows to reflect permissive handler preflight and enforcement on actual requests.
Tests & E2E
modules/system/oagw/oagw/tests/proxy_integration.rs, testing/e2e/**, testing/e2e/modules/oagw/*.py, testing/e2e/modules/file_parser/generate_file_parser_golden.py, testing/e2e/modules/oagw/test_cors.py
Rewrote tests: removed many preflight assertions, added tests expecting 403 on disallowed actual requests; E2E fixtures/functions now always send Authorization: Bearer <token> (default tokens); x-tenant-id usage removed.
Error model
modules/system/oagw/oagw/src/domain/error.rs
Removed DomainError::CorsHeaderNotAllowed variant and related mapping/documentation; adjusted method-related error comment.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Handler
  participant AuthMiddleware
  participant ProxyService
  participant Upstream

  Client->>Handler: OPTIONS + Origin + Access-Control-Request-*
  Handler-->>Client: 204 No Content
  Note right of Handler: Access-Control-Allow-Origin: <origin>\nAccess-Control-Allow-Methods: <requested>\nAccess-Control-Allow-Headers: <requested>\nVary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
Loading
sequenceDiagram
  participant Client
  participant AuthMiddleware
  participant ProxyService
  participant Upstream

  Client->>AuthMiddleware: Actual request (Origin + Authorization)
  AuthMiddleware->>ProxyService: forward (SecurityContext may be present)
  ProxyService->>ProxyService: resolve upstream (uses actual HTTP method)
  ProxyService->>ProxyService: validate origin & method vs upstream CORS
  alt origin/method not allowed
    ProxyService-->>Client: 403 Forbidden (no upstream call)
  else allowed
    ProxyService->>Upstream: forward request
    Upstream-->>ProxyService: response
    ProxyService-->>Client: response (CORS headers injected)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • mattgarmon
  • MikeFalcon77
  • Artifizer

Poem

🐇 I hopped through headers, tiny paws so ready,
OPTIONS echo kindly—short, polite, and steady.
Origins wait till the real call takes flight,
Tokens snug and tidy, the gateway holds tight.
A rabbit cheers aloud: CORS flows just right. 🎋

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: CORS preflight requests now return permissive responses without requiring tenant context.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Fix CORS preflight handling without tenant context

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Preflight requests now return permissive 204 at handler level without upstream resolution
• Removed allowed_headers and max_age fields from CORS configuration
• Origin and method validation moved to actual requests after upstream resolution
• Disallowed origins/methods now rejected with 403 before reaching upstream
Diagram
flowchart LR
  A["Browser Preflight<br/>OPTIONS + Origin + ACRM"] -->|Handler detects| B["Return 204<br/>Echo origin/method/headers"]
  C["Actual Request<br/>GET/POST + Origin"] -->|After upstream<br/>resolution| D["Validate origin<br/>& method"]
  D -->|Allowed| E["Forward to<br/>upstream"]
  D -->|Disallowed| F["403 Forbidden<br/>before upstream"]
  E -->|Success| G["Add CORS headers<br/>to response"]
Loading

Grey Divider

File Changes

1. modules/system/api-gateway/src/middleware/auth.rs 🐞 Bug fix +3/-1

Insert anonymous SecurityContext for preflight requests

modules/system/api-gateway/src/middleware/auth.rs


2. modules/system/oagw/oagw-sdk/src/models.rs ⚙️ Configuration changes +0/-2

Remove allowed_headers and max_age from CorsConfig

modules/system/oagw/oagw-sdk/src/models.rs


3. modules/system/oagw/oagw/src/api/rest/dto.rs ⚙️ Configuration changes +0/-12

Remove allowed_headers and max_age from DTO serialization

modules/system/oagw/oagw/src/api/rest/dto.rs


View more (28)
4. modules/system/oagw/oagw/src/api/rest/error.rs Error handling +3/-12

Remove CorsHeaderNotAllowed error variant and handling

modules/system/oagw/oagw/src/api/rest/error.rs


5. modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs ✨ Enhancement +40/-0

Add permissive preflight handler at request level

modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs


6. modules/system/oagw/oagw/src/domain/cors.rs ✨ Enhancement +5/-229

Remove preflight handler and header validation logic

modules/system/oagw/oagw/src/domain/cors.rs


7. modules/system/oagw/oagw/src/domain/error.rs Error handling +1/-5

Remove CorsHeaderNotAllowed error variant

modules/system/oagw/oagw/src/domain/error.rs


8. modules/system/oagw/oagw/src/domain/model.rs ⚙️ Configuration changes +0/-2

Remove allowed_headers and max_age from domain model

modules/system/oagw/oagw/src/domain/model.rs


9. modules/system/oagw/oagw/src/domain/services/client.rs Error handling +0/-9

Remove CorsHeaderNotAllowed error mapping

modules/system/oagw/oagw/src/domain/services/client.rs


10. modules/system/oagw/oagw/src/infra/proxy/service.rs ✨ Enhancement +19/-58

Simplify CORS validation to origin and method checks

modules/system/oagw/oagw/src/infra/proxy/service.rs


11. modules/system/oagw/oagw/src/infra/type_provisioning.rs ⚙️ Configuration changes +0/-10

Remove allowed_headers and max_age from provisioning

modules/system/oagw/oagw/src/infra/type_provisioning.rs


12. modules/system/oagw/oagw/tests/proxy_integration.rs 🧪 Tests +80/-148

Update CORS tests to reflect new preflight behavior

modules/system/oagw/oagw/tests/proxy_integration.rs


13. testing/e2e/conftest.py ⚙️ Configuration changes +7/-6

Add default auth token for accept_all mode

testing/e2e/conftest.py


14. testing/e2e/modules/file_parser/generate_file_parser_golden.py ⚙️ Configuration changes +3/-5

Add default auth token for accept_all mode

testing/e2e/modules/file_parser/generate_file_parser_golden.py


15. testing/e2e/modules/oagw/conftest.py ⚙️ Configuration changes +7/-15

Remove tenant_id fixture and use token-based tenant extraction

testing/e2e/modules/oagw/conftest.py


16. testing/e2e/modules/oagw/test_authz.py 🧪 Tests +17/-9

Update authz tests to use token-based tenant extraction

testing/e2e/modules/oagw/test_authz.py


17. testing/e2e/modules/oagw/test_body_validation.py 🧪 Tests +2/-2

Remove x-tenant-id header from raw socket tests

testing/e2e/modules/oagw/test_body_validation.py


18. testing/e2e/modules/oagw/test_cors.py 🧪 Tests +14/-124

Rewrite CORS tests for permissive preflight and actual request validation

testing/e2e/modules/oagw/test_cors.py


19. config/e2e-local.yaml ⚙️ Configuration changes +21/-2

Enable auth and configure static tokens for E2E testing

config/e2e-local.yaml


20. modules/system/oagw/docs/ADR/0006-cors.md 📝 Documentation +21/-37

Update ADR to reflect permissive preflight and actual request validation

modules/system/oagw/docs/ADR/0006-cors.md


21. modules/system/oagw/docs/DESIGN.md 📝 Documentation +4/-3

Update design docs for CORS handler behavior

modules/system/oagw/docs/DESIGN.md


22. modules/system/oagw/docs/adr-cors.md 📝 Documentation +22/-40

Update ADR copy to reflect permissive preflight strategy

modules/system/oagw/docs/adr-cors.md


23. modules/system/oagw/docs/features/0004-cpt-cf-oagw-feature-proxy-engine.md 📝 Documentation +1/-1

Update feature docs for CORS preflight at handler level

modules/system/oagw/docs/features/0004-cpt-cf-oagw-feature-proxy-engine.md


24. modules/system/oagw/docs/features/0008-cpt-cf-oagw-feature-observability.md 📝 Documentation +31/-29

Update observability docs for new CORS handler flow

modules/system/oagw/docs/features/0008-cpt-cf-oagw-feature-observability.md


25. modules/system/oagw/docs/schemas/route.v1.schema.json 📝 Documentation +21/-15

Remove allowed_headers and max_age from route schema

modules/system/oagw/docs/schemas/route.v1.schema.json


26. modules/system/oagw/docs/schemas/upstream.v1.schema.json 📝 Documentation +0/-13

Remove allowed_headers and max_age from upstream schema

modules/system/oagw/docs/schemas/upstream.v1.schema.json


27. modules/system/oagw/scenarios/INDEX.md 📝 Documentation +4/-8

Update scenario index for CORS handling changes

modules/system/oagw/scenarios/INDEX.md


28. modules/system/oagw/scenarios/flows/plugin-execution.md 📝 Documentation +1/-2

Update plugin execution flow for CORS at handler level

modules/system/oagw/scenarios/flows/plugin-execution.md


29. modules/system/oagw/scenarios/plugins/guards/negative-10.3-cors-credentials-wildcard-rejected-config-validation.md 📝 Documentation +1/-1

Update scenario description for actual request validation

modules/system/oagw/scenarios/plugins/guards/negative-10.3-cors-credentials-wildcard-rejected-config-validation.md


30. modules/system/oagw/scenarios/plugins/guards/positive-10.2-built-cors-handling.md 📝 Documentation +22/-10

Update scenario to demonstrate permissive preflight and actual request validation

modules/system/oagw/scenarios/plugins/guards/positive-10.2-built-cors-handling.md


31. modules/system/oagw/oagw/src/domain/services/management.rs Additional files +0/-12

...

modules/system/oagw/oagw/src/domain/services/management.rs


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 2, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Preflight bypasses disabled CORS 🐞 Bug ⛨ Security
Description
proxy_handler returns a permissive 204 for any CORS preflight (OPTIONS+Origin+ACRM) before
validating alias/path or checking whether the target has CORS enabled, so browser preflight will
succeed universally. Because the data-plane only enforces origin/method when upstream.cors is
present and enabled, requests to CORS-disabled/unconfigured upstreams will still be proxied even
for non-simple cross-origin requests that previously would have been blocked by preflight rejection.
Code

modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs[R30-68]

+    // Short-circuit CORS preflight — return permissive 204 without upstream resolution.
+    // The actual request validates the origin against the upstream's CORS config.
+    if req.method() == http::Method::OPTIONS
+        && req.headers().contains_key(http::header::ORIGIN)
+        && req
+            .headers()
+            .contains_key(http::header::ACCESS_CONTROL_REQUEST_METHOD)
+    {
+        let origin = req
+            .headers()
+            .get(http::header::ORIGIN)
+            .and_then(|v| v.to_str().ok())
+            .unwrap_or("*");
+
+        let requested_method = req
+            .headers()
+            .get(http::header::ACCESS_CONTROL_REQUEST_METHOD)
+            .and_then(|v| v.to_str().ok())
+            .unwrap_or("");
+
+        let requested_headers = req
+            .headers()
+            .get(http::header::ACCESS_CONTROL_REQUEST_HEADERS)
+            .and_then(|v| v.to_str().ok())
+            .unwrap_or("");
+
+        return Ok(Response::builder()
+            .status(StatusCode::NO_CONTENT)
+            .header("access-control-allow-origin", origin)
+            .header("access-control-allow-methods", requested_method)
+            .header("access-control-allow-headers", requested_headers)
+            .header("access-control-max-age", "86400")
+            .header(
+                "vary",
+                "Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
+            )
+            .body(Body::empty())
+            .unwrap());
+    }
Evidence
The handler-level preflight short-circuit unconditionally echoes Origin / requested method /
requested headers and returns 204 without attempting any upstream/route lookup or alias validation.
In the data plane, CORS rejection is conditional on effective_cors being Some and enabled, so
when CORS is absent/disabled there is no origin/method rejection path and the request continues
through normal proxying.

modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs[25-68]
modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs[103-120]
modules/system/oagw/oagw/src/infra/proxy/service.rs[404-437]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The proxy handler currently returns a permissive CORS preflight response for any OPTIONS+Origin+ACRM request, regardless of whether the target upstream/route has CORS enabled. This makes browser preflight succeed even when CORS is disabled/unconfigured, enabling non-simple cross-origin requests to be sent and proxied.

### Issue Context
Because upstream resolution requires tenant context, handler-level preflight cannot trivially look up the effective CORS policy. However, returning `Access-Control-Allow-*` for all preflights removes the only browser-enforced block for non-simple cross-origin requests when CORS is not configured.

### Fix Focus Areas
- modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs[30-68]
- modules/system/oagw/oagw/src/infra/proxy/service.rs[404-437]

### What to change
- Introduce a way to decide whether to emit permissive `Access-Control-Allow-*` at preflight time (e.g., a safe global config flag, an allowlisted set of proxy aliases, or a cache keyed by alias that indicates CORS is enabled for that alias).
- If the decision cannot be made, prefer failing closed for preflight (e.g., return 204 without `Access-Control-Allow-*` headers, or return 403) to preserve the "CORS disabled by default" security posture for non-simple browser requests.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. OpenAPI spec still advertises fields 🐞 Bug ⚙ Maintainability
Description
The REST DTO CorsConfig no longer includes allowed_headers or max_age, but the committed
OpenAPI JSON still documents those properties under components.schemas.CorsConfig. This will
mislead users and can break client generation against the published spec.
Code

modules/system/oagw/oagw/src/api/rest/dto.rs[R210-212]

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, utoipa::ToSchema)]
pub struct CorsConfig {
    #[serde(default)]
Evidence
The DTO used for OpenAPI (utoipa::ToSchema) has removed allowed_headers/max_age, but the
checked-in OpenAPI artifact still includes them, indicating the artifact was not regenerated/updated
alongside the model change.

modules/system/oagw/oagw/src/api/rest/dto.rs[210-224]
docs/api/api.json[6588-6629]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The repository’s committed OpenAPI JSON (`docs/api/api.json`) still lists `allowed_headers` and `max_age` in `CorsConfig`, but the implemented DTO/SDK model removed those fields. This desynchronizes the published spec from the actual API.

### Issue Context
`CorsConfig` is annotated with `utoipa::ToSchema`, so OpenAPI output should reflect the Rust DTO. If `docs/api/api.json` is treated as a source-of-truth artifact, it needs regeneration in this PR.

### Fix Focus Areas
- modules/system/oagw/oagw/src/api/rest/dto.rs[210-224]
- docs/api/api.json[6588-6629]

### What to change
- Regenerate/update `docs/api/api.json` so `CorsConfig` matches the new DTO (remove `allowed_headers` and `max_age`).
- If `docs/api/api.json` is intentionally not maintained manually, remove it from source control or add CI checks to prevent spec drift.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@asmith987 asmith987 force-pushed the fix/oagw-cors-preflight branch from 5cf93f0 to bcb5195 Compare April 2, 2026 17:54
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
testing/e2e/modules/file_parser/generate_file_parser_golden.py (1)

14-14: ⚠️ Potential issue | 🟡 Minor

Docstring is now outdated.

Line 14 states "Optional bearer token for authentication" but the token is no longer optional — a default is always used. Consider updating to: E2E_AUTH_TOKEN: Bearer token for authentication (defaults to e2e-token-tenant-a).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testing/e2e/modules/file_parser/generate_file_parser_golden.py` at line 14,
Update the outdated docstring for E2E_AUTH_TOKEN to reflect that it is no longer
optional and that a default is used; change the description for the
E2E_AUTH_TOKEN constant/variable in generate_file_parser_golden.py to something
like "Bearer token for authentication (defaults to e2e-token-tenant-a)" so the
docstring accurately describes its behavior and default value.
testing/e2e/modules/oagw/test_cors.py (1)

8-14: ⚠️ Potential issue | 🟠 Major

Add credentialed-preflight test coverage before supporting allow_credentials=True flows.

Per CORS specifications, a preflight response must include Access-Control-Allow-Credentials: true when the actual request will use credentials; browsers will reject the subsequent request if this header is absent. The current test suite only covers allow_credentials=False cases, and the handler at modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs:30-68 does not emit this header. If credentialed flows are later needed, this gap will cause failures and won't be caught by existing tests. Add a credentialed-preflight test case or document that credentialed requests are not supported.

🧹 Nitpick comments (4)
modules/system/oagw/oagw/src/domain/cors.rs (1)

131-134: Add direct unit tests for the newly public CORS helper APIs.

These helpers are now externally consumable; adding dedicated tests for case-insensitive methods, unknown methods, exact/wildcard origins will prevent regressions.

Also applies to: 191-196

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/system/oagw/oagw/src/domain/cors.rs` around lines 131 - 134, Add
focused unit tests for the newly public CORS helpers (e.g., is_method_allowed
and the origin helper referenced around the later block) using CorsConfig to
prevent regressions: create tests that assert case-insensitive method matching
(e.g., "GET" vs "get"), that unknown/invalid methods (where
CorsHttpMethod::from_str_loose returns None) yield false, and that origin checks
verify exact matches and wildcard patterns; place these in a tests module near
cors.rs (or an integration test) and exercise CorsConfig.allowed_methods and
allowed_origins to cover positive and negative cases.
testing/e2e/conftest.py (1)

18-29: Reintroduce an explicit no-auth fixture path to keep unauth test scenarios possible.

Always returning a Bearer header makes not auth_headers skip patterns unreachable and removes a clean way to exercise unauthenticated flows via shared fixtures.

♻️ Suggested tweak
 `@pytest.fixture`
 def auth_headers():
     """
     Build Authorization headers using E2E_AUTH_TOKEN env var.
-
-    Falls back to a dummy token suitable for the static-authn-plugin
-    running in ``accept_all`` mode (any non-empty Bearer token is accepted).
+    Set E2E_DISABLE_AUTH_HEADERS=1 to run unauthenticated scenarios.
+    Otherwise falls back to local static token if E2E_AUTH_TOKEN is unset.
 
     Returns:
-        dict: Headers dict with Authorization header.
+        dict: Headers dict (possibly empty for unauthenticated scenarios).
     """
+    if os.getenv("E2E_DISABLE_AUTH_HEADERS") == "1":
+        return {}
     token = os.getenv("E2E_AUTH_TOKEN", "e2e-token-tenant-a")
     return {"Authorization": f"Bearer {token}"}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testing/e2e/conftest.py` around lines 18 - 29, The test suite needs an
explicit unauthenticated fixture so tests can exercise no-auth flows; add a new
fixture/function named no_auth_headers (in testing/e2e/conftest.py alongside
auth_headers) that returns an empty headers mapping (e.g. {} or None) to
represent absence of Authorization rather than relying on env tricks, and keep
auth_headers unchanged so existing authenticated flows keep working; update any
tests that expect a no-auth path to use no_auth_headers.
modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs (1)

38-42: Consider the "*" fallback when Origin header is non-UTF8.

If Origin exists but contains invalid UTF-8 bytes, the code falls back to "*". While malformed headers are rare, this could inadvertently echo a wildcard origin to the client.

A stricter approach would return 400 Bad Request for unparseable Origin headers:

🛡️ Alternative: reject malformed Origin
-        let origin = req
-            .headers()
-            .get(http::header::ORIGIN)
-            .and_then(|v| v.to_str().ok())
-            .unwrap_or("*");
+        let origin = match req.headers().get(http::header::ORIGIN) {
+            Some(v) => v.to_str().map_err(|_| {
+                error_response(DomainError::Validation {
+                    detail: "invalid Origin header encoding".into(),
+                    instance: req.uri().path().to_string(),
+                })
+            })?,
+            None => "*",
+        };

That said, since preflight is explicitly permissive and actual requests still validate origin against the upstream's allowed_origins, this is defense-in-depth rather than a correctness issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs` around lines 38 -
42, The current Origin header handling falls back to "*" when to_str() fails;
change it to treat a present-but-unparseable Origin as a client error by
returning a 400 Bad Request instead of using the wildcard. In the proxy request
handler (where origin is computed from req.headers().get(http::header::ORIGIN)),
detect if the header exists but to_str().ok() is None and return an immediate
400 response (with a short error message) rather than setting origin = "*"; keep
the existing behavior of using "*" only when the header is completely absent
(get(...) returns None).
modules/system/oagw/oagw/tests/proxy_integration.rs (1)

3655-3679: Match the specific CORS rejection instead of any error.

response.is_err() will also pass on unrelated pre-upstream failures, so these assertions don’t really prove the new “403 due to CORS” contract. Matching the concrete CORS error/status here would make the regression coverage much tighter.

Also applies to: 3681-3748, 3796-3811

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/system/oagw/oagw/tests/proxy_integration.rs` around lines 3655 -
3679, The test currently asserts response.is_err() which is too broad; instead
inspect the Result from AppHarness::facade().proxy_request(...) and assert the
concrete CORS rejection (HTTP 403 due to CORS) by either pattern-matching the
error variant returned (e.g.,
ProxyError::RequestRejected/ProxyError::CorsRejected) and asserting the status
== 403, or if proxy_request returns an Ok<Response>, assert response.status() ==
StatusCode::FORBIDDEN and body/reason indicates CORS; update
cors_actual_request_disallowed_origin_rejected_before_upstream to check that
exact condition (and make the analogous changes in the other tests mentioned:
the blocks covering lines 3681-3748 and 3796-3811).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@testing/e2e/modules/file_parser/generate_file_parser_golden.py`:
- Line 14: Update the outdated docstring for E2E_AUTH_TOKEN to reflect that it
is no longer optional and that a default is used; change the description for the
E2E_AUTH_TOKEN constant/variable in generate_file_parser_golden.py to something
like "Bearer token for authentication (defaults to e2e-token-tenant-a)" so the
docstring accurately describes its behavior and default value.

---

Nitpick comments:
In `@modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs`:
- Around line 38-42: The current Origin header handling falls back to "*" when
to_str() fails; change it to treat a present-but-unparseable Origin as a client
error by returning a 400 Bad Request instead of using the wildcard. In the proxy
request handler (where origin is computed from
req.headers().get(http::header::ORIGIN)), detect if the header exists but
to_str().ok() is None and return an immediate 400 response (with a short error
message) rather than setting origin = "*"; keep the existing behavior of using
"*" only when the header is completely absent (get(...) returns None).

In `@modules/system/oagw/oagw/src/domain/cors.rs`:
- Around line 131-134: Add focused unit tests for the newly public CORS helpers
(e.g., is_method_allowed and the origin helper referenced around the later
block) using CorsConfig to prevent regressions: create tests that assert
case-insensitive method matching (e.g., "GET" vs "get"), that unknown/invalid
methods (where CorsHttpMethod::from_str_loose returns None) yield false, and
that origin checks verify exact matches and wildcard patterns; place these in a
tests module near cors.rs (or an integration test) and exercise
CorsConfig.allowed_methods and allowed_origins to cover positive and negative
cases.

In `@modules/system/oagw/oagw/tests/proxy_integration.rs`:
- Around line 3655-3679: The test currently asserts response.is_err() which is
too broad; instead inspect the Result from
AppHarness::facade().proxy_request(...) and assert the concrete CORS rejection
(HTTP 403 due to CORS) by either pattern-matching the error variant returned
(e.g., ProxyError::RequestRejected/ProxyError::CorsRejected) and asserting the
status == 403, or if proxy_request returns an Ok<Response>, assert
response.status() == StatusCode::FORBIDDEN and body/reason indicates CORS;
update cors_actual_request_disallowed_origin_rejected_before_upstream to check
that exact condition (and make the analogous changes in the other tests
mentioned: the blocks covering lines 3681-3748 and 3796-3811).

In `@testing/e2e/conftest.py`:
- Around line 18-29: The test suite needs an explicit unauthenticated fixture so
tests can exercise no-auth flows; add a new fixture/function named
no_auth_headers (in testing/e2e/conftest.py alongside auth_headers) that returns
an empty headers mapping (e.g. {} or None) to represent absence of Authorization
rather than relying on env tricks, and keep auth_headers unchanged so existing
authenticated flows keep working; update any tests that expect a no-auth path to
use no_auth_headers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d81768b9-e1fc-46d9-8d89-6c7bdcf70133

📥 Commits

Reviewing files that changed from the base of the PR and between cbbbd6c and bcb5195.

📒 Files selected for processing (31)
  • config/e2e-local.yaml
  • modules/system/api-gateway/src/middleware/auth.rs
  • modules/system/oagw/docs/ADR/0006-cors.md
  • modules/system/oagw/docs/DESIGN.md
  • modules/system/oagw/docs/adr-cors.md
  • modules/system/oagw/docs/features/0004-cpt-cf-oagw-feature-proxy-engine.md
  • modules/system/oagw/docs/features/0008-cpt-cf-oagw-feature-observability.md
  • modules/system/oagw/docs/schemas/route.v1.schema.json
  • modules/system/oagw/docs/schemas/upstream.v1.schema.json
  • modules/system/oagw/oagw-sdk/src/models.rs
  • modules/system/oagw/oagw/src/api/rest/dto.rs
  • modules/system/oagw/oagw/src/api/rest/error.rs
  • modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs
  • modules/system/oagw/oagw/src/domain/cors.rs
  • modules/system/oagw/oagw/src/domain/error.rs
  • modules/system/oagw/oagw/src/domain/model.rs
  • modules/system/oagw/oagw/src/domain/services/client.rs
  • modules/system/oagw/oagw/src/domain/services/management.rs
  • modules/system/oagw/oagw/src/infra/proxy/service.rs
  • modules/system/oagw/oagw/src/infra/type_provisioning.rs
  • modules/system/oagw/oagw/tests/proxy_integration.rs
  • modules/system/oagw/scenarios/INDEX.md
  • modules/system/oagw/scenarios/flows/plugin-execution.md
  • modules/system/oagw/scenarios/plugins/guards/negative-10.3-cors-credentials-wildcard-rejected-config-validation.md
  • modules/system/oagw/scenarios/plugins/guards/positive-10.2-built-cors-handling.md
  • testing/e2e/conftest.py
  • testing/e2e/modules/file_parser/generate_file_parser_golden.py
  • testing/e2e/modules/oagw/conftest.py
  • testing/e2e/modules/oagw/test_authz.py
  • testing/e2e/modules/oagw/test_body_validation.py
  • testing/e2e/modules/oagw/test_cors.py
💤 Files with no reviewable changes (7)
  • modules/system/oagw/oagw/src/infra/type_provisioning.rs
  • modules/system/oagw/oagw/src/domain/model.rs
  • modules/system/oagw/oagw/src/domain/services/client.rs
  • modules/system/oagw/oagw/src/api/rest/dto.rs
  • modules/system/oagw/docs/schemas/upstream.v1.schema.json
  • modules/system/oagw/oagw-sdk/src/models.rs
  • modules/system/oagw/oagw/src/domain/services/management.rs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/api/api.json (1)

6588-6618: ⚠️ Potential issue | 🟠 Major

CORS schema and runtime config are out of sync.

components.schemas.CorsConfig no longer exposes header/max-age fields, but runtime still defines and uses allowed_headers and max_age_seconds (modules/system/api-gateway/src/config.rs Line 88-124 and modules/system/api-gateway/src/cors.rs Line 37-56). This creates API contract drift for generated clients and docs.

Please either (a) restore these fields in the OpenAPI schema (optionally deprecated), or (b) remove them from runtime in the same change set.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/api/api.json` around lines 6588 - 6618, The OpenAPI CorsConfig schema is
missing allowed_headers and max_age_seconds while runtime
(modules/system/api-gateway/src/config.rs and
modules/system/api-gateway/src/cors.rs) still defines and uses allowed_headers
and max_age_seconds, causing a contract drift; fix by either (A) restoring
allowed_headers (array[string]) and max_age_seconds (integer) into
components.schemas.CorsConfig in docs/api/api.json (mark them deprecated if
desired) so the schema matches runtime symbols, or (B) remove the runtime
fields/usages named allowed_headers and max_age_seconds from config.rs and
cors.rs (and adjust any callers) so the runtime matches the current schema—pick
one approach and update both schema and runtime consistently.
🧹 Nitpick comments (3)
docs/api/api.json (1)

8283-8285: Define a concrete schema for Problem.context to stabilize codegen.

context currently has only a description, so schema generators typically emit an untyped any. If intent is structured context, declare it explicitly.

Proposed schema tightening
           "context": {
+            "type": "object",
+            "additionalProperties": true,
             "description": "Optional structured context (e.g. `resource_type`, `resource_name`)."
           },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/api/api.json` around lines 8283 - 8285, The Problem.context entry is
only a description and causes codegen to emit untyped any; replace the freeform
description with an explicit JSON Schema for Problem.context (an object schema
named "Problem.context") that lists expected keys (e.g., "resource_type": {
"type":"string" }, "resource_name": { "type":"string" }, any other known
fields), sets appropriate "required" flags if needed, and sets
"additionalProperties": false (or true if truly extensible) to stabilize code
generation and typing for the Problem.context field.
modules/system/oagw/oagw/src/domain/cors.rs (1)

131-134: Add unit coverage for the new method gate.

is_method_allowed() is now part of the moved actual-request CORS check, but this module never exercises it directly. A tiny table test for allowed, lowercase, disallowed, and unknown methods would keep this behavior pinned down.

🧪 Example coverage
+    #[test]
+    fn test_is_method_allowed() {
+        let config = make_config();
+        assert!(is_method_allowed(&config, "GET"));
+        assert!(is_method_allowed(&config, "post"));
+        assert!(!is_method_allowed(&config, "DELETE"));
+        assert!(!is_method_allowed(&config, "PROPFIND"));
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/system/oagw/oagw/src/domain/cors.rs` around lines 131 - 134, Add unit
tests that exercise the new is_method_allowed() gate to pin its behavior: create
a small table-driven test in the cors module's test suite that constructs a
CorsConfig with a known allowed_methods set and then asserts
is_method_allowed(&config, method) for cases including an allowed method (e.g.,
exactly matched), the same method in lowercase, a disallowed method, and an
unknown/invalid method string; use CorsHttpMethod::... variants or strings to
drive inputs and assert true/false as appropriate so the behavior for allowed,
lowercase, disallowed, and unknown methods is covered.
testing/e2e/modules/oagw/test_cors.py (1)

62-90: Please add an actual-request method rejection test.

This proves preflight is intentionally permissive, but it never follows up with an actual DELETE and asserts the 403 moved from preflight into the real request path. Without that, a broken method-enforcement path can still slip through this suite.

🧪 Minimal follow-up test shape
+@pytest.mark.asyncio
+async def test_cors_actual_request_disallowed_method_rejected(
+    oagw_base_url, oagw_headers, mock_upstream_url, mock_upstream,
+):
+    _ = mock_upstream
+    alias = unique_alias("cors-actual-method-bad")
+    async with httpx.AsyncClient(timeout=10.0) as client:
+        upstream = await create_upstream(
+            client, oagw_base_url, oagw_headers, mock_upstream_url,
+            alias=alias, cors=CORS_CONFIG,
+        )
+        uid = upstream["id"]
+        await create_route(
+            client, oagw_base_url, oagw_headers, uid, ["DELETE"], "/echo",
+        )
+
+        resp = await client.delete(
+            f"{oagw_base_url}/oagw/v1/proxy/{alias}/echo",
+            headers={**oagw_headers, "origin": "https://app.example.com"},
+        )
+        assert resp.status_code == 403
+
+        await delete_upstream(client, oagw_base_url, oagw_headers, uid)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testing/e2e/modules/oagw/test_cors.py` around lines 62 - 90, Add a follow-up
actual-request assertion to test_cors_preflight_permissive_echoes_any_method:
after the existing OPTIONS preflight, send a real DELETE to
f"{oagw_base_url}/oagw/v1/proxy/{alias}/echo" using the same oagw_headers plus
"origin": "https://app.example.com" and assert the response.status_code == 403
(and optionally that the body or headers indicate method not allowed); this
validates that method enforcement occurs on the real request path even when
preflight is permissive. Use the same alias created via create_upstream and the
route created with create_route so the DELETE targets the configured proxy
route.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config/e2e-local.yaml`:
- Around line 231-235: The token entry named "e2e-token-tenant-b" authenticates
as tenant ID "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" which is not present in the
static tenant registry; either add a corresponding tenant object with that
tenant_id under modules.static-tr-plugin.config.tenants in this YAML, or change
the token's subject_tenant_id to one of the tenant IDs already defined in
modules.static-tr-plugin.config.tenants so the token references an existing
configured tenant.

---

Outside diff comments:
In `@docs/api/api.json`:
- Around line 6588-6618: The OpenAPI CorsConfig schema is missing
allowed_headers and max_age_seconds while runtime
(modules/system/api-gateway/src/config.rs and
modules/system/api-gateway/src/cors.rs) still defines and uses allowed_headers
and max_age_seconds, causing a contract drift; fix by either (A) restoring
allowed_headers (array[string]) and max_age_seconds (integer) into
components.schemas.CorsConfig in docs/api/api.json (mark them deprecated if
desired) so the schema matches runtime symbols, or (B) remove the runtime
fields/usages named allowed_headers and max_age_seconds from config.rs and
cors.rs (and adjust any callers) so the runtime matches the current schema—pick
one approach and update both schema and runtime consistently.

---

Nitpick comments:
In `@docs/api/api.json`:
- Around line 8283-8285: The Problem.context entry is only a description and
causes codegen to emit untyped any; replace the freeform description with an
explicit JSON Schema for Problem.context (an object schema named
"Problem.context") that lists expected keys (e.g., "resource_type": {
"type":"string" }, "resource_name": { "type":"string" }, any other known
fields), sets appropriate "required" flags if needed, and sets
"additionalProperties": false (or true if truly extensible) to stabilize code
generation and typing for the Problem.context field.

In `@modules/system/oagw/oagw/src/domain/cors.rs`:
- Around line 131-134: Add unit tests that exercise the new is_method_allowed()
gate to pin its behavior: create a small table-driven test in the cors module's
test suite that constructs a CorsConfig with a known allowed_methods set and
then asserts is_method_allowed(&config, method) for cases including an allowed
method (e.g., exactly matched), the same method in lowercase, a disallowed
method, and an unknown/invalid method string; use CorsHttpMethod::... variants
or strings to drive inputs and assert true/false as appropriate so the behavior
for allowed, lowercase, disallowed, and unknown methods is covered.

In `@testing/e2e/modules/oagw/test_cors.py`:
- Around line 62-90: Add a follow-up actual-request assertion to
test_cors_preflight_permissive_echoes_any_method: after the existing OPTIONS
preflight, send a real DELETE to f"{oagw_base_url}/oagw/v1/proxy/{alias}/echo"
using the same oagw_headers plus "origin": "https://app.example.com" and assert
the response.status_code == 403 (and optionally that the body or headers
indicate method not allowed); this validates that method enforcement occurs on
the real request path even when preflight is permissive. Use the same alias
created via create_upstream and the route created with create_route so the
DELETE targets the configured proxy route.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a1aa96e-c93c-4b95-8165-efde624c1dda

📥 Commits

Reviewing files that changed from the base of the PR and between bcb5195 and 1b3aeb0.

📒 Files selected for processing (32)
  • config/e2e-local.yaml
  • docs/api/api.json
  • modules/system/api-gateway/src/middleware/auth.rs
  • modules/system/oagw/docs/ADR/0006-cors.md
  • modules/system/oagw/docs/DESIGN.md
  • modules/system/oagw/docs/adr-cors.md
  • modules/system/oagw/docs/features/0004-cpt-cf-oagw-feature-proxy-engine.md
  • modules/system/oagw/docs/features/0008-cpt-cf-oagw-feature-observability.md
  • modules/system/oagw/docs/schemas/route.v1.schema.json
  • modules/system/oagw/docs/schemas/upstream.v1.schema.json
  • modules/system/oagw/oagw-sdk/src/models.rs
  • modules/system/oagw/oagw/src/api/rest/dto.rs
  • modules/system/oagw/oagw/src/api/rest/error.rs
  • modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs
  • modules/system/oagw/oagw/src/domain/cors.rs
  • modules/system/oagw/oagw/src/domain/error.rs
  • modules/system/oagw/oagw/src/domain/model.rs
  • modules/system/oagw/oagw/src/domain/services/client.rs
  • modules/system/oagw/oagw/src/domain/services/management.rs
  • modules/system/oagw/oagw/src/infra/proxy/service.rs
  • modules/system/oagw/oagw/src/infra/type_provisioning.rs
  • modules/system/oagw/oagw/tests/proxy_integration.rs
  • modules/system/oagw/scenarios/INDEX.md
  • modules/system/oagw/scenarios/flows/plugin-execution.md
  • modules/system/oagw/scenarios/plugins/guards/negative-10.3-cors-credentials-wildcard-rejected-config-validation.md
  • modules/system/oagw/scenarios/plugins/guards/positive-10.2-built-cors-handling.md
  • testing/e2e/conftest.py
  • testing/e2e/modules/file_parser/generate_file_parser_golden.py
  • testing/e2e/modules/oagw/conftest.py
  • testing/e2e/modules/oagw/test_authz.py
  • testing/e2e/modules/oagw/test_body_validation.py
  • testing/e2e/modules/oagw/test_cors.py
💤 Files with no reviewable changes (7)
  • modules/system/oagw/oagw/src/infra/type_provisioning.rs
  • modules/system/oagw/oagw/src/domain/services/client.rs
  • modules/system/oagw/oagw/src/domain/model.rs
  • modules/system/oagw/oagw-sdk/src/models.rs
  • modules/system/oagw/docs/schemas/upstream.v1.schema.json
  • modules/system/oagw/oagw/src/domain/services/management.rs
  • modules/system/oagw/oagw/src/api/rest/dto.rs
✅ Files skipped from review due to trivial changes (8)
  • modules/system/api-gateway/src/middleware/auth.rs
  • modules/system/oagw/docs/features/0004-cpt-cf-oagw-feature-proxy-engine.md
  • modules/system/oagw/scenarios/plugins/guards/negative-10.3-cors-credentials-wildcard-rejected-config-validation.md
  • modules/system/oagw/docs/features/0008-cpt-cf-oagw-feature-observability.md
  • modules/system/oagw/docs/ADR/0006-cors.md
  • modules/system/oagw/docs/DESIGN.md
  • testing/e2e/modules/oagw/conftest.py
  • modules/system/oagw/oagw/src/domain/error.rs
🚧 Files skipped from review as they are similar to previous changes (11)
  • testing/e2e/conftest.py
  • modules/system/oagw/docs/schemas/route.v1.schema.json
  • testing/e2e/modules/oagw/test_authz.py
  • modules/system/oagw/oagw/src/api/rest/error.rs
  • modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs
  • modules/system/oagw/scenarios/plugins/guards/positive-10.2-built-cors-handling.md
  • modules/system/oagw/scenarios/flows/plugin-execution.md
  • modules/system/oagw/scenarios/INDEX.md
  • modules/system/oagw/oagw/src/infra/proxy/service.rs
  • modules/system/oagw/docs/adr-cors.md
  • modules/system/oagw/oagw/tests/proxy_integration.rs

@asmith987 asmith987 force-pushed the fix/oagw-cors-preflight branch from 1b3aeb0 to 732c5b6 Compare April 2, 2026 18:54
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/api/api.json (1)

8272-8277: ⚠️ Potential issue | 🟠 Major

Problem.instance contract was weakened and now conflicts with existing consumers.

Line 8272 removes instance from required, but current consumers/tests still treat it as present (e.g., proxy integration assertions and SDK error shapes). This creates an API contract drift risk.

🔧 Proposed fix
       "required": [
         "type",
         "title",
         "status",
-        "detail"
+        "detail",
+        "instance"
       ],

Also applies to: 8300-8303

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/api/api.json` around lines 8272 - 8277, The Problem schema's required
array was weakened by removing "instance", breaking consumers/tests; restore
"instance" to the required arrays for the Problem schema entries (the one that
currently lists ["type","title","status","detail"] and the second occurrence
around the other diff block) so that "Problem.instance" is mandatory again;
update both schema definitions referenced (the two required arrays) to include
"instance" alongside "type", "title", "status", and "detail" to re-establish the
contract.
🧹 Nitpick comments (3)
modules/system/oagw/docs/features/0008-cpt-cf-oagw-feature-observability.md (1)

222-232: Optional: Consider addressing list indentation warnings.

Static analysis flagged MD005 (list-indent) warnings on these lines, indicating inconsistent indentation in the numbered list. While markdownlint enforcement is disabled in CI (non-blocking), addressing these could improve readability. Based on learnings, markdownlint issues in docs are non-blocking unless explicitly opted in via CI config.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/system/oagw/docs/features/0008-cpt-cf-oagw-feature-observability.md`
around lines 222 - 232, Fix the inconsistent list indentation in the numbered
CORS checklist so nested items align uniformly: indent each sub-item (e.g.,
inst-cors-h-4a and inst-cors-h-4b under inst-cors-h-4, inst-cors-h-6a under
inst-cors-h-6, and inst-cors-h-8a/inst-cors-h-8b under inst-cors-h-8) using a
consistent indentation level (e.g., 4 spaces per nesting level) and consistent
list markers so markdownlint MD005 warnings are resolved and the hierarchy for
inst-cors-h-3, inst-cors-h-4, inst-cors-h-5, inst-cors-h-6, inst-cors-h-7, and
inst-cors-h-8 is visually and syntactically consistent.
docs/api/api.json (1)

8283-8285: Problem.context should declare an explicit schema type.

Line 8283 adds context with only a description, which makes it unconstrained. If this is intended to be structured context, declare it as an object explicitly.

♻️ Suggested schema tightening
           "context": {
+            "type": "object",
             "description": "Optional structured context (e.g. `resource_type`, `resource_name`)."
+            ,"additionalProperties": true
           },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/api/api.json` around lines 8283 - 8285, The Problem.context entry is
missing an explicit schema type; update the Problem.context definition in
docs/api/api.json to declare it as an object (e.g., add "type": "object") and
either define expected properties or set "additionalProperties": true if
free-form key/value pairs are allowed so the schema is constrained and
validated.
modules/system/oagw/oagw/tests/proxy_integration.rs (1)

3655-3679: Cover the new handler path and assert the exact CORS failures.

These cases still call facade().proxy_request(...), so they never hit the 204 preflight short-circuit in modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs:25-68, which is the main change in this PR. They also only assert is_err(). Please add one OPTIONS test through start_oagw_server() and match ServiceGatewayError::CorsOriginNotAllowed / CorsMethodNotAllowed here so the contract stays explicit.

One way to tighten the assertion pattern
-    assert!(
-        response.is_err(),
-        "disallowed origin on actual request should be rejected before reaching upstream"
-    );
+    assert!(matches!(
+        response,
+        Err(oagw_sdk::error::ServiceGatewayError::CorsOriginNotAllowed { .. })
+    ));

Also applies to: 3681-3748, 3796-3811

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/system/oagw/oagw/tests/proxy_integration.rs` around lines 3655 -
3679, Update the test to cover the new handler path by adding an OPTIONS request
that goes through start_oagw_server instead of facade().proxy_request and assert
the specific CORS error variants; specifically, in the test
cors_actual_request_disallowed_origin_rejected_before_upstream (and the other
affected tests), replace or add an OPTIONS request sent via start_oagw_server
(so it hits the 204 preflight handler path) and match the returned error to
ServiceGatewayError::CorsOriginNotAllowed or
ServiceGatewayError::CorsMethodNotAllowed (as appropriate) rather than only
asserting response.is_err(), ensuring the test explicitly checks the CORS
failure contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@modules/system/oagw/docs/features/0008-cpt-cf-oagw-feature-observability.md`:
- Around line 218-233: The doc currently mentions adding
Access-Control-Expose-Headers "if configured" but the configuration section only
lists allowed_origins and allowed_methods; decide which of three options to
implement and update the spec accordingly: (1) If expose_headers is supported,
add a clear config field named expose_headers to the configuration requirements
(alongside allowed_origins and allowed_methods) and document its format and
behavior; (2) If expose_headers was removed, delete the “Add
Access-Control-Expose-Headers if configured” note (inst-cors-h-8a) so the
behavior is consistent; or (3) If it is an optional/advanced feature, explicitly
mark expose_headers as optional in the configuration requirements (line ~364)
and reference it from inst-cors-h-8a as optional, including its expected type
and semantics. Ensure references to Access-Control-Expose-Headers and the config
field name expose_headers are consistent across the CORS flow section and the
configuration requirements.

In `@modules/system/oagw/oagw/src/infra/proxy/service.rs`:
- Around line 420-436: The current CORS check runs whenever an Origin header
exists, causing same-origin requests to be treated as cross-origin; change the
guard in the block that uses effective_cors, request_origin and instance_uri so
it only performs CORS validation when the request is actually cross-origin
(i.e., the request Origin does not match the gateway/instance origin), not
merely when the Origin header is present. Concretely, before calling
crate::domain::cors::is_origin_allowed and is_method_allowed, compare the parsed
request_origin to the instance_uri (or use/implement a small helper like
is_same_origin(origin, instance_uri) that compares scheme/host/port) and skip
the origin/method checks when they are same-origin; keep the existing error
returns (DomainError::CorsOriginNotAllowed / CorsMethodNotAllowed) for true
cross-origin failures.

---

Outside diff comments:
In `@docs/api/api.json`:
- Around line 8272-8277: The Problem schema's required array was weakened by
removing "instance", breaking consumers/tests; restore "instance" to the
required arrays for the Problem schema entries (the one that currently lists
["type","title","status","detail"] and the second occurrence around the other
diff block) so that "Problem.instance" is mandatory again; update both schema
definitions referenced (the two required arrays) to include "instance" alongside
"type", "title", "status", and "detail" to re-establish the contract.

---

Nitpick comments:
In `@docs/api/api.json`:
- Around line 8283-8285: The Problem.context entry is missing an explicit schema
type; update the Problem.context definition in docs/api/api.json to declare it
as an object (e.g., add "type": "object") and either define expected properties
or set "additionalProperties": true if free-form key/value pairs are allowed so
the schema is constrained and validated.

In `@modules/system/oagw/docs/features/0008-cpt-cf-oagw-feature-observability.md`:
- Around line 222-232: Fix the inconsistent list indentation in the numbered
CORS checklist so nested items align uniformly: indent each sub-item (e.g.,
inst-cors-h-4a and inst-cors-h-4b under inst-cors-h-4, inst-cors-h-6a under
inst-cors-h-6, and inst-cors-h-8a/inst-cors-h-8b under inst-cors-h-8) using a
consistent indentation level (e.g., 4 spaces per nesting level) and consistent
list markers so markdownlint MD005 warnings are resolved and the hierarchy for
inst-cors-h-3, inst-cors-h-4, inst-cors-h-5, inst-cors-h-6, inst-cors-h-7, and
inst-cors-h-8 is visually and syntactically consistent.

In `@modules/system/oagw/oagw/tests/proxy_integration.rs`:
- Around line 3655-3679: Update the test to cover the new handler path by adding
an OPTIONS request that goes through start_oagw_server instead of
facade().proxy_request and assert the specific CORS error variants;
specifically, in the test
cors_actual_request_disallowed_origin_rejected_before_upstream (and the other
affected tests), replace or add an OPTIONS request sent via start_oagw_server
(so it hits the 204 preflight handler path) and match the returned error to
ServiceGatewayError::CorsOriginNotAllowed or
ServiceGatewayError::CorsMethodNotAllowed (as appropriate) rather than only
asserting response.is_err(), ensuring the test explicitly checks the CORS
failure contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 26f51445-bd95-4cca-b2a5-b5bedf451ea1

📥 Commits

Reviewing files that changed from the base of the PR and between 1b3aeb0 and 732c5b6.

📒 Files selected for processing (32)
  • config/e2e-local.yaml
  • docs/api/api.json
  • modules/system/api-gateway/src/middleware/auth.rs
  • modules/system/oagw/docs/ADR/0006-cors.md
  • modules/system/oagw/docs/DESIGN.md
  • modules/system/oagw/docs/adr-cors.md
  • modules/system/oagw/docs/features/0004-cpt-cf-oagw-feature-proxy-engine.md
  • modules/system/oagw/docs/features/0008-cpt-cf-oagw-feature-observability.md
  • modules/system/oagw/docs/schemas/route.v1.schema.json
  • modules/system/oagw/docs/schemas/upstream.v1.schema.json
  • modules/system/oagw/oagw-sdk/src/models.rs
  • modules/system/oagw/oagw/src/api/rest/dto.rs
  • modules/system/oagw/oagw/src/api/rest/error.rs
  • modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs
  • modules/system/oagw/oagw/src/domain/cors.rs
  • modules/system/oagw/oagw/src/domain/error.rs
  • modules/system/oagw/oagw/src/domain/model.rs
  • modules/system/oagw/oagw/src/domain/services/client.rs
  • modules/system/oagw/oagw/src/domain/services/management.rs
  • modules/system/oagw/oagw/src/infra/proxy/service.rs
  • modules/system/oagw/oagw/src/infra/type_provisioning.rs
  • modules/system/oagw/oagw/tests/proxy_integration.rs
  • modules/system/oagw/scenarios/INDEX.md
  • modules/system/oagw/scenarios/flows/plugin-execution.md
  • modules/system/oagw/scenarios/plugins/guards/negative-10.3-cors-credentials-wildcard-rejected-config-validation.md
  • modules/system/oagw/scenarios/plugins/guards/positive-10.2-built-cors-handling.md
  • testing/e2e/conftest.py
  • testing/e2e/modules/file_parser/generate_file_parser_golden.py
  • testing/e2e/modules/oagw/conftest.py
  • testing/e2e/modules/oagw/test_authz.py
  • testing/e2e/modules/oagw/test_body_validation.py
  • testing/e2e/modules/oagw/test_cors.py
💤 Files with no reviewable changes (7)
  • modules/system/oagw/oagw/src/infra/type_provisioning.rs
  • modules/system/oagw/oagw/src/domain/services/client.rs
  • modules/system/oagw/oagw-sdk/src/models.rs
  • modules/system/oagw/oagw/src/domain/model.rs
  • modules/system/oagw/oagw/src/api/rest/dto.rs
  • modules/system/oagw/docs/schemas/upstream.v1.schema.json
  • modules/system/oagw/oagw/src/domain/services/management.rs
✅ Files skipped from review due to trivial changes (3)
  • modules/system/oagw/docs/features/0004-cpt-cf-oagw-feature-proxy-engine.md
  • modules/system/oagw/scenarios/flows/plugin-execution.md
  • modules/system/oagw/docs/ADR/0006-cors.md
🚧 Files skipped from review as they are similar to previous changes (14)
  • modules/system/oagw/scenarios/plugins/guards/negative-10.3-cors-credentials-wildcard-rejected-config-validation.md
  • modules/system/oagw/oagw/src/domain/error.rs
  • testing/e2e/modules/oagw/test_body_validation.py
  • modules/system/api-gateway/src/middleware/auth.rs
  • modules/system/oagw/docs/DESIGN.md
  • modules/system/oagw/docs/schemas/route.v1.schema.json
  • modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs
  • testing/e2e/conftest.py
  • testing/e2e/modules/oagw/test_authz.py
  • config/e2e-local.yaml
  • modules/system/oagw/scenarios/INDEX.md
  • modules/system/oagw/scenarios/plugins/guards/positive-10.2-built-cors-handling.md
  • modules/system/oagw/docs/adr-cors.md
  • testing/e2e/modules/oagw/test_cors.py

.header("access-control-allow-origin", origin)
.header("access-control-allow-methods", requested_method)
.header("access-control-allow-headers", requested_headers)
.header("access-control-max-age", "86400")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH

Permissive preflight response omits Access-Control-Allow-Credentials: true.

Per the Fetch spec (CORS check, steps 6-8), the browser checks the preflight response for Access-Control-Allow-Credentials: true when the main request uses credentials: 'include'. Without it, browsers reject credentialed cross-origin requests at the preflight stage — breaking any upstream configured with allow_credentials: true.

Add .header("access-control-allow-credentials", "true") here. Since the preflight is intentionally permissive (no upstream config available), always including it is consistent with the echo-everything approach. The actual-request response will enforce the real credentials policy via apply_cors_headers.

@asmith987 asmith987 force-pushed the fix/oagw-cors-preflight branch from 732c5b6 to b2958d2 Compare April 3, 2026 14:26
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
modules/system/oagw/oagw/src/infra/proxy/service.rs (1)

420-436: ⚠️ Potential issue | 🟠 Major

Only enforce CORS on true cross-origin requests.

This block still runs whenever an Origin header is present. Browsers also send Origin on same-origin POST/PUT/DELETE requests and on WebSocket handshakes, so same-origin traffic can be rejected here whenever the gateway’s own origin is not in allowed_origins. It also means the later response path can add proxy CORS headers to same-origin responses. Gate this on an actual origin comparison (scheme/host/port) instead of raw header presence.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/system/oagw/oagw/src/infra/proxy/service.rs` around lines 420 - 436,
The CORS checks in the block referencing effective_cors, request_origin,
is_origin_allowed and is_method_allowed are firing on any present Origin header;
change this to only enforce when the request is actually cross-origin by parsing
and comparing the request Origin against the gateway's own origin (scheme, host,
port) rather than raw header presence. Update the logic around
request_origin/instance_uri to parse both origins (or use an existing helper) to
detect same-origin requests and skip the is_origin_allowed/is_method_allowed
checks for same-origin; only call DomainError::CorsOriginNotAllowed or
DomainError::CorsMethodNotAllowed when the origins differ (true cross-origin).
🧹 Nitpick comments (4)
docs/api/api.json (1)

8272-8285: Add an explicit type: "object" to the Problem.context schema.

The context property lacks type specification, which weakens validation and client code generation. Since it holds structured metadata, it should be explicitly typed as an object.

♻️ Proposed schema adjustment
           "context": {
+            "type": "object",
+            "additionalProperties": true,
             "description": "Optional structured context (e.g. `resource_type`, `resource_name`)."
           },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/api/api.json` around lines 8272 - 8285, The Problem schema's context
property is missing an explicit type; update the Problem.context schema by
adding "type": "object" to the context property definition so it is validated as
structured metadata (e.g., modify the properties block for "context" inside the
Problem schema to include type: "object" and any needed
description/constraints). Ensure the change targets the "Problem" schema's
"context" property entry.
modules/system/oagw/oagw/tests/proxy_integration.rs (3)

3671-3678: Assert specific error variant instead of just is_err().

The test verifies the request fails but doesn't confirm it fails for the correct reason. Without asserting the specific error variant (CorsOriginNotAllowed), this test could pass if the request fails due to an unrelated issue (e.g., route not found, validation error).

♻️ Proposed fix to assert specific error variant
     let response = h
         .facade()
         .proxy_request(h.security_context().clone(), req)
         .await;
-    assert!(
-        response.is_err(),
-        "disallowed origin on actual request should be rejected before reaching upstream"
-    );
+    match response {
+        Err(err) => assert!(
+            matches!(
+                err,
+                oagw_sdk::error::ServiceGatewayError::CorsOriginNotAllowed { .. }
+            ),
+            "expected CorsOriginNotAllowed, got: {err:?}"
+        ),
+        Ok(resp) => panic!(
+            "expected CorsOriginNotAllowed error, got response with status {}",
+            resp.status()
+        ),
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/system/oagw/oagw/tests/proxy_integration.rs` around lines 3671 -
3678, The test currently only checks response.is_err() after calling
facade().proxyRequest (proxy_request) which can hide unrelated failures; replace
that generic assertion with a specific assertion that response is an Err
containing the CorsOriginNotAllowed variant (e.g., match or matches! against the
error enum variant used by proxy_request, such as
OagwError::CorsOriginNotAllowed) so the test ensures the failure reason is CORS
origin rejection rather than some other error from proxy_request/facade().

3804-3811: Assert specific error variant for non-matching origin.

For consistency with the other CORS rejection tests, this should also verify the CorsOriginNotAllowed variant.

♻️ Proposed fix
     let response = h
         .facade()
         .proxy_request(h.security_context().clone(), req)
-        .await;
-    assert!(
-        response.is_err(),
-        "non-matching origin should be rejected with 403"
-    );
+        .await;
+    match response {
+        Err(err) => assert!(
+            matches!(
+                err,
+                oagw_sdk::error::ServiceGatewayError::CorsOriginNotAllowed { .. }
+            ),
+            "expected CorsOriginNotAllowed for non-matching origin, got: {err:?}"
+        ),
+        Ok(resp) => panic!(
+            "expected CorsOriginNotAllowed error, got response with status {}",
+            resp.status()
+        ),
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/system/oagw/oagw/tests/proxy_integration.rs` around lines 3804 -
3811, The test currently only asserts that proxy_request returned an error for a
non-matching origin; change the assertion to verify the specific CORS rejection
variant (CorsOriginNotAllowed) instead. Update the check around the call to
h.facade().proxy_request(h.security_context().clone(), req).await (the response
variable) to assert that the Err matches the CorsOriginNotAllowed variant (e.g.,
by using pattern matching or an expect_err and matching the error enum variant)
so the test aligns with other CORS rejection tests.

3740-3748: Assert specific error variant for method rejection test.

Same issue as above — the test should verify CorsMethodNotAllowed rather than just checking is_err().

♻️ Proposed fix
     let response = h
         .facade()
         .proxy_request(h.security_context().clone(), req)
         .await;
-    assert!(
-        response.is_err(),
-        "disallowed method on actual request should be rejected before reaching upstream"
-    );
+    match response {
+        Err(err) => assert!(
+            matches!(
+                err,
+                oagw_sdk::error::ServiceGatewayError::CorsMethodNotAllowed { .. }
+            ),
+            "expected CorsMethodNotAllowed, got: {err:?}"
+        ),
+        Ok(resp) => panic!(
+            "expected CorsMethodNotAllowed error, got response with status {}",
+            resp.status()
+        ),
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/system/oagw/oagw/tests/proxy_integration.rs` around lines 3740 -
3748, The test currently only checks response.is_err(); instead assert that the
error is the specific CorsMethodNotAllowed variant by unwrapping the Err and
matching its variant — call h.facade().proxy_request(...).await, capture the
Result into response, then extract the error (e.g. let err =
response.expect_err("...");) and assert matches!(err,
ErrorType::CorsMethodNotAllowed) or assert_eq!(err,
ErrorType::CorsMethodNotAllowed) using the actual error enum used in the code;
update the assertion in proxy_integration.rs to reference the exact
CorsMethodNotAllowed variant instead of is_err().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@modules/system/oagw/oagw/src/infra/proxy/service.rs`:
- Around line 420-436: The CORS checks in the block referencing effective_cors,
request_origin, is_origin_allowed and is_method_allowed are firing on any
present Origin header; change this to only enforce when the request is actually
cross-origin by parsing and comparing the request Origin against the gateway's
own origin (scheme, host, port) rather than raw header presence. Update the
logic around request_origin/instance_uri to parse both origins (or use an
existing helper) to detect same-origin requests and skip the
is_origin_allowed/is_method_allowed checks for same-origin; only call
DomainError::CorsOriginNotAllowed or DomainError::CorsMethodNotAllowed when the
origins differ (true cross-origin).

---

Nitpick comments:
In `@docs/api/api.json`:
- Around line 8272-8285: The Problem schema's context property is missing an
explicit type; update the Problem.context schema by adding "type": "object" to
the context property definition so it is validated as structured metadata (e.g.,
modify the properties block for "context" inside the Problem schema to include
type: "object" and any needed description/constraints). Ensure the change
targets the "Problem" schema's "context" property entry.

In `@modules/system/oagw/oagw/tests/proxy_integration.rs`:
- Around line 3671-3678: The test currently only checks response.is_err() after
calling facade().proxyRequest (proxy_request) which can hide unrelated failures;
replace that generic assertion with a specific assertion that response is an Err
containing the CorsOriginNotAllowed variant (e.g., match or matches! against the
error enum variant used by proxy_request, such as
OagwError::CorsOriginNotAllowed) so the test ensures the failure reason is CORS
origin rejection rather than some other error from proxy_request/facade().
- Around line 3804-3811: The test currently only asserts that proxy_request
returned an error for a non-matching origin; change the assertion to verify the
specific CORS rejection variant (CorsOriginNotAllowed) instead. Update the check
around the call to h.facade().proxy_request(h.security_context().clone(),
req).await (the response variable) to assert that the Err matches the
CorsOriginNotAllowed variant (e.g., by using pattern matching or an expect_err
and matching the error enum variant) so the test aligns with other CORS
rejection tests.
- Around line 3740-3748: The test currently only checks response.is_err();
instead assert that the error is the specific CorsMethodNotAllowed variant by
unwrapping the Err and matching its variant — call
h.facade().proxy_request(...).await, capture the Result into response, then
extract the error (e.g. let err = response.expect_err("...");) and assert
matches!(err, ErrorType::CorsMethodNotAllowed) or assert_eq!(err,
ErrorType::CorsMethodNotAllowed) using the actual error enum used in the code;
update the assertion in proxy_integration.rs to reference the exact
CorsMethodNotAllowed variant instead of is_err().

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 778510e8-1f8b-492d-a2ac-b9573da97d7c

📥 Commits

Reviewing files that changed from the base of the PR and between 732c5b6 and b2958d2.

📒 Files selected for processing (32)
  • config/e2e-local.yaml
  • docs/api/api.json
  • modules/system/api-gateway/src/middleware/auth.rs
  • modules/system/oagw/docs/ADR/0006-cors.md
  • modules/system/oagw/docs/DESIGN.md
  • modules/system/oagw/docs/adr-cors.md
  • modules/system/oagw/docs/features/0004-cpt-cf-oagw-feature-proxy-engine.md
  • modules/system/oagw/docs/features/0008-cpt-cf-oagw-feature-observability.md
  • modules/system/oagw/docs/schemas/route.v1.schema.json
  • modules/system/oagw/docs/schemas/upstream.v1.schema.json
  • modules/system/oagw/oagw-sdk/src/models.rs
  • modules/system/oagw/oagw/src/api/rest/dto.rs
  • modules/system/oagw/oagw/src/api/rest/error.rs
  • modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs
  • modules/system/oagw/oagw/src/domain/cors.rs
  • modules/system/oagw/oagw/src/domain/error.rs
  • modules/system/oagw/oagw/src/domain/model.rs
  • modules/system/oagw/oagw/src/domain/services/client.rs
  • modules/system/oagw/oagw/src/domain/services/management.rs
  • modules/system/oagw/oagw/src/infra/proxy/service.rs
  • modules/system/oagw/oagw/src/infra/type_provisioning.rs
  • modules/system/oagw/oagw/tests/proxy_integration.rs
  • modules/system/oagw/scenarios/INDEX.md
  • modules/system/oagw/scenarios/flows/plugin-execution.md
  • modules/system/oagw/scenarios/plugins/guards/negative-10.3-cors-credentials-wildcard-rejected-config-validation.md
  • modules/system/oagw/scenarios/plugins/guards/positive-10.2-built-cors-handling.md
  • testing/e2e/conftest.py
  • testing/e2e/modules/file_parser/generate_file_parser_golden.py
  • testing/e2e/modules/oagw/conftest.py
  • testing/e2e/modules/oagw/test_authz.py
  • testing/e2e/modules/oagw/test_body_validation.py
  • testing/e2e/modules/oagw/test_cors.py
💤 Files with no reviewable changes (7)
  • modules/system/oagw/oagw/src/infra/type_provisioning.rs
  • modules/system/oagw/oagw/src/domain/model.rs
  • modules/system/oagw/oagw-sdk/src/models.rs
  • modules/system/oagw/oagw/src/api/rest/dto.rs
  • modules/system/oagw/oagw/src/domain/services/client.rs
  • modules/system/oagw/docs/schemas/upstream.v1.schema.json
  • modules/system/oagw/oagw/src/domain/services/management.rs
✅ Files skipped from review due to trivial changes (2)
  • modules/system/oagw/docs/features/0004-cpt-cf-oagw-feature-proxy-engine.md
  • modules/system/oagw/scenarios/plugins/guards/negative-10.3-cors-credentials-wildcard-rejected-config-validation.md
🚧 Files skipped from review as they are similar to previous changes (14)
  • modules/system/oagw/oagw/src/api/rest/error.rs
  • modules/system/oagw/docs/schemas/route.v1.schema.json
  • testing/e2e/modules/oagw/test_authz.py
  • modules/system/oagw/oagw/src/domain/error.rs
  • modules/system/oagw/docs/DESIGN.md
  • testing/e2e/modules/oagw/conftest.py
  • modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs
  • testing/e2e/conftest.py
  • testing/e2e/modules/file_parser/generate_file_parser_golden.py
  • modules/system/oagw/docs/adr-cors.md
  • modules/system/oagw/scenarios/flows/plugin-execution.md
  • modules/system/oagw/scenarios/INDEX.md
  • modules/system/oagw/docs/ADR/0006-cors.md
  • testing/e2e/modules/oagw/test_body_validation.py

Browser preflight (OPTIONS) requests carry no credentials, so upstream
resolution fails. Preflight now returns a permissive 204 at the handler
level; origin and method enforcement moves to the actual request path
after upstream resolution. Removes allowed_headers and max_age from
CorsConfig (max_age hardcoded to 86400, header validation unnecessary
with permissive preflight).

Signed-off-by: Andre Smith <andre.smith+oss@acronis.com>
Signed-off-by: Andre Smith <andre.smith@acronis.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@asmith987 asmith987 force-pushed the fix/oagw-cors-preflight branch from b2958d2 to 84bc327 Compare April 3, 2026 15:03
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (1)
modules/system/oagw/oagw/src/infra/proxy/service.rs (1)

420-436: ⚠️ Potential issue | 🟠 Major

Don't treat every Origin header as cross-origin.

Line 422 gates this block on header presence alone. Browsers also send Origin on same-origin POST/PUT/DELETE requests and WebSocket handshakes, so this can return 403 for legitimate same-origin traffic whenever the gateway's own origin is not in allowed_origins. Gate the origin/method checks on an actual cross-origin comparison instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@modules/system/oagw/oagw/src/infra/proxy/service.rs` around lines 420 - 436,
The current CORS gating treats any present Origin header as cross-origin; change
the logic in the block that references effective_cors, request_origin,
is_origin_allowed, and is_method_allowed so you only enforce origin/method
checks when the request is actually cross-origin (compare the parsed Origin
value against the gateway's own origin/instance URI or the request
host+scheme+port to determine same-origin). If the Origin matches the gateway's
origin consider it same-origin and skip the
CorsOriginNotAllowed/CorsMethodNotAllowed checks; otherwise run
crate::domain::cors::is_origin_allowed and is_method_allowed and return the
DomainError variants when appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@modules/system/oagw/docs/DESIGN.md`:
- Around line 279-280: The table row incorrectly attributes CORS enforcement to
the guard plugin `gts.x.core.oagw.guard_plugin.v1~x.core.oagw.cors.v1`; update
the DESIGN.md row to either remove CORS from the guard-plugin table or relabel
it as "Core CORS (not a guard-plugin)" and change the description to state that
preflight handling is implemented in api/rest/handlers/proxy.rs and
origin/method validation is implemented in infra/proxy/service.rs, so readers
know the actual implementation locations and responsibilities.

In `@modules/system/oagw/docs/features/0008-cpt-cf-oagw-feature-observability.md`:
- Around line 218-232: The spec omits the CORS method-rejection path implemented
in infra/proxy/service.rs: add a step after Origin validation (e.g., after
inst-cors-h-5 / before inst-cors-h-7) that checks the request HTTP method
against configured allowed_methods and, if not allowed, returns 403 (similar to
inst-cors-h-6 for origin); update the numbered flow, DoD and acceptance-criteria
entries (also update the corresponding mentions around lines 364-365 and
438-440) to include this allowed_methods rejection and its 403 response.

In `@modules/system/oagw/oagw/tests/proxy_integration.rs`:
- Around line 3671-3679: The test currently only asserts that proxy_request
returned an error; change it to match the concrete CORS rejection (expecting the
CorsOriginNotAllowed or CorsMethodNotAllowed variant) by inspecting the Err
returned from h.facade().proxy_request(h.security_context().clone(), req).await
and pattern-matching or using assert_eq! / matches! against the specific
variant; additionally assert that no requests reached the upstream by checking
the test helper's request capture (via h’s upstream/guard request counter or
request list) to ensure upstream received zero requests. Apply the same changes
to the other occurrences noted (around lines 3740-3747 and 3804-3811).

In `@testing/e2e/conftest.py`:
- Around line 18-29: The helper auth_headers currently always returns a truthy
dict because it uses os.getenv with a default token; change it to return None
when E2E_AUTH_TOKEN is not set so existing tests that call `if not auth_headers:
pytest.skip(...)` still skip. Concretely, in the auth_headers function read
E2E_AUTH_TOKEN without a default (e.g., token = os.getenv("E2E_AUTH_TOKEN")),
return None when token is falsy, and only return {"Authorization": f"Bearer
{token}"} when a token exists; this preserves behavior expected by tests like
testing/e2e/modules/file_parser/test_file_parser_xlsx_pptx.py.

In `@testing/e2e/modules/oagw/test_cors.py`:
- Around line 23-31: The OPTIONS tests currently send oagw_headers and use
routes that already allow the tested method, which can mask handler-level
bypass; update test_cors_preflight_fully_permissive and
test_cors_preflight_permissive_echoes_any_method to omit oagw_headers (make the
preflight anonymous), request an HTTP method that the targeted route does NOT
allow (pick a method not in that route's allowed methods so route-level method
checks would fail if invoked), and add an additional case with an Origin value
that is NOT in the allowlist to verify the handler-level permissive preflight
still echoes/permits correctly; adjust the corresponding assertions to expect
permissive 204 behavior for the anonymous preflight and include the new
non-allowlisted Origin case (apply same changes to the tests around lines
68-97).

---

Duplicate comments:
In `@modules/system/oagw/oagw/src/infra/proxy/service.rs`:
- Around line 420-436: The current CORS gating treats any present Origin header
as cross-origin; change the logic in the block that references effective_cors,
request_origin, is_origin_allowed, and is_method_allowed so you only enforce
origin/method checks when the request is actually cross-origin (compare the
parsed Origin value against the gateway's own origin/instance URI or the request
host+scheme+port to determine same-origin). If the Origin matches the gateway's
origin consider it same-origin and skip the
CorsOriginNotAllowed/CorsMethodNotAllowed checks; otherwise run
crate::domain::cors::is_origin_allowed and is_method_allowed and return the
DomainError variants when appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f2cf5509-a9d1-4bf7-91a2-98f0bbaa739b

📥 Commits

Reviewing files that changed from the base of the PR and between b2958d2 and 84bc327.

📒 Files selected for processing (32)
  • config/e2e-local.yaml
  • docs/api/api.json
  • modules/system/api-gateway/src/middleware/auth.rs
  • modules/system/oagw/docs/ADR/0006-cors.md
  • modules/system/oagw/docs/DESIGN.md
  • modules/system/oagw/docs/adr-cors.md
  • modules/system/oagw/docs/features/0004-cpt-cf-oagw-feature-proxy-engine.md
  • modules/system/oagw/docs/features/0008-cpt-cf-oagw-feature-observability.md
  • modules/system/oagw/docs/schemas/route.v1.schema.json
  • modules/system/oagw/docs/schemas/upstream.v1.schema.json
  • modules/system/oagw/oagw-sdk/src/models.rs
  • modules/system/oagw/oagw/src/api/rest/dto.rs
  • modules/system/oagw/oagw/src/api/rest/error.rs
  • modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs
  • modules/system/oagw/oagw/src/domain/cors.rs
  • modules/system/oagw/oagw/src/domain/error.rs
  • modules/system/oagw/oagw/src/domain/model.rs
  • modules/system/oagw/oagw/src/domain/services/client.rs
  • modules/system/oagw/oagw/src/domain/services/management.rs
  • modules/system/oagw/oagw/src/infra/proxy/service.rs
  • modules/system/oagw/oagw/src/infra/type_provisioning.rs
  • modules/system/oagw/oagw/tests/proxy_integration.rs
  • modules/system/oagw/scenarios/INDEX.md
  • modules/system/oagw/scenarios/flows/plugin-execution.md
  • modules/system/oagw/scenarios/plugins/guards/negative-10.3-cors-credentials-wildcard-rejected-config-validation.md
  • modules/system/oagw/scenarios/plugins/guards/positive-10.2-built-cors-handling.md
  • testing/e2e/conftest.py
  • testing/e2e/modules/file_parser/generate_file_parser_golden.py
  • testing/e2e/modules/oagw/conftest.py
  • testing/e2e/modules/oagw/test_authz.py
  • testing/e2e/modules/oagw/test_body_validation.py
  • testing/e2e/modules/oagw/test_cors.py
💤 Files with no reviewable changes (7)
  • modules/system/oagw/oagw/src/infra/type_provisioning.rs
  • modules/system/oagw/oagw-sdk/src/models.rs
  • modules/system/oagw/oagw/src/domain/model.rs
  • modules/system/oagw/oagw/src/api/rest/dto.rs
  • modules/system/oagw/docs/schemas/upstream.v1.schema.json
  • modules/system/oagw/oagw/src/domain/services/management.rs
  • modules/system/oagw/oagw/src/domain/services/client.rs
✅ Files skipped from review due to trivial changes (2)
  • modules/system/oagw/scenarios/plugins/guards/negative-10.3-cors-credentials-wildcard-rejected-config-validation.md
  • docs/api/api.json
🚧 Files skipped from review as they are similar to previous changes (15)
  • modules/system/oagw/scenarios/flows/plugin-execution.md
  • modules/system/api-gateway/src/middleware/auth.rs
  • modules/system/oagw/docs/features/0004-cpt-cf-oagw-feature-proxy-engine.md
  • testing/e2e/modules/oagw/test_body_validation.py
  • modules/system/oagw/oagw/src/domain/error.rs
  • testing/e2e/modules/file_parser/generate_file_parser_golden.py
  • testing/e2e/modules/oagw/conftest.py
  • config/e2e-local.yaml
  • modules/system/oagw/oagw/src/api/rest/error.rs
  • modules/system/oagw/scenarios/INDEX.md
  • modules/system/oagw/docs/schemas/route.v1.schema.json
  • modules/system/oagw/oagw/src/api/rest/handlers/proxy.rs
  • modules/system/oagw/scenarios/plugins/guards/positive-10.2-built-cors-handling.md
  • modules/system/oagw/docs/adr-cors.md
  • modules/system/oagw/docs/ADR/0006-cors.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants