client-server-boundary — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited client-server-boundary (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The client-server boundary is the line in a unified codebase where execution context changes between a server runtime and a client runtime. The server runtime can hold secrets, reach databases, read files, and enforce authority. The client runtime is a browser or other public caller that can read any code and data shipped to it and can send arbitrary requests back.
Anything that crosses this line is serialized. It is encoded by a wire format on one side and decoded into a value on the other. The active format matters: JSON, structured clone, React Server Component payloads, React Server Function arguments and returns, SvelteKit server-load data, and FormData are related but not interchangeable. The boundary is governed by three properties:
Modern frameworks make this boundary syntactically visible through directives such as 'use client' and 'use server', guard imports such as import 'server-only' and import 'client-only', and file conventions such as SvelteKit's .server suffix, $lib/server directory, and +page.server.js / +layout.server.js load files. These marks do not invent the boundary. They expose an old boundary inside a shared module tree where it is otherwise easy to forget.
This skill covers the serialization and trust transition between server and client code: directive semantics, import-graph reachability, what values may cross in common formats, how React Server Components and Server Functions encode values, why client-to-server calls are endpoint calls, how server-only code leaks into client bundles, how server-to-client payloads leak secrets or raw records, how URL-derived values remain client input, and how TypeScript stops at the runtime boundary.
It is framework-portable, but it uses React Server Components and Next.js App Router as the modern canonical example because those systems put server and client modules in one file tree. SvelteKit is the compact cross-checking example: .server files and $lib/server modules are rejected from browser-running import graphs, while server load files (+page.server.js, +layout.server.js) return data that is serialized for the browser.
The boundary always exists. A PHP template, a JSON API, a Remix / React Router action, a SvelteKit server load, a Next.js Server Component tree, and a React Server Function all have a server side and a client side separated by bytes. The difference in modern frameworks is that the code is colocated, so the boundary needs explicit marks and review questions.
The discipline is to stop asking "can I import this?" and ask:
A program that treats both sides as interchangeable will leak secrets, publish internal fields, ship broken modules, and trust attackers. A program that designs with the boundary gets legible import graphs, smaller client bundles, explicit data transfer objects, and server-side authorization where it belongs.
| Mark | Where it goes | What it means | Boundary risk |
|---|---|---|---|
'use client' | Top of a module, before imports | This module is a client entry point when imported from server code. Its transitive dependency subtree is client code and is shipped/evaluated on the client. | Everything reachable from that entry point must be browser-safe: no secrets, server drivers, filesystem, privileged environment reads, or Node-only assumptions. |
'use server' | Top of an async function body, or top of a server module whose exports are async functions | The function is a React Server Function. When called from client code it makes a network request with serialized arguments and serialized return value. In forms and mutations this is commonly called a Server Action. | It is reachable from the client. Treat arguments as untrusted, authorize the operation, and return only what the UI needs. |
import 'server-only' | Top of a server-intended module | A guard that makes supported frameworks error if the module is pulled into a client graph. | Use on modules that read secrets, database clients, filesystem state, or server-only environment variables. |
import 'client-only' | Top of a browser-intended module | A guard that makes supported frameworks error if the module is imported into the server graph. | Use on modules that require window, document, browser storage, layout APIs, or client-only SDKs. |
SvelteKit .server modules / $lib/server | Filename suffix such as secrets.server.ts, or the $lib/server directory | A server-only module convention. SvelteKit rejects direct or indirect imports from browser-running code because the import chain could leak sensitive code or data. | Put private env, database, filesystem, and server SDK code behind these conventions; do not re-export them through shared utilities. |
SvelteKit +page.server.js / +layout.server.js | Route load files | Server-only load functions. Their return values are serialized before reaching browser code. | Return minimal DTOs that the browser may read; keep non-serializable objects and private credentials on the server side. |
'use client' is an entry-point directive, not a label required on every Client Component file. A component can become client-evaluated because it is a transitive dependency of a client entry. Conversely, a module without 'use client' can still be evaluated on the client when imported below a client entry. Review the import graph, not just the first line of a file.
'use server' marks Server Functions, not Server Components. In React Server Components there is no "Server Component directive"; Server Components are the default unless a client module boundary is introduced.
Next.js proxy.ts belongs to a different but adjacent network boundary: request interception before routing. In Next.js 16, middleware.ts is deprecated/renamed to proxy.ts, and Proxy uses the Node.js runtime. Use it for coarse decisions such as redirects, rewrites, and request shaping, but do not treat Proxy coverage as authorization for Server Functions or component data access. A matcher change, route refactor, or direct action invocation can bypass the place where Proxy ran; Server Functions still validate, authenticate, authorize, and rate-limit inside server code.
Use the exact serializer for the crossing. "Serializable" is not a universal type.
| Crossing / wire format | Generally supported | Not supported / not safe | Review rule |
|---|---|---|---|
| Plain JSON | string, number, boolean, null, arrays, plain objects | undefined, functions, Symbols, BigInts, Maps, Sets, class instances, circular references; Dates become strings by convention | Convert to plain DTOs. Parse unknown JSON at the receiving side before trusting it. |
| Structured clone | JSON-like values plus Date, RegExp, Blob, File, Map, Set, ArrayBuffer, typed arrays, many Error objects | Functions, DOM nodes, Symbols; prototypes, property descriptors, getters/setters, and class private fields are not preserved | Structured clone is not a domain-object transport. Treat cloned objects as data snapshots. |
| React Server Component -> Client Component props | React-serializable primitives including undefined and bigint, globally registered symbols, iterables, Map, Set, Date, typed arrays/ArrayBuffer, Promises, JSX / React elements, and Server Function references | Ordinary functions, event objects, class instances, null-prototype objects, non-global symbols, host objects, raw Request/Response objects, ORM/domain entities | The serializer may support more than JSON, but the browser can still read every prop. Pass minimal UI-shaped DTOs. |
| React Server Function / Server Action arguments | React-serializable arguments including primitives, plain objects, arrays/iterables, Map, Set, Date, typed arrays/ArrayBuffer, FormData, Promises, and Server Function references | React elements/JSX as arguments, ordinary functions, class instances, null-prototype objects, non-global symbols, event objects | Arguments are fully client-controlled. Validate and authorize inside the function. |
| React Server Function / Server Action return values | Same return-value family as React-serializable props for boundary Client Components | Raw database records, secrets, internal flags, privileged objects, unvalidated error payloads | Return the smallest value the UI needs, often { success: true } or a narrow state object. |
SvelteKit server load return values | Values serializable by devalue: JSON-representable data plus values such as BigInt, Date, Map, Set, RegExp, repeated/cyclical references, and promises for streamed data | Component constructors and other non-serializable custom values unless explicitly handled with transport hooks | Server load is still server -> browser data transfer. Shape output as browser-safe DTOs. |
| FormData / multipart form submission | string fields and Blob/File values; repeated keys are allowed | Nested objects are not represented structurally; non-string non-Blob values are stringified by append() | Convert and validate after reading. If nesting is needed, encode one field deliberately and parse it as unknown. |
The practical rule is not merely "can I encode this as bytes?" It is: can this exact framework serializer encode it, can the other side reconstruct the expected data shape, and is the reconstructed shape safe for that side to see or trust?
Do not use structuredClone() as the oracle for React Server Component props or Server Function arguments. React has its own allowlists: Server Component props can include JSX / React elements, while Server Function arguments explicitly cannot. Conversely, structured clone and React serialization differ on host objects and prototype behavior. Test the serializer that actually carries the value.
When data must cross through a narrower format, reshape at the boundary:
// JSON boundary: reshape rich server values before crossing.
const serializedUserMap = Array.from(userMap.entries());
const serializedDate = eventDate.toISOString();
// Receiving side: reconstruct only as data, not as authority.
const userMap = new Map(serializedUserMap);
const eventDate = new Date(serializedDate);The "vanishing method" failure is a serialization smell: if client code expects user.getFullName() after user crossed the boundary, the design accidentally depended on a class/prototype, not on data. Pass { displayName }, not a domain object with methods.
The most common leak is not a secret literal in JavaScript. It is over-sharing server data:
// Bad: a whole persistence record crosses into the browser.
<ProfileCard user={userRow} />
// Better: the server maps to the UI contract before the boundary.
<ProfileCard user={{ displayName: userRow.name, avatarUrl: userRow.avatarUrl }} />Use a server-only Data Access Layer for reusable reads:
// lib/current-user.ts
import 'server-only';
import { z } from 'zod';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
const ProfileDto = z.object({
displayName: z.string(),
avatarUrl: z.string().url().nullable(),
});
export async function getCurrentUserProfile() {
const session = await auth();
if (!session?.userId) return null;
const row = await db.user.findUniqueOrThrow({
where: { id: session.userId },
select: { name: true, avatarUrl: true },
});
return ProfileDto.parse({
displayName: row.name,
avatarUrl: row.avatarUrl,
});
}React's experimental taint APIs can add a backstop: taint a specific object reference or high-entropy unique value so React errors if it reaches a Client Component. Tainting is useful for catching simple mistakes, but it is experimental and cloning or deriving a new value can bypass the original taint. Use it as defense in depth, not as a substitute for DTOs, server-only modules, and authorization.
React calls functions marked with 'use server' Server Functions. A Server Function used as a form or mutation is commonly called a Server Action. Either way, when client code invokes it, the client sends a serialized request to the server and receives a serialized response.
Security consequences:
bind, route params, search params, cookies, and request bodies.<form action={serverFunction}> still submit through a network request. Frameworks can progressively enhance the experience before or after the JavaScript bundle loads; the security model is still a server endpoint receiving untrusted input.Prefer re-deriving identity and ownership inside the function:
// app/actions.ts
'use server';
import { z } from 'zod';
import { requireSession } from '@/lib/auth';
import { db } from '@/lib/db';
const UpdateNameInput = z.object({
userId: z.string().uuid(),
name: z.string().trim().min(1).max(100),
});
export async function updateUserName(rawInput: unknown) {
const session = await requireSession();
const input = UpdateNameInput.parse(rawInput);
if (session.userId !== input.userId && !session.isAdmin) {
throw new Error('not allowed');
}
await db.user.update({
where: { id: input.userId },
data: { name: input.name },
});
return { success: true };
}The client-supplied userId is not authority. It is an input to compare with the server-derived session. A safer variant may avoid accepting userId from the client at all and derive the target user from the session unless admin delegation is required.
| Leakage mode | What goes wrong | How to prevent |
|---|---|---|
| Secret in shared module | A module imported by both sides reads process.env.SECRET or a private SDK token. The client graph reaches it, causing a build error, empty substitution, or leak. | Move secret reads into server-only modules, Server Functions, Route Handlers, or the DAL; add import 'server-only'. |
| Server-only API in client graph | fs, database clients, Node-only crypto, or server SDKs become reachable below a 'use client' entry. | Push 'use client' to interactive leaves; guard server modules; review transitive imports. |
| Raw record in Client Component props | A Server Component passes a full DB row or ORM entity. Hidden fields cross even if not rendered. | Map to a minimal DTO before the boundary; select only needed columns. |
| Over-trusting React serialization | Because React can serialize Date, Map, Set, Promise, or Server Function references, developers pass rich domain objects. | Treat React-serializable as a transport allowlist, not a design target. Pass small data shapes. |
| Method/prototype dependency | A class instance or ORM entity crosses and client code expects methods, private fields, getters, or prototype identity to survive. | Pass plain data and reconstruct client behavior locally; keep domain objects on the server. |
| Untyped client -> server validation gap | A Server Function accepts a TypeScript type and skips runtime parsing. A forged call sends a different shape. | Accept unknown or FormData; parse with Zod, valibot, io-ts, or equivalent before use. |
| Client-supplied authority | The server trusts userId, isAdmin, tenant ID, cookie contents, hidden inputs, bound args, route params, or search params directly. | Authenticate and authorize from server-verifiable session/database/IdP state; compare client inputs against that state. |
| Inline action closure over sensitive data | An inline Server Action closes over a token or internal record. Framework support may send captured values to the client and back. | Close over stable non-secret identifiers only; re-read sensitive state on the server; treat encryption as defense in depth. |
| FormData nesting illusion | Code expects object structure from FormData, but only string/blob fields arrive; non-string values were stringified. | Parse each field deliberately; encode nested JSON in a named string field and validate after parsing. |
| Proxy/middleware authorization shortcut | A redirect or matcher in proxy.ts / middleware is treated as the only access-control check. A route change, matcher miss, or direct Server Function call skips it. | Use Proxy for coarse routing decisions; authenticate and authorize again inside Server Functions, Route Handlers, and server-only data access. |
| Stale vulnerable RSC packages | The project uses vulnerable React, framework, or react-server-dom-* versions whose decoder processes attacker-influenced Server Function/RSC payloads. | Check current React/framework advisories during boundary reviews and upgrade to patched versions. |
After applying this skill, verify:
'use client' entry is intentionally placed as low as practical in the tree; its transitive imports are browser-safe.server-only or is otherwise guarded from client reachability.window, document, browser storage, or layout APIs imports client-only or is otherwise guarded from server reachability.rg "db\\..*\\.(find|select|query)" followed by inspection of whether each result is mapped through a DTO before it reaches a 'use client' component.'use server' at function or module level and is treated as a client-reachable endpoint.unknown or FormData; no as cast substitutes for validation.proxy.ts / middleware redirects, or disabled UI.react-server-dom-* packages are checked against current RSC / Server Function advisories.| Instead of this skill | Use | Why |
|---|---|---|
| Deciding when and where UI is produced (CSR/SSR/SSG/ISR/RSC) | rendering-models | rendering-models owns staging; this skill owns the serialization and trust frontier once data crosses. |
| Designing the RSC read path, Suspense placement, DTO/DAL architecture, or cache/freshness model | server-components-design | That skill owns which work belongs on the server side of the RSC tree; this skill owns boundary mechanics shared by RSC, actions, and other crossings. |
Designing a full Server Action mutation flow with forms, useActionState, revalidation, and optimistic UI | server-actions-design | This skill teaches that actions are endpoint crossings; server-actions-design owns the mutation workflow. |
Designing a public route.ts / Route Handler endpoint for third parties, mobile apps, webhooks, or explicit HTTP consumers | route-handler-design | Route Handlers are explicit public HTTP surfaces; this skill owns the framework-mediated component/action boundary. |
| Designing HTTP caching, status codes, content negotiation, or header semantics | http-semantics | http-semantics owns the wire protocol; this skill owns what data and authority enter the wire. |
| Organizing frontend folder layout, feature boundaries, or client state architecture | frontend-architecture | Frontend architecture is wider; client-server-boundary is one import/data-flow axis inside it. |
| Designing the JSON shape of an external REST/GraphQL/mobile/third-party API | api-design | api-design owns public contracts and versioning; this skill owns internal server/client crossings that may not be stable APIs. |
| Choosing a validation library or enforcing TypeScript discipline generally | type-safety | type-safety owns compile-time and parsing discipline; this skill names where parsing is mandatory. |
| Performing a broad application-security review or OWASP-category vulnerability triage | security-fundamentals / owasp-security | This skill uses security principles at one boundary; security-fundamentals and owasp-security own wider security review. |
'use client' directive. Provenance: primary vendor documentation for how 'use client' creates a client module subtree, how transitive dependencies become client-evaluated, and which Server Component prop values are serializable.'use server' directive. Provenance: primary vendor documentation for Server Functions, network invocation, security considerations, and the current serializable argument/return allowlist.use client directive. Provenance: framework documentation for entry-point placement and serializable Client Component props.use server directive. Provenance: framework documentation for file-level and inline Server Actions plus authentication/authorization guidance.server-only / client-only guards, and environment-poisoning prevention.proxy.js / proxy.ts file convention and Next.js 16 upgrade guide. Provenance: framework documentation for middleware.ts -> proxy.ts, Node.js runtime behavior, and why Proxy cannot replace per-Server-Function authorization..server filename suffixes, $lib/server, private environment module restrictions, and import-chain errors that prevent server-only code from reaching browser-running code.+page.server.js / +layout.server.js, server-only load functions, universal-vs-server load boundaries, and devalue serialization of server-load return data.experimental_taintObjectReference and experimental_taintUniqueValue. Provenance: primary React documentation for optional taint guardrails and their caveats.FormData.append(). Provenance: platform reference for string/blob values, repeated fields, and automatic stringification of non-string non-Blob values.react-server-dom-* versions.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.