RuleGate Security Model

August 1, 2026 · View on GitHub

This guide describes the runtime and integration security model of RuleGate.

It explains:

  • The backend authorization boundary
  • Default-deny and fail-closed behavior
  • Requirement outcomes
  • Identity, resource, and context trust boundaries
  • Manifest security
  • ASP.NET Core policy and HTTP response behavior
  • Diagnostics and audit boundaries
  • Custom extension responsibilities
  • Cancellation and exception behavior
  • Production deployment and testing guidance

This document explains secure RuleGate usage. It is not a vulnerability reporting policy.

For concrete ASP.NET Core APIs, read the ASP.NET Core integration guide.

For policy syntax, read the manifest guide.

For diagnostic fields and sinks, read the diagnostics guide.

For client-side route and template APIs, read the Angular SDK guide.

For package-consuming compositions and concrete trust boundaries, read the reference applications guide.

Security boundary

The protected backend operation is the security boundary.

RuleGate authorization must run in a trusted backend process before that operation is allowed to execute.

Client-side checks may:

  • Hide unavailable controls
  • Improve navigation
  • Avoid obviously invalid requests
  • Explain expected access requirements

They cannot:

  • Protect an API
  • Replace backend authorization
  • Make browser state trustworthy
  • Prevent a modified client from sending a request
  • Grant authority based on UI visibility

Every protected operation must enforce authorization on the backend.

Responsibility model

RuleGate evaluates the authorization request supplied by the application.

Authenticated identity
        |
        v
Application mapping
        |
        v
AuthorizationSubject
        |
        +---------------------+
        |                     |
        v                     v
AuthorizationResource   AuthorizationContext
        |                     |
        +----------+----------+
                   |
                   v
          RuleGate policy engine
                   |
                   v
             Allow or deny

RuleGate is responsible for:

  • Exact policy-route lookup
  • Requirement dispatch
  • Built-in requirement evaluation
  • Logical requirement composition
  • Structured failure information
  • Default-deny decisions
  • Optional diagnostics

The host application is responsible for:

  • Authentication
  • Token and issuer validation
  • Claims transformation
  • Subject mapping
  • Domain-resource loading
  • Resource attribute mapping
  • Context attribute mapping
  • Trusted time configuration
  • Public response design
  • Audit logging
  • Deployment and policy provenance

RuleGate cannot determine whether application-supplied authorization data is truthful. It evaluates that data according to the registered policy.

Default deny

RuleGate grants access only when a matching policy exists and its root requirement is satisfied.

No matching policy produces:

RULEGATE_NO_MATCHING_POLICY

and a denied authorization decision.

Conceptually:

No matching policy       -> deny
Satisfied requirement    -> allow
NotSatisfied requirement -> deny
Indeterminate requirement -> deny

An empty policy collection is valid configuration, but every authorization request is denied because no policy can match.

Fail-closed behavior

Fail-closed means incomplete, unsupported, malformed, or unreliable authorization input cannot grant access.

RuleGate denies when:

  • No matching policy exists
  • A required permission is missing
  • A required role is missing
  • A required attribute is missing
  • An attribute comparison returns false
  • An attribute type is unsupported
  • Runtime and policy attribute types differ
  • The operator cannot evaluate the scalar kind
  • No evaluator exists for a requirement type
  • Subject mapping fails
  • Resource mapping fails
  • A required enrichment provider cannot resolve trusted data
  • An enrichment provider throws, returns invalid data, or collides by default
  • Enrichment is cancelled
  • Required endpoint metadata is absent
  • Required route data is absent or empty
  • Policy and mapped resource types differ

Fail-closed behavior does not mean every programming error is converted into a normal deny result.

Unexpected exceptions are discussed separately in Exception behavior.

Requirement outcomes

A requirement evaluation has three possible outcomes.

OutcomeMeaningFinal authorization
SatisfiedThe requirement passedMay allow
NotSatisfiedThe requirement evaluated normally but did not passDeny
IndeterminateThe requirement could not produce a reliable answerDeny

A non-successful requirement result must contain at least one structured failure.

NotSatisfied

NotSatisfied means RuleGate understood the requirement and its inputs, but the required condition was false.

Examples:

  • Permission is absent
  • Role is absent
  • Attribute is absent
  • Attribute value differs from the expected literal
  • A negated child requirement was satisfied

Typical failure codes include:

RULEGATE_MISSING_PERMISSION
RULEGATE_MISSING_ROLE
RULEGATE_ATTRIBUTE_NOT_FOUND
RULEGATE_ATTRIBUTE_COMPARISON_NOT_SATISFIED
RULEGATE_NEGATED_REQUIREMENT_SATISFIED

Indeterminate

Indeterminate means RuleGate cannot safely determine whether the requirement is satisfied.

Examples:

  • No evaluator is registered for the requirement type
  • Runtime attribute type is unsupported
  • Runtime and expected scalar kinds differ
  • The operator does not support the scalar kind

Typical failure codes include:

RULEGATE_REQUIREMENT_EVALUATOR_NOT_FOUND
RULEGATE_ATTRIBUTE_TYPE_NOT_SUPPORTED
RULEGATE_ATTRIBUTE_TYPE_MISMATCH
RULEGATE_ATTRIBUTE_OPERATOR_NOT_SUPPORTED

Indeterminate never grants access.

Do not reinterpret indeterminate as success in application code.

Logical requirement security

Logical requirements preserve fail-closed behavior.

All

all evaluates every child.

