Typescript data safety primitive that prevents sensitive state from leaking through logging, devtools, and serialization.
Modern JavaScript applications handle sensitive data constantly — auth tokens, payment details, PII, session credentials. The risk isn't always a malicious attacker. Most leaks happen from your own team, during normal development and operations.
Console logging during debugging
// Developer debugging an auth issueconsole.log("current store:",store)// → { user: "Eka", token: "eyJhbGciOiJIUzI1NiIsInR..." } ❌A single console.log in a code review, a debug session, or a staging environment can expose tokens that were never meant to be visible.
Error tracking capturing full state
Sentry.captureException(err,{extra: store})// → token, cardToken, ssn all appear in your Sentry dashboard ❌Tools like Sentry, Datadog, and LogRocket serialize everything they receive. If your store is in scope when an error occurs, every key goes to the cloud.
JSON serialization leaking to the client
// Server-side rendering — state serialized into HTMLconsthtml=`<script>window.__STATE__ = ${JSON.stringify(store)}</script>`// → sensitive fields visible in page source ❌SSR hydration is a common vector for leaking server-side secrets to the browser.
Devtools broadcasting everything
Redux DevTools, Zustand devtools, and React Query devtools display the full store in real time. Any sensitive value in state is immediately visible to anyone with devtools open — including in production if devtools extensions are installed.
The core issue
No existing state manager addresses this at the primitive level. The safe path requires manually sanitizing every log call, every error boundary, every analytics event, and every serialization point. @ekaone/shielded makes the safe path the default.
sealed() wraps a value in an opaque container that is invisible to all standard serialization and logging mechanisms. The value is stored in a native private class field (#value) — unreachable via Object.keys, Object.entries, or property enumeration. It only surfaces when you explicitly call .unwrap().
consttoken=sealed("eyJhbG...")console.log(token)// [Sealed]JSON.stringify({ token })// '{}'`${token}`// "[Sealed]"Object.keys({ token })// [] (no enumerable properties)token.unwrap()// "eyJhbG..." ← explicit opt-in onlynpm install @ekaone/shieldedpnpm install @ekaone/shieldedyarn install @ekaone/shieldedWraps any value so it is hidden from serialization and logging. Use .unwrap() to explicitly read it.
import{sealed}from"@ekaone/shielded"consttoken=sealed("eyJhbG...")// All of these hide the valueconsole.log(token)// [Sealed]JSON.stringify({ token })// '{}'String(token)// "[Sealed]"`Bearer ${token}`// "Bearer [Sealed]"// Explicit readtoken.unwrap()// "eyJhbG..."token.isSealed// truetoken.isExpired// falseWorks with any type:
sealed(42).unwrap()// 42sealed({a: 1}).unwrap()// { a: 1 }sealed(true).unwrap()// truesealed(null).unwrap()// nullCreates a store with full sealed value awareness. Plain values behave normally. Sealed values stay sealed until explicitly unwrapped.
import{createStore,sealed}from"@ekaone/shielded"conststore=createStore({user: "Eka",token: sealed("eyJhbG..."),cardToken: sealed("tok_visa_..."),})// Read plain valuestore.get("user")// "Eka"// Read sealed value — still sealedstore.get("token")// SealedValue<string>store.get("token").unwrap()// "eyJhbG..."// Writestore.set("user","John")store.set("token",sealed("newToken..."))// Snapshot — sealed keys omitted entirelystore.snapshot()// { user: "Eka" }// Safe to log or send to error trackingconsole.log(store.snapshot())// { user: "Eka" } ✅Sentry.captureException(err,{extra: store.snapshot()// token and cardToken never appear ✅})Subscribe to state changes. Returns an unsubscribe function. Sealed values remain sealed inside the subscriber.
constunsubscribe=store.subscribe((state)=>{console.log(state.user)// "Eka"console.log(state.token)// [Sealed] ← never leaks})store.set("user","John")// subscriber fires// Stop listeningunsubscribe()Wraps a sealed value with a time-to-live in milliseconds. After expiry, .unwrap() throws. Useful for session tokens, OTP values, and temporary credentials.
import{sealed,withTTL}from"@ekaone/shielded"consttoken=withTTL(sealed("eyJhbG..."),5*60*1000)// 5 minutestoken.unwrap()// "eyJhbG..." (within TTL)token.isExpired// false// After 5 minutes:token.unwrap()// throws "[shielded] sealed value has expired"token.isExpired// trueType guard — returns true if a value is a SealedValue. Useful for conditional unwrapping.
import{isSealedValue}from"@ekaone/shielded"constval=store.get("token")if(isSealedValue(val)){constraw=val.unwrap()// TypeScript knows this is safe}isSealedValue(sealed("x"))// trueisSealedValue("x")// falseisSealedValue({isSealed: true})// false ← spoofed objects rejectedAuth tokens
constauthStore=createStore({user: {name: "Eka",role: "admin"},accessToken: sealed("eyJhbG..."),refreshToken: sealed("dGhpcyBp..."),})// Use in API callsawaitfetch("/api/data",{headers: {Authorization: `Bearer ${authStore.get("accessToken").unwrap()}`}})// Safe to log the store stateconsole.log(authStore.snapshot())// { user: { name: "Eka", role: "admin" }} ← tokens never appearPayment flow
constcheckoutStore=createStore({cartTotal: 9900,currency: "USD",cardLast4: "4242",stripeToken: sealed("tok_visa_..."),})// Sentry captures a crash during checkoutSentry.captureException(err,{extra: checkoutStore.snapshot()// { cartTotal: 9900, currency: "USD", cardLast4: "4242" }// stripeToken is never in the report ✅})Healthcare / PII
constpatientStore=createStore({name: "John Doe",dob: sealed("1985-03-12"),ssn: sealed("***-**-1234"),appointmentDate: "2026-04-01",})// Safe analytics eventanalytics.track("appointment_viewed",patientStore.snapshot())// { name: "John Doe", appointmentDate: "2026-04-01" } ← no PII ✅Session with expiry
constsessionStore=createStore({userId: "usr_abc123",sessionToken: withTTL(sealed("sess_xyz..."),30*60*1000),// 30 min})// Works within session windowsessionStore.get("sessionToken").unwrap()// "sess_xyz..."// After 30 minutes — automatic protectionsessionStore.get("sessionToken").unwrap()// throws: sealed value has expiredsealed() works as a standalone primitive. You can drop it into Zustand, Jotai, or any other state manager without replacing anything.
With Zustand
import{create}from"zustand"import{sealed,isSealedValue}from"@ekaone/shielded"constuseStore=create(()=>({user: "Eka",token: sealed("eyJhbG..."),}))// In a componentconsttoken=useStore((s)=>s.token)token.unwrap()// "eyJhbG..."// Safe devtools snapshotconstsafeState=Object.fromEntries(Object.entries(useStore.getState()).filter(([,v])=>!isSealedValue(v)))With Jotai
import{atom}from"jotai"import{sealed}from"@ekaone/shielded"consttokenAtom=atom(sealed("eyJhbG..."))// In a componentconst[token]=useAtom(tokenAtom)token.unwrap()// "eyJhbG..."Fully typed. sealed() preserves the inner type through unwrap().
importtype{SealedValue,Store}from"@ekaone/shielded"consttoken: SealedValue<string>=sealed("eyJhbG...")constnum: SealedValue<number>=sealed(42)token.unwrap()// stringnum.unwrap()// numbersealed() adds no asymptotic overhead. All operations remain O(1).
| Operation | Complexity | vs baseline |
|---|---|---|
sealed(value) | O(1) | same |
.unwrap() | O(1) | +1 function call |
JSON.stringify | O(k) | same or faster — sealed keys skipped |
snapshot() | O(k) | O(k) key scan |
subscribe notify | O(n) | same — n subscribers |
The constant overhead of .unwrap() is a single property access — nanoseconds in practice, unmeasurable in any real application.
MIT © Eka Prasetia
⭐ If this library helps you, please consider giving it a star on GitHub!