Skip to content

feat(cors): configurable allowed headers, credentials, maxAge, and wildcard subdomain origins#299

Merged
intel352 merged 6 commits intomainfrom
copilot/add-cors-configurable-headers-origins
Mar 12, 2026
Merged

feat(cors): configurable allowed headers, credentials, maxAge, and wildcard subdomain origins#299
intel352 merged 6 commits intomainfrom
copilot/add-cors-configurable-headers-origins

Conversation

Copy link
Contributor

Copilot AI commented Mar 11, 2026

http.middleware.cors had hardcoded Access-Control-Allow-Headers: Content-Type, Authorization with no support for allowCredentials, maxAge, or wildcard subdomain origin matching — blocking real-world browser apps that send CSRF tokens, cookies, or custom headers.

Changes

  • module/http_middleware.go

    • New CORSMiddlewareConfig struct (AllowedOrigins, AllowedMethods, AllowedHeaders, AllowCredentials, MaxAge)
    • New NewCORSMiddlewareWithConfig(name, CORSMiddlewareConfig) constructor; NewCORSMiddleware delegates to it (backward compat: defaults headers to [Content-Type, Authorization])
    • corsOriginAllowed helper: supports "*", exact match, and "*.example.com" wildcard subdomain patterns
    • Process: uses configurable headers, emits Access-Control-Allow-Credentials: true and Access-Control-Max-Age when configured
  • plugins/http/modules.go

    • corsMiddlewareFactory parses new config keys: allowedHeaders, allowCredentials, maxAge

Usage

modules:
  - name: cors-mw
    type: http.middleware.cors
    config:
      allowedOrigins:
        - "*.example.com"
        - "https://trusted.io"
      allowedMethods: [GET, POST, PUT, DELETE, OPTIONS]
      allowedHeaders:
        - Authorization
        - Content-Type
        - X-CSRF-Token
        - X-Request-Id
      allowCredentials: true
      maxAge: 3600

Wildcard subdomain matching (*.example.com) matches any direct subdomain. When allowCredentials: true, the actual request Origin is always reflected in the response (never *).

Original prompt

This section details on the original issue you should resolve

<issue_title>http.server CORS middleware: support configurable allowed headers and origins</issue_title>
<issue_description>## Problem

The built-in CORS middleware (http.middleware.cors) only allows a fixed set of headers (Content-Type, Authorization). Real-world browser applications send additional headers that are required for security and functionality:

  • X-CSRF-Token / X-CSRFToken / x-csrftoken — CSRF protection
  • X-Request-Id — distributed tracing correlation
  • Cookie — session-based authentication
  • Custom headers like X-Affiliate-Id — multi-tenant routing

When the built-in middleware encounters these headers, the browser's preflight OPTIONS request fails, blocking the actual request entirely. The middleware also doesn't support configuring allowed origins — it either allows all or none.

Use Case

Any application where the workflow engine serves as a backend for browser-based frontends, particularly:

  1. API gateways / BFF proxies: A facade service proxies browser requests to backend APIs. The browser sends CSRF tokens, cookies, and custom headers. The proxy must reflect all of these in Access-Control-Allow-Headers.

  2. Multi-origin deployments: The same API serves multiple frontend applications (e.g., admin portal on admin.example.com and customer portal on app.example.com). CORS must allow specific origins, not just *.

  3. Security-sensitive applications: Wildcard Access-Control-Allow-Origin: * is incompatible with credentials: include (cookies). The middleware must reflect the requesting origin with Access-Control-Allow-Credentials: true.

Proposed Solution

Make the CORS middleware fully configurable via YAML on the http.server or http.router module:

modules:
  - name: server
    type: http.server
    config:
      port: 8080
      cors:
        enabled: true
        allowedOrigins:
          - "https://app.example.com"
          - "https://admin.example.com"
          - "*.example.com"           # wildcard subdomain matching
        allowedMethods: [GET, POST, PUT, PATCH, DELETE, OPTIONS]
        allowedHeaders:
          - Authorization
          - Content-Type
          - Cookie
          - X-CSRF-Token
          - X-Request-Id
          - X-Affiliate-Id
        allowCredentials: true
        maxAge: 3600