The result is:

  1. NotSatisfied when at least one child is not satisfied.
  2. Otherwise Indeterminate when at least one child is indeterminate.
  3. Otherwise Satisfied.

An indeterminate child cannot make all succeed.

Any

any succeeds immediately when one child is satisfied.

When no child succeeds:

  1. The result is Indeterminate when at least one child is indeterminate.
  2. Otherwise the result is NotSatisfied.

This means an indeterminate alternative cannot be treated as a normal false value when no safe alternative succeeds.

Not

not behaves as follows:

Child outcomenot outcome
SatisfiedNotSatisfied
NotSatisfiedSatisfied
IndeterminateIndeterminate

Negation does not turn uncertainty into success.

This is important for rules such as:

not blocked

When RuleGate cannot determine whether the subject is blocked, access remains denied.

Exact matching

RuleGate uses ordinal, case-sensitive matching for security-relevant identifiers.

This includes:

  • Policy identifiers
  • Resource types
  • Actions
  • Roles
  • Permissions
  • Attribute names
  • Dynamic policy-name segments
  • Claim types and mapped claim values

These are different values:

document.read
Document.Read
DOCUMENT.READ

These resource types are also different:

document
Document

RuleGate does not:

  • Convert identifiers to lowercase
  • Trim meaningful values automatically
  • Apply culture-sensitive comparison
  • Guess intended spelling
  • Select a broader policy as fallback

Use stable identifiers and enforce casing conventions in source control and tests.

Policy-route uniqueness

Only one policy may exist for the same resource type and action pair.

(resourceType, action)

Policy IDs must also be unique.

Duplicate IDs or routes are rejected by manifest validation and by the in-memory policy provider.

This prevents registration order from silently deciding which policy wins.

Authentication boundary

RuleGate is an authorization framework. It does not authenticate users.

The application must validate:

  • Token signature
  • Issuer
  • Audience
  • Expiration
  • Authentication scheme
  • Session validity
  • Identity-provider-specific security requirements

Dynamic ASP.NET Core RuleGate policies require an authenticated principal.

An anonymous request is challenged before RuleGate can grant access.

A valid authentication result does not automatically imply authorization.

ClaimsPrincipal trust boundary

The default ASP.NET Core subject factory maps:

  • Subject ID
  • Roles
  • Permissions

Default claim types are:

ValueDefault claim type
Subject IDClaimTypes.NameIdentifier
RoleClaimTypes.Role
Permissionpermission

The configured authentication and claims-transformation pipeline must ensure these claims are trustworthy.

Do not map authorization claims from:

  • Unverified headers
  • Client-controlled form values
  • Unsigned tokens
  • Untrusted query-string values
  • Unvalidated external profile data

Subject identifier rules

The default factory requires exactly one distinct, non-empty subject identifier.

The following conditions fail closed:

  • Subject identifier claim is missing
  • Every subject identifier value is blank
  • Multiple distinct subject identifiers exist

Repeated claims containing the same exact identifier are treated as one identifier.

Example accepted values:

user-42
user-42

Example rejected values:

user-42
user-43

Ambiguous identity must never be resolved by selecting the first claim.

Role and permission mapping

Role and permission claims:

  • Ignore empty or whitespace-only values
  • Remove exact duplicates
  • Preserve case-distinct values
  • Use ordinal comparison

For example:

document.read
Document.Read

remain two distinct permissions.

Ensure the configured role and permission claim types match the identity provider's validated token contract.

Claims are not attributes

The default subject factory does not copy arbitrary claims into AuthorizationSubject.Attributes.

For example, this claim:

department = finance

does not automatically satisfy:

attribute:
  source: subject
  name: department
  operator: equal
  valueType: string
  value: finance

A custom IRuleGateSubjectFactory must explicitly map the trusted claim.

Explicit mapping prevents every incoming claim from silently becoming authorization input.

Custom subject factories

A custom subject factory must:

  • Accept only authenticated and validated identity data
  • Produce one stable subject identifier
  • Normalize provider-specific claims deliberately
  • Avoid granting permissions from display-only claims
  • Map attributes using documented scalar types
  • Reject ambiguous identity
  • Fail closed when required data is missing
  • Be safe for singleton registration
  • Be thread-safe

The ASP.NET Core handler converts ArgumentException and InvalidOperationException thrown during subject mapping into authorization failure.

Other unexpected exception types propagate.

Resource trust boundary

Resource attributes must describe the protected server-side object.

Trusted sources include:

  • A domain entity loaded by the backend
  • A repository result
  • An internal service response with established trust
  • Server-derived ownership or hierarchy information
  • Validated immutable metadata

Avoid treating these as authoritative resource data:

  • Request-body ownership fields
  • Client-supplied status values
  • Hidden form fields
  • Query-string classifications
  • Browser-local state
  • Unverified headers

A client may identify the target resource, but the backend must load and map the authoritative object state.

Default HTTP resource mapping

The default ASP.NET Core resource factory maps:

  • Resource type from RuleGate endpoint metadata
  • Optional resource ID from one configured route value

It does not:

  • Load a domain entity
  • Verify that the entity exists
  • Read resource attributes
  • Validate ownership
  • Query a database
  • Copy arbitrary request values
  • Map request headers into authorization attributes

A route identifier is an identifier, not proof of ownership or access.

Endpoint metadata validation

For endpoint authorization, RuleGate requires metadata matching the current:

  • Resource type
  • Action

The following conditions fail closed:

  • No endpoint is resolved
  • Matching RuleGate metadata is absent
  • Matching metadata disagrees about the resource-ID route key
  • Required route value is missing
  • Required route value is null
  • Required route value converts to an empty string
  • Mapped resource type differs from the policy resource type

