Skip to content

[Feature]: Path policies completely replace global configuration instead of merging/inheriting #3

Description

@michelposseth-droid

Problem Statement

SafeWebCore path policies currently replace the entire global configuration object when a path match occurs.

This behavior is non-obvious and creates a security footgun: users may assume a path-specific policy only overrides the settings they explicitly configure, while in reality all unspecified values silently fall back to library defaults.

This can unintentionally weaken security headers on specific endpoints.

Current Behavior
`options.HstsValue = "max-age=63072000; includeSubDomains; preload";

options.PathPolicy("/api", api =>
{
api.XFrameOptions = "DENY";
});`

A developer might reasonably expect /api to inherit the global HSTS configuration and only override X-Frame-Options.

Instead, the /api policy receives a fresh configuration object. Any values not explicitly redefined fall back to SafeWebCore defaults.

As a result:

Strict-Transport-Security: max-age=31536000

may be emitted on /api instead of the globally configured:

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

Security Impact

This can lead to unintentional header downgrades.

A concrete example is HSTS:

HSTS is host-scoped.
Browsers continuously update the remembered HSTS policy from received responses.
If a path policy emits a weaker Strict-Transport-Security header than the rest of the application, browsers may update the stored policy to the weaker value.
Developers may therefore weaken effective domain-wide HSTS protection without realizing it.

The same risk applies to other security-related settings when teams assume inheritance semantics.

Why This Is Surprising

Most configuration systems follow one of these patterns:

Override only explicitly specified values.
Support inheritance from a base policy.
Offer a merge mechanism between global and path-specific configuration.

SafeWebCore currently appears to do neither.

Because ApplyPreset(...) is internal and there is no public inheritance mechanism, consumers must manually duplicate all desired settings in every path policy.

This is error-prone and difficult to maintain.

Proposed Solution

Suggested Improvements
Option 1: Merge with global configuration

Have path policies start from a clone of the global configuration and allow overriding specific settings.

options.PathPolicy("/api", api => { api.ContentSecurityPolicy = null; });
Everything else would inherit automatically.

Option 2: Public inheritance API

Expose something like:

options.PathPolicy("/api", api => { api.ApplyPreset(options); });

or

options.PathPolicy("/api", api => { api.InheritGlobal(); });

before applying overrides

Option 3: Documentation

At minimum, document prominently that:

Path policies replace the global configuration completely and do not inherit or merge settings.

This should be highlighted in both documentation and examples because it is not the behavior many users will expect.

Reproduction
Configure a custom global HSTS value.
Configure a path policy that only changes a single setting.
Request a matching endpoint.
Observe that unspecified settings revert to SafeWebCore defaults rather than inheriting the global configuration.
Expected Behavior

Path policies should either:

inherit from the global policy by default, or
expose an official inheritance/merge mechanism, or
clearly document that replacement semantics are used.
Additional Context

Verified against SafeWebCore 1.6.0 using reflection and live endpoint testing.

The behavior is currently worked around by maintaining a custom helper that manually copies all desired settings into every path-specific policy. This avoids accidental security regressions but introduces configuration duplication and maintenance overhead.

Alternatives Considered

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions