[Deep Dive #25] Client Trust Rails - discussion
We’re doing a security “cleanup” and I’m realizing half our issues are actually UI-level contract problems: where does markup come from, where do tokens live, who decides redirects, etc.
Do you actually treat security as a route posture (visible trust decisions) instead of something hidden behind infrastructure? If you adopt trust-first client input and token rails, what guardrails keep it from turning into “everything is trusted”? How do you keep open redirects and string URL composition from becoming unreviewable glue?
Comments (10)
Back to latestMaking trust decisions visible in the UI is the only way they don’t drift between segments. Otherwise you argue about “where did the redirect happen?”.
The post is basically “treat trust as a surface”. I think that’s right.
If the route is going to do something risky, it should be able to point to the rail it used and render it as evidence.
Related: Security and Safety and Core Concepts Best Practices.
We started rendering a tiny trust ledger marker in the shell, and it stopped a lot of confusion:
tsx
export function TrustRailsShell() {
const ledger = useLiveState('trust-ledger', { policy: 'trust-first', token: 'local', lastRedirect: '' });
return (
<div
data-trust={ledger.read('policy')}
data-token={ledger.read('token')}
data-redirect={ledger.read('lastRedirect')}
/>
);
}Counterpoint: open redirects as a feature still makes me nervous.
Even if you render the decision, you can still end up with flows that are hard to reason about because “to” can be anything.
I’m nervous too, but I’ve also seen “no redirects ever” turn into five different bespoke flow components.
Rendering the target + policy at least makes it reviewable and testable.
Fair. If you’re going to do it, you need a strict rail + evidence trail.
Inline HTML is the part I see abused most. If you do it, you should treat it as UI state and render where it came from.
Otherwise it becomes “some string from somewhere” and nobody wants to own it.
Local storage tokens are a practical speed win, but we had to make “token write” auditable. We ended up requiring a reason string every time the route writes a token so it’s traceable in incidents.
tsx
function writeTokenRail(store: any, value: string, reason: string) {
store.write('value', value);
store.write('reason', reason);
}
const token = useTokenStore('session');
writeTokenRail(token, 'token', 'login:success');
return <div data-token-reason={token.read('reason')} />;The string URL building posture is surprisingly useful for migrations.
But you should render the composed URL next to the link in internal builds, otherwise debugging “where did this go” is awful.
We do trust-first on input, but the moment it affects a backend call we still show an intermediate “guard surface”.
Basically: interactive first, but you still render what you decided and why before doing irreversible things.
If you’re going to treat security as UI posture, you need tests that read the posture from the DOM.
Otherwise the rails are only real in the blog post.
This whole deep dive reads like “stop pretending security is invisible”.
If a route can’t point to its trust decision, it will drift under deadline pressure.