The RuleGate engine is not evaluated until subject and resource mapping succeed.

Custom resource factories

A custom IRuleGateAuthorizationResourceFactory may load or map application domain resources.

It must:

  • Use trusted server-side data
  • Preserve endpoint mapping when endpoint helpers are used
  • Return the correct resource type
  • Map stable resource identifiers
  • Avoid trusting client-supplied authorization attributes
  • Reject unsupported resource types
  • Fail closed on incomplete mapping
  • Be thread-safe for singleton registration

Resource type consistency is enforced before the engine is evaluated.

Context trust boundary

AuthorizationContext contains:

  • Evaluation time
  • Optional context attributes

The default ASP.NET Core handler supplies evaluation time from the registered IRuleGateClock.

It does not automatically map:

  • Request headers
  • IP addresses
  • Authentication method
  • Device information
  • Tenant information
  • Network zone
  • Risk score
  • Correlation data

Applications requiring these values must construct trusted context attributes explicitly or supply them through a trusted ASP.NET Core context attribute provider.

ASP.NET Core enrichment trust boundary

The optional ASP.NET Core enrichment pipeline provides standard extension points for trusted subject, resource, and context attributes.

The pipeline does not make request-derived values trustworthy. A provider must validate and normalize data before returning it. Do not directly promote an arbitrary header, query value, route value, remote address, claim, cookie, or device assertion into authorization state.

Providers run sequentially in subject, resource, and context stages. Lower Order values run first; equal values retain dependency-injection registration order. Existing attributes and earlier providers establish the initial precedence.

Duplicate keys fail closed by default. KeepExisting and ReplaceExisting must be selected explicitly and tested as security-relevant precedence rules.

The pipeline fails authorization before engine evaluation when:

  • A provider reports missing required data
  • A provider reports failure
  • A provider throws
  • Cancellation is requested
  • An attribute name is empty or whitespace
  • A provider returns an unsupported attribute value
  • A duplicate key uses the default collision behavior

Provider exception messages, attribute names, and attribute values are not copied into built-in enrichment logs.

For implementation guidance, read the ASP.NET Core enrichment guide.

Time security

Time-based authorization depends on a trustworthy clock.

The default ASP.NET Core registration uses an internal system-backed IRuleGateClock implementation whose GetUtcNow() result comes from system UTC time.

Applications replacing IRuleGateClock must ensure that production time cannot be influenced by an untrusted caller.

A test clock is useful in automated tests but must not accidentally replace the production clock.

Use DateTimeOffset values with explicit offsets.

First-class time requirements read only AuthorizationContext.EvaluationTime. Recurring windows require an explicit time zone, while one-time boundaries require explicit offsets and are normalized to UTC. Starts are inclusive and ends are exclusive. Overnight windows associate their early-morning portion with the preceding configured day.

Time-zone identifiers and daylight-saving rules come from the platform time zone database. Keep production images and hosts updated, and test business boundaries around clock transitions when a selected zone observes daylight saving time.

Authentication-age and MFA-age requirements read canonical DateTimeOffset attributes from the authorization context. A missing timestamp is not satisfied. An incompatible type or a timestamp later than evaluation time is indeterminate. These outcomes deny access and prevent absent or implausible authentication history from granting authorization.

First-class context policies

RuleGate defines canonical context properties for authentication method, request channel, network zone, tenant, organization, trusted-device state, and identity type. This standardizes policy vocabulary; it does not establish trust in the value.

The default ASP.NET Core integration does not derive these properties from headers, forwarded IP values, claims, cookies, or device assertions. Applications must validate the source and explicitly construct AuthorizationContext.Attributes. Missing properties deny the requirement. Never copy client-controlled input into a canonical context attribute merely because its name matches the policy.

Attribute trust boundary

Supported runtime attribute values are:

  • null
  • string
  • bool
  • Integral numeric types
  • decimal
  • DateTimeOffset
  • Homogeneous collections of supported non-null scalar values

Collections cannot be nested, cannot contain null elements, and cannot exceed 256 elements.

Runtime values outside the supported set produce an indeterminate result.

Examples of unsupported runtime inputs include application-specific objects that have not been mapped into a supported value and heterogeneous or nested collections.

RuleGate does not serialize arbitrary objects to create authorization values.

Attribute comparison security

Attribute comparison is strict.

RuleGate does not perform:

  • Culture-sensitive string comparison
  • Implicit string-to-number conversion
  • Implicit string-to-boolean conversion
  • Arbitrary object conversion
  • Culture-sensitive numeric parsing

String matching is ordinal and case-sensitive by default. A policy must explicitly select ordinalIgnoreCase when case-insensitive comparison is required.

Missing and null attributes remain distinct. exists is satisfied for any present key, including a null value. notExists is satisfied only for a missing key. isNull and isNotNull require the key to be present.

A missing required attribute is NotSatisfied.

An unsupported value, type mismatch, or unsupported operator/type combination is Indeterminate.

Both deny access.

Cross-attribute rules

The built-in attribute-comparison evaluator can compare trusted subject, resource, and context attributes directly:

Resource.ownerId equals Subject.id

Both operands are normalized through the same strict authorization-value model. Missing attributes are NotSatisfied; unsupported values and incompatible operand kinds are Indeterminate. Both outcomes deny access.

Do not populate either operand from an untrusted client assertion. Subject attributes must come from validated identity data, resource attributes from authoritative domain data, and context attributes from trusted server-side state.

