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
↓
ResponseRegistering 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
| Field | Type | Available at |
|---|---|---|
request.method | string | all hooks |
request.path | string | all hooks |
request.remote_addr | string | all hooks |
request.headers | map | all hooks |
user.id | string | pre_policy and later |
user.role | string | pre_policy and later |
user.tenant_id | string | pre_policy and later |
user.raw_claims | map | pre_policy and later |
policy.action | string | post_policy and later |
policy.result | "allowed" | "denied" | post_policy and later |
policy.matched_rule | string | post_policy and later |
response.status | int | post_handler only |
response.body | bytes | post_handler only |
HookResult values
| Return value | Effect |
|---|---|
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 Requirement | Hook point |
|---|---|
| V1.4.2 — Access control enforced at trusted layer | pre_policy, post_policy |
| V4.1.3 — Deny-by-default when no rule matches | post_policy (supplement Rego fail-closed) |
| V7.1.1 — Log all security decisions | post_policy |
| V14.4 — HTTP security headers present | post_handler |
Next steps
- Middleware Pipeline — where hooks sit relative to middleware
- Authorization with Rego — policy-layer enforcement before hooks run
- Compliance Controls — ASVS, SOC 2, and HIPAA alignment
- Roadmap — security hooks are a Phase 3 enterprise feature