Key behaviors:

  • Origin reflection: When allowCredentials: true, respond with the actual Origin header value (not *) if it matches the allowed list
  • Header passthrough: All configured headers included in Access-Control-Allow-Headers
  • Preflight handling: Automatically respond to OPTIONS with 200 and appropriate headers
  • Defaults: If allowedHeaders is omitted, default to [Content-Type, Authorization] for backward compatibility

Current Workaround

Consumers must implement a custom CORS middleware struct in Go, discover the HTTP router via the service registry, and inject it as global middleware:

type facadeCORS struct{ allowedHeaders string }

func (c *facadeCORS) Process(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        origin := r.Header.Get("Origin")
        if origin != "" {
            w.Header().Set("Access-Control-Allow-Origin", origin)
            w.Header().Set("Access-Control-Allow-Headers", c.allowedHeaders)
            w.Header().Set("Access-Control-Allow-Credentials", "true")
            // ...
        }
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusOK)
            return
        }
        next.ServeHTTP(w, r)
    })
}

This requires Go code for what should be a pure configuration concern.</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…dcard subdomain origins

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Copilot AI changed the title [WIP] Add configurable allowed headers and origins to CORS middleware feat(cors): configurable allowed headers, credentials, maxAge, and wildcard subdomain origins Mar 11, 2026
@intel352 intel352 marked this pull request as ready for review March 11, 2026 23:15
Copilot AI review requested due to automatic review settings March 11, 2026 23:15
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR expands the HTTP CORS middleware to support additional configuration (allowed headers, credentials, max-age, and wildcard subdomain origins) and updates unit/e2e tests accordingly, along with some minor formatting-only cleanup in unrelated module tests/types.

Changes:

  • Extend CORSMiddleware to accept a full config object (allowedHeaders, allowCredentials, maxAge) and add wildcard subdomain matching for origins.
  • Update the HTTP plugin CORS middleware factory to build and pass the new CORS config.
  • Add unit + e2e coverage for the new CORS behaviors.

Reviewed changes

Copilot reviewed 4 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
plugins/http/modules.go Updates CORS middleware factory to build CORSMiddlewareConfig and pass headers/credentials/maxAge.
module/http_middleware.go Adds CORSMiddlewareConfig, wildcard origin matching, configurable allowed headers, credentials, and max-age response header support.
module/http_middleware_test.go Adds unit tests covering allowedHeaders defaults, allowCredentials, maxAge, and wildcard subdomains.
e2e_middleware_test.go Adds an end-to-end test verifying factory-driven full CORS config behavior.
module/scan_provider_test.go Formatting-only changes (gofmt alignment).
module/platform_do_database.go Formatting-only changes (comment alignment).
module/platform_do_database_test.go Formatting-only changes (map literal alignment).