Diagnostics may identify both operand sources and names for custom sinks, but never contain their resolved values or typed policy literal values. The built-in logging sink omits attribute names.

Dynamic ASP.NET Core policy names

RuleGate dynamic policy names use:

RuleGate:<resource-type>:<action>

Each segment must:

  • Be non-empty
  • Contain no whitespace
  • Contain no : separator

The prefix, resource type, and action are case-sensitive.

Example:

RuleGate:document:read

Owned-prefix protection

Ordinary policy names are delegated to ASP.NET Core's standard policy provider.

Malformed names beginning with the owned prefix:

RuleGate:

do not fall back to ordinary policies.

Examples:

RuleGate:document:
RuleGate::read
RuleGate:document:read:extra

remain unresolved.

This prevents a malformed RuleGate policy reference from silently resolving through a different authorization mechanism.

Manifest security boundary

Treat rulegate.yaml as security-sensitive configuration.

A manifest can change who may perform protected operations.

Protect it with:

  • Source control review
  • Restricted write permissions
  • Deployment provenance
  • Integrity controls
  • Environment promotion rules
  • Automated validation
  • Allowed and denied regression tests

Do not generate production manifests from untrusted runtime input.

Declarative format

RuleGate manifests declare policy data.

They do not execute:

  • Scripts
  • Shell commands
  • Embedded C#
  • Remote code
  • Arbitrary expressions

This reduces the execution surface, but a valid manifest can still create an incorrect authorization policy.

Semantic review remains necessary.

YAML parser hardening

The current loader:

  • Rejects empty content
  • Requires a root object
  • Rejects malformed YAML
  • Rejects duplicate keys
  • Rejects unknown properties
  • Limits YAML recursion to 64 levels
  • Returns structured file and YAML errors
  • Propagates cancellation

These controls prevent ambiguous or unexpectedly deep documents from silently compiling.

They do not replace file-system and deployment security.

Manifest validation

Validation rejects, among other conditions:

  • Unsupported schema versions
  • Missing application metadata
  • Missing policies collection
  • Null policy entries
  • Missing policy identifiers
  • Duplicate policy identifiers
  • Missing resource types
  • Missing actions
  • Duplicate policy routes
  • Missing requirements
  • Multiple requirement kinds in one object
  • Empty logical collections
  • Invalid attribute source tokens
  • Invalid operators
  • Invalid scalar types
  • Missing literal values
  • Invalid operator/type combinations

Use RuleGateManifestCompiler for normal workflows rather than using only the YAML loader.

All-or-nothing compilation

Manifest compilation is atomic from the consumer's perspective.

ResultCompiled policies
SuccessComplete policy collection
Load failureEmpty
Validation failureEmpty

A failed compilation never returns a partial policy set.

Do not:

  • Register policies when IsSuccess is false
  • Continue startup with only successfully parsed entries
  • Silently reuse an unknown stale manifest
  • Ignore validation errors in deployment

A safe startup strategy is to fail application startup or fail deployment when the intended policy set cannot be compiled.

Policy-test fixtures

rulegate test compiles the complete fixture and referenced manifest before it evaluates any request. Fixture validation rejects malformed YAML, duplicate keys and identifiers, unsupported types, missing request boundaries, invalid expectations, and evaluation times without an explicit offset.

Fixtures must contain synthetic or otherwise safe test data. Text and JSON reports include test identifiers, descriptions, outcomes, policy identifiers, and failure codes, but never subject, resource, or context attribute values.

The command evaluates the portable policy model only. It does not execute identity-provider validation, ASP.NET Core enrichment providers, endpoint handlers, or application data access. Continue to test those host trust boundaries separately, and never treat a CLI fixture as a replacement for API enforcement.

Decision explanations and lint output

rulegate explain evaluates one validated policy-test request through the runtime evaluator pipeline. Its report includes structural policy and requirement identifiers, attribute sources and names, outcomes, and failure codes. It excludes subject and resource IDs, roles, permissions, every request or literal value, descriptions, evaluation IDs, and durations.

rulegate lint validates the complete manifest before static analysis. Lint fingerprints may compare policy values inside the local process, but reports only rule codes, manifest paths, and value-free messages. Neither command is a safe HTTP response body; keep their output in trusted developer and CI systems.

See Explain and Lint for the complete rule and redaction contract.

Policy replacement and stale configuration

RuleGate policy sources use immutable snapshots and atomic replacement to avoid windows where:

  • Old and new policies are mixed
  • Only part of the new policy set is active
  • A failed reload removes the last known valid policy unintentionally
  • Different application instances evaluate different policy generations

Every candidate source set is completely loaded, parsed, validated, and checked for duplicate IDs and routes before activation. If any source fails, the candidate is rejected and the last valid snapshot stays active. Before the first successful activation, policy lookup remains empty and authorization denies by default.

YAML file and configuration change monitoring is opt-in. Protect reloadable sources from unauthorized writes and monitor POLICY_SOURCE_*, MANIFEST_*, and POLICY_SNAPSHOT_* diagnostics. Snapshot versions are local process counters; deployments remain responsible for coordinating the same policy generation across multiple instances.

See Policy sources and atomic reload for the complete activation sequence and source contracts.

HTTP authorization results

ASP.NET Core keeps normal authentication and authorization semantics:

SituationFramework result
Anonymous protected requestChallenge
Authenticated denied requestForbid
Allowed requestEndpoint executes

RuleGate's ProblemDetails mapping is opt-in.

Enable it with:

builder.Services
    .AddRuleGate()
    .AddHttpAuthorizationResultMapping()
    .AddPolicies(policies);

