Skip to content

feat: CORS implementation#159

Merged
thushan merged 7 commits into
mainfrom
feature/cors-impl
Jun 6, 2026
Merged

feat: CORS implementation#159
thushan merged 7 commits into
mainfrom
feature/cors-impl

Conversation

@thushan

@thushan thushan commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Adds CORS support back into Olla via rs/cors. Addresses #156 .

CORS is opt in via:

server:

  # CORS (Cross-Origin Resource Sharing) for browser-based clients (OpenWebUI, dashboards).
  # Off by default. Non-browser clients (curl, SDKs, coding agents) ignore CORS entirely,
  # so enabling this has no effect on them. When a request carries no Origin header it is
  # passed through untouched.
  #
  # Caveat: allow_credentials: true is incompatible with allowed_origins: ["*"] (the CORS
  # spec forbids it and browsers reject the response). List explicit origins when using
  # credentials. exposed_headers left empty auto-exposes the full X-Olla-* response header
  # set, so browser clients can read routing/model metadata.
  cors:
    enabled: false                   # opt-in: only enable when browser clients need access
    allowed_origins:
      - "*"
    allowed_methods:
      - "GET"
      - "POST"
      - "OPTIONS"
    allowed_headers:
      - "*"
    exposed_headers: []              # empty: auto-exposes the X-Olla-* headers
    allow_credentials: false         # set true only with explicit allowed_origins (not "*")
    max_age: 300                     # preflight cache in seconds (5 minutes)

Summary by CodeRabbit

  • New Features

    • Added optional CORS support for browser-direct connections with configurable allowed origins, methods, headers, exposed headers, credentials support, and preflight caching (disabled by default).
  • Documentation

    • Updated API reference and added comprehensive CORS configuration guides, security best practices, and troubleshooting FAQs with environment variable overrides.
  • Tests

    • Added CORS middleware and HTTP service integration tests.
  • Chores

    • Added CORS middleware dependency.

@thushan thushan self-assigned this Jun 5, 2026
@thushan thushan added the security security related issue label Jun 5, 2026
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Lost in the diff? Review this PR in Change Stack to follow the change map from intent to exact ranges.

Review Change Stack

Walkthrough

Adds optional CORS middleware support for browser-direct client connections to the Olla API server. Includes configuration contracts with validation, rs/cors middleware integration wired into the HTTP service, comprehensive configuration loading with environment variable overrides, and extensive user documentation covering browser-direct use cases and production safety constraints.

Changes

CORS Middleware and Configuration

Layer / File(s) Summary
CORS configuration contract and validation
internal/config/types.go
CorsConfig type defines allowed origins, methods, headers, credentials, and max age for preflight caching. Validate() method enforces runtime safety: rejects empty allowed origins when enabled, rejects negative max age, and prevents unsafe combination of allow_credentials: true with wildcard origins (["*"]).
Configuration loading and validation
internal/config/config.go, internal/config/config_test.go
DefaultConfig() initialises CORS disabled with sensible defaults (wildcard origins/methods/headers, 300s max age, credentials off). applyEnvOverrides() parses OLLA_SERVER_CORS_* environment variables including comma-separated lists and boolean flags. Config.Validate() chains CorsConfig.Validate(). Tests verify YAML parsing, environment overrides, and validation across enabled/disabled/invalid states.
CORS middleware implementation
internal/app/middleware/cors.go, internal/app/middleware/cors_test.go
DefaultCORSExposedHeaders lists auto-exposed X-Olla-* response headers for browser clients. NewCORS() wraps github.com/rs/cors with config-driven settings, defaulting exposed headers when unconfigured. Tests verify preflight OPTIONS handling (204 response without downstream handler invocation), actual request forwarding with origin headers, no-origin passthrough, explicit header config overrides, credentials reflection, and disallowed origin rejection.
HTTP service CORS wiring
internal/app/services/http.go, internal/app/services/http_cors_test.go
HTTP service wraps its route mux with CORS middleware during Start() via new applyCORS() helper, which conditionally applies the middleware when enabled or returns the handler unchanged when disabled. Logs CORS configuration when active. Tests verify preflight interception, request forwarding, disabled passthrough, and no-origin passthrough.
Configuration defaults and dependencies
go.mod, config/config.yaml, CLAUDE.md, readme.md
Adds github.com/rs/cors v1.11.1 direct dependency. Default config.yaml specifies CORS disabled with wildcard defaults and 300s cache. CLAUDE.md documents the endorsed dependency. readme.md updates production-ready features to include CORS and request size limits alongside graceful shutdown.
User documentation
docs/content/, docs/content/api-reference/overview.md, docs/content/configuration/, docs/content/faq.md
API reference clarifies CORS disabled by default, applies to browser-direct clients, handles preflight and exposes X-Olla-* headers when enabled. Configuration overview provides basic setup example. Security best practices document development vs production configuration, warn about wildcard+credentials rejection, detail environment variable overrides and header exposure. FAQ explains when CORS is required (browser-direct only), that server-to-server clients including OpenWebUI do not need CORS, and guides response header visibility troubleshooting.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Browser Client
  participant HTTP as HTTP Service
  participant CORS as CORS Middleware
  participant Handler as Route Handler
  
  Client->>HTTP: OPTIONS /api/models (preflight)
  HTTP->>CORS: wrapped mux
  CORS->>CORS: check origin allowed
  CORS-->>Client: 204 No Content + Access-Control-* headers
  
  Client->>HTTP: GET /api/models (with Origin header)
  HTTP->>CORS: wrapped mux
  CORS->>Handler: forward request
  Handler-->>CORS: response + X-Olla-Model header
  CORS->>CORS: add Access-Control-Allow-Origin<br/>add Access-Control-Expose-Headers
  CORS-->>Client: response + CORS headers
  
  Client->>HTTP: GET /api/models (no Origin header)
  HTTP->>CORS: wrapped mux
  CORS->>Handler: forward request
  Handler-->>CORS: response
  CORS-->>Client: response (no CORS headers added)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

enhancement

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: CORS implementation' directly and clearly summarizes the main change—adding CORS support to the codebase using the rs/cors library.
Docstring Coverage ✅ Passed Docstring coverage is 88.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/cors-impl

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/content/configuration/overview.md`:
- Around line 150-158: Replace the fenced YAML code block with an indented code
block to satisfy MD046: remove the triple-backtick fence and indent each line of
the YAML snippet (starting with "server:" and including nested "cors:",
"enabled:", "allowed_origins:", etc.) by at least four spaces so the YAML
remains formatted as an indented code block within
docs/content/configuration/overview.md.

In `@internal/config/types.go`:
- Around line 75-100: The CorsConfig.Validate method correctly enforces that
Enabled CORS cannot have an empty AllowedOrigins (to match rs/cors treating
nil/empty as allow-all) and checks MaxAge and AllowCredentials vs
AllowedOrigins; keep these guards but add a short note to project docs and CI:
update README or docs to state the minimum supported Go version (matching
dependencies like rs/cors v1.11.1 which declares go 1.23) and either add CI jobs
to run tests/build on Go 1.24 or explicitly document that Go 1.23 is the minimum
supported runtime; reference CorsConfig.Validate, AllowedOrigins,
AllowCredentials, and MaxAge when adding the documentation/CI change so
reviewers can verify the compatibility rationale.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ffe38626-58f9-41f9-adc3-068bff46761f

📥 Commits

Reviewing files that changed from the base of the PR and between f09b5c9 and 2000207.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • CLAUDE.md
  • config/config.yaml
  • docs/content/api-reference/overview.md
  • docs/content/configuration/overview.md
  • docs/content/configuration/practices/security.md
  • docs/content/configuration/reference.md
  • docs/content/faq.md
  • docs/content/index.md
  • docs/content/integrations/frontend/openwebui.md
  • go.mod
  • internal/app/middleware/cors.go
  • internal/app/middleware/cors_test.go
  • internal/app/services/http.go
  • internal/app/services/http_cors_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/config/types.go
  • readme.md

Comment on lines +150 to +158
```yaml
server:
cors:
enabled: true
allowed_origins:
- "https://my-dashboard.example.com"
allow_credentials: true
max_age: 600
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use indented code block style to satisfy markdownlint (MD046).