Comment on lines +389 to +400
func corsOriginAllowed(origin string, allowedOrigins []string) bool {
for _, allowed := range allowedOrigins {
if allowed == "*" || allowed == origin {
return true
}
// Wildcard subdomain matching: "*.example.com" matches "sub.example.com"
if strings.HasPrefix(allowed, "*.") {
suffix := allowed[1:] // ".example.com"
if strings.HasSuffix(origin, suffix) {
return true
}
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

corsOriginAllowed does suffix matching against the full Origin header value (e.g. http://app.example.com:3000). This means patterns like *.example.com will not match origins that include a port, and the logic isn't robust to URL parsing/normalization. Consider parsing Origin with url.Parse and doing wildcard checks against u.Hostname() (and optionally validating scheme/port), while still using the full origin string for the exact-match case.

Copilot uses AI. Check for mistakes.
Comment on lines 408 to +416
origin := r.Header.Get("Origin")

// Check if origin is allowed
allowed := false
for _, allowedOrigin := range m.allowedOrigins {
if allowedOrigin == "*" || allowedOrigin == origin {
allowed = true
break
}
}
allowed := corsOriginAllowed(origin, m.allowedOrigins)

if allowed {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Methods", strings.Join(m.allowedMethods, ", "))
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
w.Header().Set("Access-Control-Allow-Headers", strings.Join(m.allowedHeaders, ", "))
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

corsOriginAllowed returns true for allowedOrigins containing "*" even when the request has no Origin header (i.e., origin == ""). In that case the middleware sets Access-Control-Allow-Origin to an empty value, which is an invalid CORS response. Consider short-circuiting CORS handling when Origin is empty (only apply CORS headers when the request includes an Origin header).

Copilot uses AI. Check for mistakes.
corsCfg.AllowCredentials = allowCreds
}
if maxAge, ok := cfg["maxAge"].(int); ok {
corsCfg.MaxAge = maxAge
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

maxAge is only read when the config value is an int. Elsewhere in this file you accept both int and float64 for numeric config values (e.g. cacheMaxAge, burstSize, requestsPerMinute), and other parts of the repo parse maxAge as float64 (e.g. plugins/api/plugin.go). Consider accepting float64 here too (and casting to int) so JSON-decoded / UI-provided configs work consistently.

Suggested change
corsCfg.MaxAge = maxAge
corsCfg.MaxAge = maxAge
} else if maxAgeFloat, ok := cfg["maxAge"].(float64); ok {
corsCfg.MaxAge = int(maxAgeFloat)

Copilot uses AI. Check for mistakes.
Comment on lines 175 to 180
func corsMiddlewareFactory(name string, cfg map[string]any) modular.Module {
allowedOrigins := []string{"*"}
allowedMethods := []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}
corsCfg := module.CORSMiddlewareConfig{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
}
if origins, ok := cfg["allowedOrigins"].([]any); ok {
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

This factory now supports allowedHeaders, allowCredentials, and maxAge, but the HTTP plugin’s module schema for http.middleware.cors still only lists allowedOrigins/allowedMethods (see plugins/http/schemas.go). Because the generated workflow JSON Schema sets config.additionalProperties=false per module type, these new keys won’t be represented/allowed in generated schemas and editor tooling. Please update the CORS module schema fields/defaults to include the new config options (and ensure any related docs/UI schema stay in sync).

Copilot uses AI. Check for mistakes.
@intel352
Copy link
Contributor

@copilot apply changes based on the comments in this thread

Build failing, lint failing, test failing, validate configs failing

@github-actions
Copy link

github-actions bot commented Mar 12, 2026

⏱ Benchmark Results

No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:244: parsing iteration count: invalid syntax
baseline-bench.txt:306617: parsing iteration count: invalid syntax
baseline-bench.txt:596433: parsing iteration count: invalid syntax
baseline-bench.txt:884572: parsing iteration count: invalid syntax
baseline-bench.txt:1186922: parsing iteration count: invalid syntax
baseline-bench.txt:1482537: parsing iteration count: invalid syntax
benchmark-results.txt:244: parsing iteration count: invalid syntax
benchmark-results.txt:318065: parsing iteration count: invalid syntax
benchmark-results.txt:586782: parsing iteration count: invalid syntax
benchmark-results.txt:886771: parsing iteration count: invalid syntax
benchmark-results.txt:1185641: parsing iteration count: invalid syntax
benchmark-results.txt:1461486: parsing iteration count: invalid syntax
goos: linux
goarch: amd64
pkg: github.com/GoCodeAlone/workflow/dynamic
cpu: AMD EPYC 7763 64-Core Processor                
                            │ baseline-bench.txt │       benchmark-results.txt        │
                            │       sec/op       │    sec/op     vs base              │
InterpreterCreation-4               7.200m ± 58%   5.865m ± 74%       ~ (p=0.818 n=6)
ComponentLoad-4                     3.483m ±  5%   3.439m ±  9%       ~ (p=0.180 n=6)
ComponentExecute-4                  2.026µ ±  2%   1.997µ ±  2%  -1.46% (p=0.039 n=6)
PoolContention/workers-1-4          1.103µ ±  3%   1.083µ ±  3%  -1.86% (p=0.041 n=6)
PoolContention/workers-2-4          1.092µ ±  1%   1.084µ ±  1%  -0.78% (p=0.009 n=6)
PoolContention/workers-4-4          1.102µ ±  1%   1.080µ ±  1%  -2.04% (p=0.002 n=6)
PoolContention/workers-8-4          1.098µ ±  2%   1.084µ ±  1%  -1.28% (p=0.004 n=6)
PoolContention/workers-16-4         1.095µ ±  2%   1.104µ ±  2%       ~ (p=0.485 n=6)
ComponentLifecycle-4                3.455m ±  0%   3.449m ±  1%       ~ (p=0.394 n=6)
SourceValidation-4                  2.284µ ±  1%   2.267µ ±  1%  -0.74% (p=0.009 n=6)
RegistryConcurrent-4                771.9n ±  3%   765.9n ±  5%       ~ (p=0.485 n=6)
LoaderLoadFromString-4              3.468m ±  0%   3.484m ±  0%  +0.45% (p=0.004 n=6)
geomean                             18.60µ         18.14µ        -2.44%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               1.944Mi ± 0%   1.944Mi ± 0%       ~ (p=0.926 n=6)
ComponentLoad-4                     2.097Mi ± 0%   2.097Mi ± 0%       ~ (p=0.818 n=6)
ComponentExecute-4                  1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-1-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-2-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-4-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-8-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-16-4         1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
ComponentLifecycle-4                2.099Mi ± 0%   2.099Mi ± 0%       ~ (p=0.788 n=6)
SourceValidation-4                  1.984Ki ± 0%   1.984Ki ± 0%       ~ (p=1.000 n=6) ¹
RegistryConcurrent-4                1.133Ki ± 0%   1.133Ki ± 0%       ~ (p=1.000 n=6) ¹
LoaderLoadFromString-4              2.099Mi ± 0%   2.099Mi ± 0%       ~ (p=0.900 n=6)
geomean                             15.05Ki        15.05Ki       -0.00%
¹ all samples are equal

                            │ baseline-bench.txt │        benchmark-results.txt        │
                            │     allocs/op      │  allocs/op   vs base                │
InterpreterCreation-4                15.09k ± 0%   15.09k ± 0%       ~ (p=1.000 n=6)
ComponentLoad-4                      17.43k ± 0%   17.43k ± 0%       ~ (p=1.000 n=6)
ComponentExecute-4                    25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-1-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-2-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-4-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-8-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-16-4           25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
ComponentLifecycle-4                 17.48k ± 0%   17.48k ± 0%       ~ (p=1.000 n=6) ¹
SourceValidation-4                    32.00 ± 0%    32.00 ± 0%       ~ (p=1.000 n=6) ¹
RegistryConcurrent-4                  2.000 ± 0%    2.000 ± 0%       ~ (p=1.000 n=6) ¹
LoaderLoadFromString-4               17.47k ± 0%   17.47k ± 0%       ~ (p=1.000 n=6) ¹
geomean                               181.2         181.2       +0.00%
¹ all samples are equal

pkg: github.com/GoCodeAlone/workflow/middleware
                                  │ baseline-bench.txt │       benchmark-results.txt       │
                                  │       sec/op       │   sec/op     vs base              │
CircuitBreakerDetection-4                  287.2n ± 4%   286.0n ± 4%       ~ (p=0.485 n=6)
CircuitBreakerExecution_Success-4          22.53n ± 0%   22.47n ± 1%       ~ (p=0.061 n=6)
CircuitBreakerExecution_Failure-4          65.38n ± 0%   65.06n ± 6%       ~ (p=0.063 n=6)
geomean                                    75.06n        74.78n       -0.37%

                                  │ baseline-bench.txt │       benchmark-results.txt        │
                                  │        B/op        │    B/op     vs base                │
CircuitBreakerDetection-4                 144.0 ± 0%     144.0 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Success-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Failure-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                              ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                  │ baseline-bench.txt │       benchmark-results.txt        │
                                  │     allocs/op      │ allocs/op   vs base                │
CircuitBreakerDetection-4                 1.000 ± 0%     1.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Success-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Failure-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                              ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/module
                                 │ baseline-bench.txt │       benchmark-results.txt        │
                                 │       sec/op       │    sec/op     vs base              │
JQTransform_Simple-4                     914.2n ± 22%   880.2n ± 27%       ~ (p=0.727 n=6)
JQTransform_ObjectConstruction-4         1.455µ ±  0%   1.465µ ±  1%       ~ (p=0.069 n=6)
JQTransform_ArraySelect-4                3.297µ ±  1%   3.323µ ±  0%  +0.80% (p=0.002 n=6)
JQTransform_Complex-4                    37.83µ ±  1%   38.17µ ±  0%  +0.92% (p=0.015 n=6)
JQTransform_Throughput-4                 1.798µ ±  1%   1.826µ ±  1%  +1.53% (p=0.002 n=6)
SSEPublishDelivery-4                     66.86n ±  0%   65.42n ±  0%  -2.15% (p=0.002 n=6)
geomean                                  1.647µ         1.641µ        -0.35%

                                 │ baseline-bench.txt │        benchmark-results.txt         │
                                 │        B/op        │     B/op      vs base                │
JQTransform_Simple-4                   1.273Ki ± 0%     1.273Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ObjectConstruction-4       1.773Ki ± 0%     1.773Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ArraySelect-4              2.625Ki ± 0%     2.625Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Complex-4                  16.22Ki ± 0%     16.22Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Throughput-4               1.984Ki ± 0%     1.984Ki ± 0%       ~ (p=1.000 n=6) ¹
SSEPublishDelivery-4                     0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                             ²                 +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                 │ baseline-bench.txt │       benchmark-results.txt        │
                                 │     allocs/op      │ allocs/op   vs base                │
JQTransform_Simple-4                     10.00 ± 0%     10.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ObjectConstruction-4         15.00 ± 0%     15.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ArraySelect-4                30.00 ± 0%     30.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Complex-4                    324.0 ± 0%     324.0 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Throughput-4                 17.00 ± 0%     17.00 ± 0%       ~ (p=1.000 n=6) ¹
SSEPublishDelivery-4                     0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                             ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/schema
                                    │ baseline-bench.txt │       benchmark-results.txt       │
                                    │       sec/op       │   sec/op     vs base              │
SchemaValidation_Simple-4                    1.124µ ± 3%   1.115µ ± 6%       ~ (p=1.000 n=6)
SchemaValidation_AllFields-4                 1.690µ ± 6%   1.690µ ± 2%       ~ (p=0.786 n=6)
SchemaValidation_FormatValidation-4          1.599µ ± 1%   1.595µ ± 1%       ~ (p=0.784 n=6)
SchemaValidation_ManySchemas-4               1.820µ ± 3%   1.829µ ± 3%       ~ (p=0.976 n=6)
geomean                                      1.533µ        1.531µ       -0.14%

                                    │ baseline-bench.txt │       benchmark-results.txt        │
                                    │        B/op        │    B/op     vs base                │
SchemaValidation_Simple-4                   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_AllFields-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_FormatValidation-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_ManySchemas-4              0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                    │ baseline-bench.txt │       benchmark-results.txt        │
                                    │     allocs/op      │ allocs/op   vs base                │
SchemaValidation_Simple-4                   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_AllFields-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_FormatValidation-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_ManySchemas-4              0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/store
                                   │ baseline-bench.txt │       benchmark-results.txt        │
                                   │       sec/op       │    sec/op     vs base              │
EventStoreAppend_InMemory-4                1.358µ ± 22%   1.224µ ± 13%       ~ (p=0.310 n=6)
EventStoreAppend_SQLite-4                  1.564m ±  9%   1.586m ±  4%       ~ (p=0.699 n=6)
GetTimeline_InMemory/events-10-4           14.03µ ±  2%   14.09µ ±  2%       ~ (p=0.665 n=6)
GetTimeline_InMemory/events-50-4           70.13µ ± 14%   74.58µ ± 16%       ~ (p=0.589 n=6)
GetTimeline_InMemory/events-100-4          123.9µ ±  0%   125.2µ ±  1%  +0.99% (p=0.002 n=6)
GetTimeline_InMemory/events-500-4          638.7µ ±  1%   647.3µ ±  0%  +1.34% (p=0.002 n=6)
GetTimeline_InMemory/events-1000-4         1.304m ±  0%   1.341m ±  1%  +2.83% (p=0.002 n=6)
GetTimeline_SQLite/events-10-4             112.0µ ±  1%   111.4µ ±  1%       ~ (p=0.065 n=6)
GetTimeline_SQLite/events-50-4             253.9µ ±  7%   254.1µ ±  1%       ~ (p=1.000 n=6)
GetTimeline_SQLite/events-100-4            427.8µ ±  1%   428.0µ ±  1%       ~ (p=0.937 n=6)
GetTimeline_SQLite/events-500-4            1.798m ±  0%   1.821m ±  0%  +1.25% (p=0.002 n=6)
GetTimeline_SQLite/events-1000-4           3.516m ±  1%   3.562m ±  2%  +1.31% (p=0.026 n=6)
geomean                                    224.7µ         225.6µ        +0.40%

                                   │ baseline-bench.txt │        benchmark-results.txt         │
                                   │        B/op        │     B/op      vs base                │
EventStoreAppend_InMemory-4                  782.5 ± 8%     794.5 ± 8%       ~ (p=0.667 n=6)
EventStoreAppend_SQLite-4                  1.981Ki ± 2%   1.986Ki ± 3%       ~ (p=0.485 n=6)
GetTimeline_InMemory/events-10-4           7.953Ki ± 0%   7.953Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-50-4           46.62Ki ± 0%   46.62Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-100-4          94.48Ki ± 0%   94.48Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-500-4          472.8Ki ± 0%   472.8Ki ± 0%       ~ (p=0.545 n=6)
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%   944.3Ki ± 0%       ~ (p=0.675 n=6)
GetTimeline_SQLite/events-10-4             16.74Ki ± 0%   16.74Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4             87.14Ki ± 0%   87.14Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4            175.4Ki ± 0%   175.4Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-500-4            846.1Ki ± 0%   846.1Ki ± 0%       ~ (p=0.636 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ± 0%  -0.00% (p=0.045 n=6)
geomean                                    67.28Ki        67.38Ki       +0.15%
¹ all samples are equal

                                   │ baseline-bench.txt │        benchmark-results.txt        │
                                   │     allocs/op      │  allocs/op   vs base                │
EventStoreAppend_InMemory-4                  7.000 ± 0%    7.000 ± 0%       ~ (p=1.000 n=6) ¹
EventStoreAppend_SQLite-4                    53.00 ± 0%    53.00 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-10-4             125.0 ± 0%    125.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-50-4             653.0 ± 0%    653.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-100-4           1.306k ± 0%   1.306k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-500-4           6.514k ± 0%   6.514k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-1000-4          13.02k ± 0%   13.02k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-10-4               382.0 ± 0%    382.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4              1.852k ± 0%   1.852k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4             3.681k ± 0%   3.681k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-500-4             18.54k ± 0%   18.54k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-1000-4            37.29k ± 0%   37.29k ± 0%       ~ (p=1.000 n=6) ¹
geomean                                     1.162k        1.162k       +0.00%
¹ all samples are equal

Benchmarks run with go test -bench=. -benchmem -count=6.
Regressions ≥ 20% are flagged. Results compared via benchstat.

…gin guard, port-aware wildcard, float64 maxAge, schema update

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 12, 2026

@copilot apply changes based on the comments in this thread

Build failing, lint failing, test fail...

All review comments addressed in commit 1eb012b:

  • Port-aware wildcard matching: corsOriginAllowed now uses url.Parse to extract hostname before suffix matching, so *.example.com correctly matches http://app.example.com:3000
  • Empty origin guard: CORS headers are only set when Origin header is present and non-empty, preventing empty Access-Control-Allow-Origin responses
  • Vary: Origin header: Added whenever CORS headers are set dynamically, preventing cache poisoning
  • maxAge float64 support: Factory now accepts both int and float64 for maxAge, consistent with other numeric config values in the file
  • Schema updated: plugins/http/schemas.go CORS schema now includes allowedHeaders, allowCredentials, and maxAge fields with proper defaults
  • go mod tidy: Fixed the example directory go.mod that was causing build failures

@intel352 intel352 merged commit abf649a into main Mar 12, 2026
11 checks passed
@intel352 intel352 deleted the copilot/add-cors-configurable-headers-origins branch March 12, 2026 04:14
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.

http.server CORS middleware: support configurable allowed headers and origins

3 participants