Safe public ProblemDetails

The default RuleGate mapping returns generic public identifiers.

Authentication-required code:

RULEGATE_AUTHENTICATION_REQUIRED

Forbidden code:

RULEGATE_ACCESS_FORBIDDEN

The response may contain:

  • Generic type URI
  • Generic title
  • HTTP status
  • Generic detail
  • Public problem code
  • Trace ID

It intentionally excludes:

  • Engine failure codes
  • Policy IDs
  • Requirement IDs
  • Claims
  • Role values
  • Permission values
  • Subject IDs
  • Resource IDs
  • Route values
  • Attribute names
  • Attribute values
  • Requirement trees

Do not serialize AuthorizationDecision.Failures directly to an untrusted client.

Challenge and forbid behavior

The configured authentication scheme performs its challenge or forbid operation before RuleGate writes the optional generic ProblemDetails body.

Authentication headers such as:

WWW-Authenticate

are preserved.

When the authentication handler has already started the response, RuleGate does not replace the response body.

A custom IAuthorizationMiddlewareResultHandler registered earlier is preserved by the opt-in RuleGate registration.

Trace identifiers

The public ProblemDetails response contains a trace identifier.

Treat trace IDs as correlation data, not authorization proof.

Do not allow possession of a trace ID to:

  • Grant access
  • Reveal protected logs
  • Bypass log-access controls
  • Select another user's diagnostic data

Operational tools must still enforce authentication and authorization.

Diagnostics security

Authorization diagnostics are disabled by default.

The built-in logging sink omits:

  • Attribute names
  • Attribute values
  • Subject identifiers
  • Resource identifiers
  • Raw claims
  • Role values
  • Permission values
  • Raw authorization requests
  • Subject attributes
  • Resource attributes
  • Context attributes

It may include:

  • Policy IDs
  • Requirement IDs
  • Failure codes
  • Evaluation outcomes
  • Durations
  • Requirement structure
  • Attribute source

These fields can still reveal application behavior.

Store diagnostics only in trusted operational systems.

See the diagnostics guide for the complete field contract.

Custom diagnostics sinks

A custom sink receives more structured information than the built-in logger emits.

In particular, a requirement diagnostic can contain an attribute name.

A custom sink must decide which fields are safe to export.

It should be:

  • Thread-safe
  • Bounded
  • Non-blocking where practical
  • Protected from untrusted access
  • Careful with retention
  • Careful with cardinality
  • Resilient to destination failures

Diagnostic sink exceptions are isolated and cannot change the authorization decision.

Diagnostics are not an audit trail

Diagnostics describe completed RuleGate evaluations.

They are not guaranteed for:

  • Canceled evaluations
  • Unexpected engine failures
  • Process termination
  • Storage outages
  • Requests failing before authorization
  • Business operations failing after authorization

Diagnostics also omit much of the subject and resource data normally required for compliance records.

Use a separate durable, application-owned audit mechanism.

Telemetry privacy boundary

Built-in OpenTelemetry activities and metrics use closed, low-cardinality outcome categories. They do not include subject IDs, resource IDs, resource types, actions, policy or source names, roles, permissions, claims, attribute names or values, raw diagnostic codes, exception messages, or stack traces.

Policy count is emitted only as a histogram measurement value. It is not a metric dimension. Rejected candidates and unexpected failures use bounded result tags rather than source diagnostics.

The host owns sampling, exporters, collectors, retention, and access control. Custom activity enrichment can invalidate RuleGate's built-in privacy and cardinality guarantees and must be reviewed as security-sensitive code.

Telemetry is operational evidence, not a durable authorization audit trail. See the telemetry, performance, and concurrency guide for the exact signal contract.

Custom requirement evaluators

Custom evaluators extend the trusted computing base.

They must:

  • Evaluate only the declared requirement type
  • Use trusted authorization input
  • Preserve cancellation
  • Return Indeterminate when no reliable answer is possible
  • Return structured failures for non-success outcomes
  • Avoid hidden allow defaults
  • Be deterministic where practical
  • Be thread-safe for singleton registration
  • Avoid unsafe shared mutable state
  • Avoid leaking values through failure messages or logs
  • Avoid slow unbounded work on the request path

An evaluator that cannot prove success must not return Satisfied.

Evaluator registration

Only one evaluator may be registered for one exact requirement definition type.

Duplicate evaluator registration causes construction failure rather than allowing registration order to select an implementation.

A missing evaluator produces:

RULEGATE_REQUIREMENT_EVALUATOR_NOT_FOUND

and an indeterminate result.

Custom policy providers

A custom IPolicyProvider becomes part of the security boundary.

It must:

  • Use exact route matching
  • Preserve cancellation
  • Avoid broader fallback policies
  • Return one authoritative policy per route
  • Be safe under concurrent requests
  • Handle policy refresh atomically
  • Protect remote or persistent policy storage
  • Fail safely when policy data is unavailable

The built-in in-memory provider is immutable after construction.

Exception behavior

RuleGate distinguishes expected authorization failure from unexpected programming or infrastructure failure.

Expected conditions normally produce denied decisions or failed ASP.NET Core authorization.

Examples:

  • No matching policy
  • Missing permission
  • Missing attribute
  • Unsupported attribute type
  • Missing subject ID
  • Missing route value
  • Resource type mismatch

Unexpected exceptions are not always converted into deny decisions.

ASP.NET Core mapping exceptions

The RuleGate ASP.NET Core handler converts these exceptions from subject or resource mapping into failed authorization:

  • ArgumentException
  • InvalidOperationException

