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 issue
console.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 HTML
const html = `<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().
const token = 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"
const token = sealed("eyJhbG...")
// All of these hide the value
console.log(token) // [Sealed]
JSON.stringify({ token }) // '{}'
String(token) // "[Sealed]"
`Bearer ${token}` // "Bearer [Sealed]"
// Explicit read
token.unwrap() // "eyJhbG..."
token.isSealed // true
token.isExpired // falseWorks with any type:
sealed(42).unwrap() // 42
sealed({ a: 1 }).unwrap() // { a: 1 }
sealed(true).unwrap() // true
sealed(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"
const store = createStore({
user: "Eka",
token: sealed("eyJhbG..."),
cardToken: sealed("tok_visa_..."),
})
// Read plain value
store.get("user") // "Eka"
// Read sealed value — still sealed
store.get("token") // SealedValue<string>
store.get("token").unwrap() // "eyJhbG..."
// Write
store.set("user", "John")
store.set("token", sealed("newToken..."))
// Snapshot — sealed keys omitted entirely
store.snapshot() // { user: "Eka" }
// Safe to log or send to error tracking
console.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.
const unsubscribe = store.subscribe((state) => {
console.log(state.user) // "Eka"
console.log(state.token) // [Sealed] ← never leaks
})
store.set("user", "John") // subscriber fires
// Stop listening
unsubscribe()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"
const token = withTTL(sealed("eyJhbG..."), 5 * 60 * 1000) // 5 minutes
token.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"
const val = store.get("token")
if (isSealedValue(val)) {
const raw = val.unwrap() // TypeScript knows this is safe
}
isSealedValue(sealed("x")) // true
isSealedValue("x") // false
isSealedValue({ isSealed: true }) // false ← spoofed objects rejectedAuth tokens
const authStore = createStore({
user: { name: "Eka", role: "admin" },
accessToken: sealed("eyJhbG..."),
refreshToken: sealed("dGhpcyBp..."),
})
// Use in API calls
await fetch("/api/data", {
headers: {
Authorization: `Bearer ${authStore.get("accessToken").unwrap()}`
}
})
// Safe to log the store state
console.log(authStore.snapshot())
// { user: { name: "Eka", role: "admin" } } ← tokens never appearPayment flow
const checkoutStore = createStore({
cartTotal: 9900,
currency: "USD",
cardLast4: "4242",
stripeToken: sealed("tok_visa_..."),
})
// Sentry captures a crash during checkout
Sentry.captureException(err, {
extra: checkoutStore.snapshot()
// { cartTotal: 9900, currency: "USD", cardLast4: "4242" }
// stripeToken is never in the report ✅
})Healthcare / PII
const patientStore = createStore({
name: "John Doe",
dob: sealed("1985-03-12"),
ssn: sealed("***-**-1234"),
appointmentDate: "2026-04-01",
})
// Safe analytics event
analytics.track("appointment_viewed", patientStore.snapshot())
// { name: "John Doe", appointmentDate: "2026-04-01" } ← no PII ✅Session with expiry
const sessionStore = createStore({
userId: "usr_abc123",
sessionToken: withTTL(sealed("sess_xyz..."), 30 * 60 * 1000), // 30 min
})
// Works within session window
sessionStore.get("sessionToken").unwrap() // "sess_xyz..."
// After 30 minutes — automatic protection
sessionStore.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"
const useStore = create(() => ({
user: "Eka",
token: sealed("eyJhbG..."),
}))
// In a component
const token = useStore((s) => s.token)
token.unwrap() // "eyJhbG..."
// Safe devtools snapshot
const safeState = Object.fromEntries(
Object.entries(useStore.getState()).filter(([, v]) => !isSealedValue(v))
)With Jotai
import { atom } from "jotai"
import { sealed } from "@ekaone/shielded"
const tokenAtom = atom(sealed("eyJhbG..."))
// In a component
const [token] = useAtom(tokenAtom)
token.unwrap() // "eyJhbG..."Fully typed. sealed() preserves the inner type through unwrap().
import type { SealedValue, Store } from "@ekaone/shielded"
const token: SealedValue<string> = sealed("eyJhbG...")
const num: SealedValue<number> = sealed(42)
token.unwrap() // string
num.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!