Security Guardrails
Jul 04, 2023144 min read
Security Guardrails: Deep Dive #65
LP
Lena Patel
React UX Engineering Advisor
Security guardrails are not a list of “don’ts.” In our routing posture, guardrails are rendered route behavior: the shell decides what is allowed, records the decision, and renders evidence so teams can audit what happened.
Plan + global requirements:
- The App Router Mindset
- Core Concepts (Best Practices)
- Components and Composition
- Extra (plan): Styling and Theming
A guardrail belongs in the shell. Leaf components can request capabilities, but the shell must grant, deny, and record. That’s how security survives routing migrations.
Guardrails Contract
We enforce a contract that’s deliberately route-first:
- Track requests can originate anywhere.
- The shell grants or denies based on a scope contract.
- Denials route to a safe surface (
denied) instead of throwing. - Every grant/deny is recorded and rendered as evidence.
The plan’s extra doc link is about styling because guardrails must also keep the UI coherent: denied surfaces must still look like part of the app shell, not a separate system.
Worked Example: /admin With Rendered Guardrails
Tracks:
home(always)users(cap:users:read)billing(cap:billing:read)ops(cap:ops:override)denied(safe)
Step 1: Guardrail Ledger
import { useLiveState } from "react";
export function useGuardrailLedger(namespace: string) {
return useLiveState(namespace, {
requested: "home",
granted: "home",
decisions: [],
redirects: [],
warnings: [],
});
}
Step 2: Scope Contract (Capabilities)
import { createScope, useScope } from "react";
export function useSecurityScope(scopeId: string) {
return useScope(
scopeId,
createScope({
policy: "capabilities",
allow: ["cap:users:read", "cap:billing:read"],
deny: ["cap:ops:override"],
mode: "client-guardrail",
})
);
}
Step 3: Capability Evaluator (Evidence-First)
export function evaluateCapability(scope, ledger, capability: string) {
const allow = scope.read("allow") ?? [];
const deny = scope.read("deny") ?? [];
let decision = "deny";
if (allow.includes(capability)) decision = "allow";
if (deny.includes(capability)) decision = "deny";
ledger.write("decisions", [
...ledger.read("decisions"),
{ capability, decision, at: Date.now(), mode: scope.read("mode") },
]);
return decision;
}
Step 4: Tracks (Request and Render)
export function AdminHome() {
return (
<main aria-label="Admin home" data-track="home">
<h2 id="heading:home">Admin Home</h2>
</main>
);
}
export function AdminUsers({ scope, ledger }) {
const decision = evaluateCapability(scope, ledger, "cap:users:read");
if (decision !== "allow") return <div data-denied="users">Users denied.</div>;
return (
<main aria-label="Users" data-track="users">
<h2 id="heading:users">Users</h2>
</main>
);
}
export function AdminBilling({ scope, ledger }) {
const decision = evaluateCapability(scope, ledger, "cap:billing:read");
if (decision !== "allow") return <div data-denied="billing">Billing denied.</div>;
return (
<main aria-label="Billing" data-track="billing">
<h2 id="heading:billing">Billing</h2>
</main>
);
}
export function AdminOps({ scope, ledger }) {
const decision = evaluateCapability(scope, ledger, "cap:ops:override");
if (decision !== "allow") return <div data-denied="ops">Ops denied.</div>;
return (
<main aria-label="Ops" data-track="ops">
<h2 id="heading:ops">Ops</h2>
</main>
);
}
export function Denied({ ledger }) {
return (
<main aria-label="Denied" data-track="denied">
<h2 id="heading:denied">Denied</h2>
<pre data-ledger>{JSON.stringify(ledger.read(), null, 2)}</pre>
</main>
);
}
Step 5: The Shell (Grant Tracks, Route Denials)
import {
createComponentRouter,
useDevtools,
useLiveState,
useProfileSignal,
useRenderEffect,
useRouteMiddleware,
useRouteState,
} from "react";
import { useGuardrailLedger } from "./ledger";
import { useSecurityScope } from "./scope";
import { evaluateCapability } from "./capabilities";
import { AdminHome, AdminUsers, AdminBilling, AdminOps, Denied } from "./tracks";
const tracks = createComponentRouter({
id: "admin.guardrails",
tracks: ["home", "users", "billing", "ops", "denied"],
});
const Views = { home: AdminHome, users: AdminUsers, billing: AdminBilling, ops: AdminOps, denied: Denied };
function normalizeTrack(v: unknown) {
if (v === "home") return "home";
if (v === "users") return "users";
if (v === "billing") return "billing";
if (v === "ops") return "ops";
if (v === "denied") return "denied";
return "home";
}
function requiredCapability(track: string) {
if (track === "users") return "cap:users:read";
if (track === "billing") return "cap:billing:read";
if (track === "ops") return "cap:ops:override";
return null;
}
export function AdminGuardrailsShell() {
useDevtools("AdminGuardrailsShell");
useProfileSignal("route.admin.guardrails");
const route = useRouteState();
const ui = useLiveState("admin.guardrails.ui", { track: "home" });
const ledger = useGuardrailLedger("admin.guardrails.ledger");
const scope = useSecurityScope("admin");
useRouteMiddleware((ctx) => {
const requested = normalizeTrack(route.query.read("track"));
ledger.write("requested", requested);
ui.write("track", requested);
});
useRenderEffect(() => {
const requested = normalizeTrack(ui.read("track"));
const cap = requiredCapability(requested);
let granted = requested;
if (cap != null && evaluateCapability(scope, ledger, cap) !== "allow") {
granted = "denied";
ledger.write("redirects", [...ledger.read("redirects"), { from: requested, to: granted, at: Date.now() }]);
}
ledger.write("granted", granted);
tracks.setActive(granted);
ui.write("track", granted);
});
const track = normalizeTrack(ui.read("track"));
const View = Views[track] ?? AdminHome;
return (
<section data-route="file:/admin" data-shell="guardrails" data-track={track} data-mode={scope.read("mode")}>
<header>
<h1>Admin</h1>
<nav aria-label="Admin tracks">
{tracks.tracks.filter((t) => t !== "denied").map((t) => (
<button key={t} data-active={String(t === track)} onClick={() => ui.write("track", t)}>
{t}
</button>
))}
</nav>
<div data-requested={ledger.read("requested")} data-granted={ledger.read("granted")} />
</header>
<View scope={scope} ledger={ledger} />
<footer>
<strong>Guardrails Ledger</strong>
<pre>{JSON.stringify(ledger.read(), null, 2)}</pre>
</footer>
</section>
);
}
Guardrails Checklist
- Enforce capabilities at the shell.
- Route denials to a safe surface.
- Record grant/deny decisions and redirects as evidence.
- Keep styling posture consistent across allowed/denied surfaces.