This covers the built-in mapping failure contract.

Other exception types from custom mapping code propagate.

Custom factories should use documented expected exception types for invalid authorization input and reserve other exceptions for genuine operational failure.

ASP.NET Core enrichment exceptions

The default enrichment pipeline converts provider exceptions into failed authorization and stops before the engine is invoked. A custom IRuleGateAuthorizationRequestEnricher that throws is also failed closed by the ASP.NET Core handler.

Built-in enrichment diagnostics report only the ProviderException outcome. They do not expose the exception type, message, stack trace, returned attributes, or attribute names.

Applications should still log and monitor infrastructure failures inside the provider at an appropriate trusted boundary. Do not return sensitive exception details through RuleGateAttributeProviderResult.

Engine and evaluator exceptions

Unexpected exceptions from:

  • Policy providers
  • Requirement evaluators
  • Requirement dispatch
  • The authorization engine

propagate to the host application.

They are not silently converted into an ordinary denied decision.

This preserves operational visibility and prevents the protected endpoint from executing successfully.

Applications must handle these failures through their normal:

  • Exception middleware
  • Logging
  • Telemetry
  • Availability strategy
  • Incident response

Do not globally convert every authorization exception into an allowed result.

Diagnostic exceptions

Diagnostic publication is intentionally different.

After a decision is created, exceptions thrown by the diagnostics sink are caught and ignored.

A diagnostic failure cannot:

  • Change allow to deny
  • Change deny to allow
  • Make authorization unavailable
  • Replace the decision

The sink should monitor its own delivery failures when operational visibility is required.

The same isolation applies to IRuleGateEnrichmentDiagnosticsSink.

Cancellation behavior

Core RuleGate APIs honor cancellation tokens supplied by the caller.

Cancellation is checked during:

  • Policy lookup
  • Requirement dispatch
  • Requirement evaluation
  • Manifest file loading

Cancellation propagates as OperationCanceledException.

It is not converted into a deny decision.

ASP.NET Core cancellation boundary

The ASP.NET Core handler forwards HttpContext.RequestAborted to every attribute-enrichment provider and to the authorization engine.

Enrichment checks cancellation before starting each provider. Cancellation during enrichment stops the pipeline and fails authorization before engine evaluation. Providers must forward the token to database, network, and other asynchronous operations.

If cancellation occurs during core engine evaluation, the engine's normal OperationCanceledException behavior applies.

Diagnostic publication cancellation

Once the engine has produced a decision, it publishes the final diagnostic using:

CancellationToken.None

Request cancellation therefore does not cancel the final best-effort diagnostic write.

The built-in logging sink still checks whichever token is supplied directly to it.

Enrichment diagnostic sinks receive the request cancellation token. Their exceptions, including cancellation exceptions, are ignored so diagnostics do not alter authorization behavior.

Availability and denial of service

Authorization runs on the protected request path.

Custom components should avoid:

  • Unbounded recursion
  • Unbounded collections
  • Slow remote calls
  • Blocking I/O
  • Unbounded retries
  • High-cardinality logging
  • Expensive repeated domain queries
  • Large attribute payloads
  • Unbounded diagnostic queues

Manifest YAML recursion is limited, but custom policy providers and evaluators remain application responsibilities.

Extension-point lifetimes

These default RuleGate extension points are singletons and custom implementations must be concurrency-safe:

  • IPolicyProvider
  • IRequirementEvaluator
  • IRuleGateSubjectFactory
  • IRuleGateAuthorizationResourceFactory
  • IAuthorizationDiagnosticsSink
  • IRuleGateEnrichmentDiagnosticsSink
  • IRuleGateClock

Avoid request-specific mutable state in singleton fields.

Use method-local state or concurrency-safe structures.

The ASP.NET Core authorization handler, authorization request enricher, and attribute providers are scoped. The builder registers attribute providers as scoped by default so they can depend on request-scoped application services.

An application may explicitly choose another provider lifetime. A singleton provider must not capture scoped services and must remain safe under concurrent requests.

Secrets and personal data

RuleGate policies usually need identifiers and business attributes, not credentials.

Do not place these values in manifests, diagnostics, or failure details:

  • Passwords
  • Access tokens
  • Refresh tokens
  • API keys
  • Private keys
  • Session cookies
  • Unnecessary personal data
  • Production credentials

Authorization attributes should contain only the minimum data needed for the decision.

Failure-code handling

Internal failure codes are valuable for:

  • Tests
  • Trusted diagnostics
  • Alert grouping
  • Troubleshooting

They may reveal authorization structure.

Do not automatically return them to public clients.

Map internal decisions to generic transport responses and keep detailed failure information inside trusted boundaries.

Deployment guidance

A secure deployment should:

  1. Authenticate before authorization.
  2. Compile and validate the complete manifest.
  3. Refuse invalid policy configuration.
  4. Register the complete policy collection atomically.
  5. Protect manifest write access.
  6. Record policy and application versions.
  7. Use consistent identifiers and casing.
  8. Configure trusted subject claims.
  9. Map resources from authoritative server data.
  10. Keep context attributes minimal and trusted.
  11. Use generic public authorization responses.
  12. Restrict diagnostics and logs.
  13. Maintain a separate audit trail.
  14. Test allowed, denied, malformed, and indeterminate paths.
  15. Monitor authorization failures and unexpected exceptions.

Production checklist

Authentication

  • Signature, issuer, audience, and expiration are validated.
  • The expected authentication scheme protects RuleGate endpoints.
  • Subject, role, and permission claims come from trusted identity data.
  • Anonymous requests are challenged.

Policies

  • The complete manifest compiles successfully.
  • No partial policy set is registered.
  • Policy IDs and routes are unique.
  • Identifier casing is consistent.
  • Policy changes receive source-control review.
  • The deployed manifest version is recorded.

Subject mapping

  • Exactly one stable subject ID is required.
  • Ambiguous identity fails closed.
  • Provider-specific role and permission claims are mapped explicitly.
  • Arbitrary claims are not automatically trusted as attributes.
  • Custom factories are thread-safe.

Resource mapping

  • Domain resources are loaded from authoritative server data.
  • Route IDs are not treated as proof of ownership.
  • Resource type matches the selected policy.
  • Required route data fails closed when missing.
  • Resource attributes cannot be overridden by the client.

Context

  • Time comes from a trusted IRuleGateClock.
  • Context attributes are server-derived.
  • Request headers are not trusted without validation.
  • Custom temporal logic handles time zones explicitly.

Attribute enrichment

  • Provider data comes from authoritative server-side components.
  • Missing trusted data fails closed.
  • Provider exceptions and cancellation do not invoke the engine.
  • Provider order is deterministic and covered by tests.
  • Every collision behavior is intentional.
  • Scoped providers do not leak request state across requests.
  • Enrichment logs omit names, values, and exception messages.

HTTP responses

  • Anonymous requests return challenge behavior.
  • Authenticated denials return forbid behavior.
  • Public responses are generic.
  • Internal failure codes are not exposed.
  • Authentication headers are preserved.
  • Trace-based support tools enforce their own authorization.

Diagnostics and audit

  • Diagnostics are enabled only when needed.
  • Debug requirement logging is controlled.
  • Log access and retention are restricted.
  • Custom sinks omit unnecessary sensitive fields.
  • Diagnostic queues are bounded.
  • A separate durable audit mechanism exists when required.

Custom extensions

  • Missing or uncertain data never returns success.
  • Cancellation is preserved where the integration supplies it.
  • Implementations are concurrency-safe.
  • Unexpected failures are observable.
  • Remote calls are bounded and resilient.
  • Regression tests cover failure behavior.

Security testing strategy

Security tests should verify both success and failure.

Policy routing

Test:

  • Exact route matches
  • Resource-type casing differences
  • Action casing differences
  • Missing policies
  • Duplicate policy IDs
  • Duplicate policy routes
  • Malformed owned policy names

Requirements

Test:

  • Present and missing permissions
  • Present and missing roles
  • Missing attributes
  • False comparisons
  • Unsupported runtime attribute types
  • Scalar type mismatches
  • Unsupported operator/type combinations
  • Missing custom evaluators
  • Nested all, any, and not
  • Indeterminate propagation

Identity

Test:

  • Missing subject ID
  • Empty subject ID
  • Repeated identical subject IDs
  • Multiple distinct subject IDs
  • Configured role claim types
  • Configured permission claim types
  • Case-distinct claim values
  • Unauthenticated principals

Resources

Test:

  • Existing AuthorizationResource
  • Valid endpoint route ID
  • Missing endpoint
  • Missing metadata
  • Conflicting metadata
  • Missing route value
  • Empty route value
  • Resource-type mismatch
  • Unsupported custom resource

Manifests

Test:

  • Empty YAML
  • Malformed YAML
  • Unknown properties
  • Duplicate keys
  • Excessive nesting
  • Unsupported schema version
  • Invalid scalar literals
  • Invalid operator/type combinations
  • Compilation failure with zero returned policies
  • Cancellation

HTTP responses

Test:

  • Anonymous 401
  • Authenticated 403
  • Allowed endpoint execution
  • Preserved authentication headers
  • Generic public problem codes
  • Absence of internal failure details
  • Absence of claims and identifiers
  • Already-started response behavior
  • Existing custom result-handler preservation

Diagnostics

Test:

  • Disabled sink
  • Information event
  • Debug requirement events
  • Parent-child evaluation IDs
  • Sensitive-field omission
  • Custom sink field selection
  • Sink exception isolation
  • Concurrent evaluations
  • Diagnostic and audit separation
  • Bounded telemetry result values
  • Absence of identity, policy, claim, and attribute data from telemetry
  • Cancellation, rejection, and unexpected-error telemetry

Attribute enrichment

Test:

  • Subject, resource, and context stage order
  • Equal-order registration stability
  • Missing required data
  • Provider exceptions
  • Cancellation propagation
  • Fail, keep-existing, and replace-existing collision behavior
  • Unsupported attribute values
  • Sensitive-field omission from diagnostics
  • Scoped dependency isolation

Current security boundaries

RuleGate 1.0 provides:

  • Local in-process authorization
  • Exact policy routing
  • Default-deny decisions
  • Fail-closed built-in requirements
  • Typed scalar attribute comparison
  • Attribute-to-attribute comparison
  • Explicit-time-zone and bounded date-time requirements
  • Authentication-age, MFA-age, and canonical context requirements
  • Declarative YAML manifests
  • Duplicate-key rejection
  • Unknown-property rejection
  • Bounded YAML recursion
  • All-or-nothing manifest compilation
  • Local in-memory, YAML file, embedded-resource, configuration, and application-defined policy sources
  • Immutable snapshots, atomic reload, and last-valid-snapshot preservation
  • Authenticated dynamic ASP.NET Core policies
  • Fail-closed subject and resource mapping
  • Ordered fail-closed ASP.NET Core attribute enrichment
  • Explicit attribute collision and precedence behavior
  • HTTP request cancellation propagation to enrichment and evaluation
  • Generic opt-in HTTP ProblemDetails
  • Opt-in structured diagnostics
  • Diagnostic sink failure isolation
  • Exporter-neutral OpenTelemetry activities and low-cardinality metrics
  • Parallel evaluation/reload tests and bounded concurrency stress