This fenced YAML block breaks the configured code-block-style rule and may keep docs lint noisy or failing. Convert this block to indented style to match the repository convention.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 150-150: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/content/configuration/overview.md` around lines 150 - 158, Replace the
fenced YAML code block with an indented code block to satisfy MD046: remove the
triple-backtick fence and indent each line of the YAML snippet (starting with
"server:" and including nested "cors:", "enabled:", "allowed_origins:", etc.) by
at least four spaces so the YAML remains formatted as an indented code block
within docs/content/configuration/overview.md.

Source: Linters/SAST tools

Comment thread internal/config/types.go
Comment on lines +75 to +100
// Validate checks CORS configuration for spec-violating combinations that would
// silently break browser clients at runtime rather than failing loudly at startup.
func (c *CorsConfig) Validate() error {
if !c.Enabled {
return nil
}
// rs/cors treats an empty AllowedOrigins list as allow-all, which is the
// opposite of what an operator expects when they write allowed_origins: [].
// Require an explicit ["*"] to opt into allow-all so the intent is unambiguous.
if len(c.AllowedOrigins) == 0 {
return errors.New("cors: allowed_origins must not be empty when cors is enabled; set [\"*\"] to allow all origins explicitly")
}
if c.MaxAge < 0 {
return fmt.Errorf("cors.max_age must be non-negative, got %d", c.MaxAge)
}
// The CORS spec forbids Access-Control-Allow-Origin: * when credentials are in play.
// Browsers will refuse the response, so this is a startup error rather than a warning.
if c.AllowCredentials {
for _, origin := range c.AllowedOrigins {
if origin == "*" {
return errors.New("cors: allow_credentials=true is incompatible with allowed_origins=[\"*\"]; list explicit origins instead")
}
}
}
return nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check rs/cors documentation for AllowedOrigins behavior and Go version support

# Verify the current version and its Go compatibility
gh api repos/rs/cors/tags --jq '.[] | select(.name == "v1.11.1") | .name'

# Check go.mod in the library for minimum Go version
gh api repos/rs/cors/contents/go.mod --jq '.content' | base64 -d | grep '^go '

Repository: thushan/olla

Length of output: 74


🌐 Web query:

rs/cors library empty AllowedOrigins behavior

💡 Result:

In the rs/cors Go library, the behavior of an empty AllowedOrigins slice depends on the version being used, though the documented default is to allow all origins [1][2]. Historically, if AllowedOrigins was left empty or nil, the library treated this as a request to allow all origins (equivalent to [""]) [3][4]. However, users should consult the documentation for their specific version. While the configuration field is defined as a []string, modern best practices and the library's official documentation explicitly state that the default value is [""] [1][2]. If you intend to restrict origins, you must explicitly provide a non-empty slice of allowed origins [1]. If you require dynamic validation, you should use the AllowOriginFunc, AllowOriginRequestFunc, or AllowOriginVaryRequestFunc, which take precedence over the AllowedOrigins configuration [1][5][2].

Citations:


rs/cors behaviour matches — keep the empty-origins guard; clarify Go 1.24 support

  • rs/cors (v1.11.1) treats an empty/nil AllowedOrigins as allow-all (equivalent to ["*"]), so returning an error when cors is enabled with allowed_origins: [] aligns with rs/cors runtime behaviour.
  • rs/cors v1.11.1 declares go 1.23.0 in its go.mod; if Go 1.24 is a supported target, add CI/build coverage (or document the minimum Go version).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/config/types.go` around lines 75 - 100, The CorsConfig.Validate
method correctly enforces that Enabled CORS cannot have an empty AllowedOrigins
(to match rs/cors treating nil/empty as allow-all) and checks MaxAge and
AllowCredentials vs AllowedOrigins; keep these guards but add a short note to
project docs and CI: update README or docs to state the minimum supported Go
version (matching dependencies like rs/cors v1.11.1 which declares go 1.23) and
either add CI jobs to run tests/build on Go 1.24 or explicitly document that Go
1.23 is the minimum supported runtime; reference CorsConfig.Validate,
AllowedOrigins, AllowCredentials, and MaxAge when adding the documentation/CI
change so reviewers can verify the compatibility rationale.

@thushan
thushan merged commit b19ba57 into main Jun 6, 2026
8 checks passed
@thushan
thushan deleted the feature/cors-impl branch June 6, 2026 00:07
@petersimmons1972 petersimmons1972 mentioned this pull request Jun 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

security security related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant