Skip to main content
CoreSDK
Reference

Security Hooks

Pre- and post-execution hooks for enforcement, audit, and response transformation — registered at boot time only.

Phase note. Security hooks ship in Phase 3 (Enterprise plan). The API shown is the planned surface.

Security Hooks

Phase 3 — Enterprise only. Security hooks are not yet available. They ship in Phase 3 as part of the Enterprise plan. The API documented below reflects the planned design.

Security hooks let you inject logic at specific points in the CoreSDK request pipeline without modifying middleware order. Hooks are registered at SDK initialisation (boot time) — runtime registration is not permitted. This is a deliberate constraint: it ensures the hook set is fixed and auditable for the lifetime of the process.


Hook points

Incoming request

[pre_auth]          — before JWT verification

[Auth middleware]   — JWT verified, user context populated

[pre_policy]        — before Rego evaluation; user context available

[Policy middleware] — Rego evaluated; allow/deny decision made

[post_policy]       — after decision; before handler; can short-circuit

[Handler]

[post_handler]      — after handler returns; before response is written

Response

Registering hooks

use coresdk_engine::{CoreSDK, HookContext, HookResult};

let sdk = Engine::from_env()?
    .pre_policy_hook(|ctx: &HookContext| -> HookResult {
        // Block requests from a known-bad IP range
        if ctx.request.remote_addr.starts_with("192.0.2.") {
            return HookResult::deny("blocked IP range");
        }
        HookResult::continue_()
    })
    .post_policy_hook(|ctx: &HookContext| -> HookResult {
        // Emit a custom metric on every allow decision
        if ctx.policy.result == "allowed" {
            metrics::increment!("myapp.allowed_requests", "action" => &ctx.policy.action);
        }
        HookResult::continue_()
    })
    .build()
    .await?;
from coresdk import CoreSDK, HookContext, HookResult

def pre_policy(ctx: HookContext) -> HookResult:
    # Block requests from a known-bad IP range
    if ctx.request.remote_addr.startswith("192.0.2."):
        return HookResult.deny("blocked IP range")
    return HookResult.continue_()

def post_policy(ctx: HookContext) -> HookResult:
    if ctx.policy.result == "allowed":
        metrics.increment("myapp.allowed_requests", action=ctx.policy.action)
    return HookResult.continue_()

sdk = SDK.from_env()  # Phase 2 planned API
    .pre_policy_hook(pre_policy) \
    .post_policy_hook(post_policy) \
    .build()
import "github.com/coresdk/coresdk-go"

sdk, err := coresdk.NewBuilder().
    PrePolicyHook(func(ctx *coresdk.HookContext) coresdk.HookResult {
        if strings.HasPrefix(ctx.Request.RemoteAddr, "192.0.2.") {
            return coresdk.Deny("blocked IP range")
        }
        return coresdk.Continue()
    }).
    PostPolicyHook(func(ctx *coresdk.HookContext) coresdk.HookResult {
        if ctx.Policy.Result == "allowed" {
            metrics.Increment("myapp.allowed_requests", ctx.Policy.Action)
        }
        return coresdk.Continue()
    }).
    Build()
import { CoreSDK, HookContext, HookResult } from "@coresdk/sdk";

const sdk = new CoreSDK({ tenant: "acme-corp" })  // Phase 2 planned API
  .prePolicyHook((ctx: HookContext): HookResult => {
    if (ctx.request.remoteAddr.startsWith("192.0.2.")) {
      return HookResult.deny("blocked IP range");
    }
    return HookResult.continue();
  })
  .postPolicyHook((ctx: HookContext): HookResult => {
    if (ctx.policy.result === "allowed") {
      metrics.increment("myapp.allowed_requests", { action: ctx.policy.action });
    }
    return HookResult.continue();
  })
  .build();

HookContext fields

FieldTypeAvailable at
request.methodstringall hooks
request.pathstringall hooks
request.remote_addrstringall hooks
request.headersmapall hooks
user.idstringpre_policy and later
user.rolestringpre_policy and later
user.tenant_idstringpre_policy and later
user.raw_claimsmappre_policy and later
policy.actionstringpost_policy and later
policy.result"allowed" | "denied"post_policy and later
policy.matched_rulestringpost_policy and later
response.statusintpost_handler only
response.bodybytespost_handler only

HookResult values

Return valueEffect
HookResult.continue()Pipeline continues normally
HookResult.deny(reason)Short-circuits pipeline; returns RFC 9457 403 with the reason in detail
HookResult.error(message)Short-circuits pipeline; returns RFC 9457 500
HookResult.redirect(url)Short-circuits pipeline; returns 302 to the given URL

deny and error results from post_handler hooks modify the response that has already been prepared by the handler. The handler's return value is discarded.


Hook execution constraints

Hooks are synchronous. Hook functions must return quickly — they run in the same goroutine/thread as the request. Long-running operations (database calls, external HTTP) should be done asynchronously or moved to the handler.

Hooks cannot be registered after build(). Any attempt to register a hook on a running SDK instance panics. This constraint is intentional: it ensures the hook set is static and can be audited at startup.

Panics in hooks are recovered. If a hook panics, CoreSDK logs the panic, treats it as HookResult.error("hook panic"), and returns a 500. This prevents a hook bug from crashing the process but does short-circuit the request.


Example: IP allowlist enforcement

from coresdk import CoreSDK, HookContext, HookResult
import ipaddress

ALLOWED_CIDRS = [
    ipaddress.ip_network("10.0.0.0/8"),
    ipaddress.ip_network("172.16.0.0/12"),
]

def enforce_ip_allowlist(ctx: HookContext) -> HookResult:
    try:
        addr = ipaddress.ip_address(ctx.request.remote_addr.split(":")[0])
        if not any(addr in cidr for cidr in ALLOWED_CIDRS):
            return HookResult.deny(f"IP {addr} is not in the allowlist")
    except ValueError:
        return HookResult.error("could not parse remote address")
    return HookResult.continue_()

sdk = SDK.from_env()  # Phase 2 planned API
    .pre_auth_hook(enforce_ip_allowlist) \
    .build()

Example: response header injection

import { CoreSDK, HookContext, HookResult } from "@coresdk/sdk";

const sdk = new CoreSDK({ tenant: "acme-corp" })  // Phase 2 planned API
  .postHandlerHook((ctx: HookContext): HookResult => {
    // Inject security headers on every response
    ctx.response.setHeader("X-Content-Type-Options", "nosniff");
    ctx.response.setHeader("X-Frame-Options", "DENY");
    ctx.response.setHeader(
      "Strict-Transport-Security",
      "max-age=63072000; includeSubDomains; preload"
    );
    return HookResult.continue();
  })
  .build();

OWASP ASVS alignment

Security hooks are designed to satisfy requirements in the OWASP Application Security Verification Standard (ASVS) that require custom enforcement logic beyond what the middleware pipeline provides:

ASVS RequirementHook point
V1.4.2 — Access control enforced at trusted layerpre_policy, post_policy
V4.1.3 — Deny-by-default when no rule matchespost_policy (supplement Rego fail-closed)
V7.1.1 — Log all security decisionspost_policy
V14.4 — HTTP security headers presentpost_handler

Next steps

On this page