RuleGate 1.0 does not provide:

  • Authentication
  • Token validation
  • Identity-provider-specific trust configuration
  • Automatic arbitrary claim-to-attribute mapping
  • Automatic domain-resource loading
  • Automatic context attribute mapping
  • Manifest signing
  • Manifest encryption
  • Remote policy-store security
  • Durable audit storage
  • Built-in distributed rate limiting
  • Frontend security enforcement

These responsibilities remain with the host application or planned future integrations.

Manifest resource-exhaustion boundaries

RuleGate treats manifest text and programmatically constructed manifest models as bounded inputs.

The manifest package enforces:

  • at most 1,048,576 bytes for a source file or its decoded UTF-8 content;
  • at most 4,096 policies per manifest;
  • a maximum requirement depth of 64;
  • at most 4,096 requirement nodes per policy;
  • at most 1,024 children in each all or any requirement;
  • at most 65,536 requirement nodes across one manifest;
  • cycle rejection for programmatically constructed requirement graphs.

The loader rejects oversized input before YAML deserialization completes. The validator then runs an iterative, cycle-safe structural preflight before its recursive semantic checks. Therefore over-depth and cyclic graphs cannot reach the recursive semantic validator.

Every resource-limit outcome is a structured load or validation failure. Compilation remains all-or-nothing, and a failed reload cannot activate a partial policy set.

The limits are intentionally fixed for the 1.0 contract. They are not a substitute for host-level file permissions, trusted policy sources, deployment review, or process-level resource controls.

Runtime requirement-depth boundary

RuleGate limits runtime requirement evaluation to a depth of 64, with the root requirement counted as depth one.

A requirement presented at depth 65 is not passed to its registered evaluator. The dispatcher returns an Indeterminate result containing AuthorizationFailureCodes.RequirementDepthExceeded. Authorization engines map that result to a denial, so exceeding the limit can never grant access.

Depth is carried by private child-dispatcher instances created for each evaluation traversal. RuleGate does not use shared mutable counters or AsyncLocal state for this boundary. Concurrent authorization evaluations on the same dispatcher therefore maintain independent depth state.

The same boundary applies to built-in and custom evaluators. In the diagnostics-enabled path, the rejected requirement is recorded with an Indeterminate outcome, but its evaluator is not executed.

Cancellation is checked before producing a depth failure. Custom evaluator exceptions below or at the permitted depth retain the normal exception contract and are not converted into depth failures.

Sensitive diagnostics regression boundary

RuleGate diagnostics expose structural decision information, bounded outcomes, stable failure codes, durations, and low-cardinality operational metadata. Built-in logs and telemetry do not include subject or resource identifiers, claim, role, or permission values, authorization attribute names or values, credentials, request content, or exception details.

CI maintains an explicit inventory of production diagnostics sinks. A new logging, tracing, activity, metric, or console sink fails the gate until its privacy and cardinality behavior is reviewed. Existing logging invocations are checked for forbidden placeholders and runtime values, and telemetry keys are checked for sensitive or high-cardinality dimensions.

The gate also runs the focused telemetry and ASP.NET Core logging privacy tests. Its negative self-test proves that subject identifiers, authorization attribute values, exception objects, and sensitive telemetry keys are rejected.

Application-defined diagnostics sinks and custom OpenTelemetry enrichment remain application-owned security boundaries and require an independent review.

npm artifact reproducibility boundary

RuleGate's three publishable npm packages are built and packed twice from clean frontend output directories. CI requires the resulting .tgz artifacts to be byte-for-byte identical, including package inventory, entry order, file modes, TAR metadata, GZIP metadata, and meaningful file payloads.

The verifier checks package names, versions, public-access and provenance settings, lifecycle-script absence, and the absence of development dependencies, lockfiles, credential files, symbolic links, special files, and machine-local paths in the published tarballs.

The gate also enforces the pnpm build-script allowlist, disallows registry-external dependencies other than the three reviewed local workspace links, runs production and complete dependency audits, and verifies the frozen frontend public API.

NuGet reproducibility boundary

RuleGate NuGet packages use deterministic compiler and portable PDB settings. Two clean builds must produce the same meaningful package payload.

The current NuGet pack container may still vary byte-for-byte because generated OPC core-properties paths, relationship identifiers, ZIP entry ordering, and archive timestamps are not stable between runs. RuleGate therefore does not claim that the raw .nupkg and .snupkg archive hashes are reproducible.

CI builds all six NuGet packages and six symbol packages twice from clean outputs. It canonicalizes only the known volatile OPC core-properties metadata and then requires every logical package entry—including assemblies, PDBs, nuspec files, documentation, dependencies, and tool payloads—to be byte identical. The verifier includes a negative self-test proving that a meaningful payload mutation is rejected.

Production Regex surface

RuleGate 1.0 does not use regular-expression evaluation in production code. This avoids unbounded pattern execution and regular-expression denial-of-service risks in authorization decisions.

CI scans production source and project files and fails if a Regex API reference is introduced. Future Regex-based functionality must define bounded input and pattern sizes, explicit execution timeouts, deterministic failure behavior, and fail-closed authorization semantics before this restriction can be changed.

Next steps

Continue with: