@stopnetic/js
v0.1.11
Published
Server-side Stopnetic guard for JavaScript websites, Node.js and Express-compatible servers.
Maintainers
Readme
@stopnetic/js - JavaScript and TypeScript website guard
Automatic outage recovery (0.1.11+)
The shared guard uses a five-second default policy/verification deadline. Explicit
timeoutMs settings still apply. Browser document access-check failures return a
standalone HTTP503 retry page with one automatic reload and a manual link. The
reload waits at least three seconds, respects bounded Retry-After hints and has
a five-minute per-tab cooldown. Blocked session storage falls back to manual retry.
Protected content remains withheld; API/fetch/RSC/prefetch, HEAD and POST callers
are not replayed. Reporting retains its separate short deadline.
This comes from the shared SDK used by the server adapters. Update to0.1.11 or newer, rebuild and redeploy; no application retry-page snippet is required. Publish this SDK release before consumers install it from npm.
Unreleased interactive verification: server adapters accept
interactiveVerification: true for dashboard-selected checkbox flows and APIs
requiring an interactive-flow session. Requires a matching service/new SDK
release after0.1.8. It can be automated; completion is not human identity proof.
See setup and enforcement.
Unreleased private client previews: passwordProtection: true enables the same
dashboard-managed whole-site/selected-path password gate as the Next adapter.
No client account is needed. Requires a matching service/new SDK release.
See setup, coverage and revocation.
Unreleased data budgets: server adapters accept requestCost(request) for a
validated upper bound on returned records/action units. Verified-route policies
reserve units before the handler, including for already verified sessions.
Initial scope requires trusted Vercel network protection in block mode and the
updated service/new SDK release. See setup and limits.
Unreleased router-alias fix: Express middleware now matches policy rules using its actual root router's case/slash semantics. This closes verified-session route bypasses through uppercase or trailing-slash aliases in Express4/5. Requires the updated service before the next SDK release; missing signed support fails closed. Mixed nested routing and mounted sub-apps require a uniform supported setup; custom mappings still need handler-level authorization. See reproduction, compatibility and limits. This fixes a content-authorization bypass, not stealth-agent classification.
Repository increment after0.1.4: optional diagnostics: true and
connectionInsights: 'vercel' add correlated references and daily scoped JA4
observations. Server helpers read STOPNETIC_DIAGNOSTICS and
STOPNETIC_CONNECTION_INSIGHTS. Defaults false/none; matching service/new SDK
required. Configuration and measurement guide.
Experimental repository increment: interactionTrap: 'monitor' | 'block' adds a
server-backed hidden-action observation. Default off; requires the matching new
service and SDK release after 0.1.3. Server helpers also read
STOPNETIC_INTERACTION_TRAP. Invoking the action can withhold that session, but
agents that ignore it still pass the current stealth benchmark.
See configuration and measured limits.
First-party automation mode (unreleased)
Repository server option botProtection: 'monitor' | 'block' defaults to 'off'.
Environment-based server helpers also support STOPNETIC_BOT_PROTECTION.
This requires the matching service and a new SDK release after 0.1.2. Client
automation evidence can be hidden; stealth agents may still pass. Public routes
stay public. No external detection provider is used.
See configuration, tests and limits.
Prepared protocol 2 adds browser-report consistency, fresh server-verified SHA-256 work and a per-binding verification budget. Modern secure-context Web Crypto is required for block mode. Fully concealed browsers doing valid work may still pass; monitor records evidence without these additional denials.
Additional prepared hardening observes non-native webdriver getter replacements.
For Vercel-hosted server guards, optional botClientSource: 'vercel' adds shared
network budgets that survive cookie clearing and SDK worker changes. Use monitor
first. It needs actual trusted Vercel ingress and the matching service; missing
metadata withholds private access. The raw IP remains on the customer server;
a daily, site-scoped HMAC key is used only for short-lived counters, not events.
Server helpers accept STOPNETIC_BOT_CLIENT_SOURCE=vercel. Default source is none; no customer database or external detector is required.
Native-looking low-volume stealth and network rotation can still pass. See the
linked protection guide for thresholds, shared-network risks and test results.
Prepared requireApprovedAccount: true plus a server-side authorizeVisitor
callback can additionally require the customer's currently approved account for
every protected request. This is an integration contract, not a login/passkey
service. See approved access.
Verification screen update (unreleased)
The shared guard now prepares a centered Stopnetic-branded verification screen, embedded logo, loading indicator and failure/timeout retry message. It uses no external assets and keeps protected content withheld. Mobile, reduced-motion and JavaScript-disabled browsers have appropriate presentation. Websites need the next SDK release and a rebuild/redeployment for this update; publishing or deploying the Stopnetic dashboard alone does not change an installed SDK.
Unreleased country controls: server options countryBlocking: true and a trusted
countrySource enable the compatible protocol. The /server environment helper
also reads STOPNETIC_COUNTRY_BLOCKING=true. Deploy and test guarded requests
before enabling the country policy in the dashboard. Remove local publicPaths
bypasses and secure your ingress/origin. Published 0.1.0 lacks this feature.
See setup, recovery and coverage limits.
Stopnetic checks access on your server before sending pages, files or API data. Use it with Node.js and Express, with frontend examples for Angular, React, Vue, Svelte, Solid and plain JavaScript. No frontend framework is a required dependency. Your hosting server must run Node.js 22 or newer; browser-only JavaScript does not enforce protection on its own. Keep your existing SSR or static rendering.
Optional traffic insights (unreleased)
The working tree supports trafficInsights: true and
countrySource: 'none' | 'vercel' | 'cloudflare' in server guard options. The
@stopnetic/js/server helpers also read STOPNETIC_TRAFFIC_INSIGHTS=true and
STOPNETIC_COUNTRY_SOURCE=none from the website's server environment. These
settings are optional and collection is off by default. Published 0.1.0 does
not send these fields; use a compatible future release or local packed SDK.
Only a country code/source and broad device category are sent. No raw IPs or full user-agent strings leave the server for this feature, and no network location lookup is made. Unsupported sources/devices remain Unknown. Reported metadata is approximate and does not authorize requests or identify people/bots.
Explicitly trust a country source only on the correct hosting ingress. For Cloudflare, prevent direct-origin access or strip and rewrite visitor-supplied headers. Deploy compatible service ingestion before updating and deploying the website's guard. Disabling collection stops new context; stored context follows the event retention/deletion policy.
Install
Run inside your website project:
npm install @stopnetic/jsFor Next.js 16 App Router, use @stopnetic/next.
This release includes server/browser helpers, framework-specific SSR instructions, complete optional frontend examples, Netlify/Vercel/Node setup commands and bounded public-key parsing for raw, escaped or JSON-quoted public PEM values. Existing unmodified generated guides can migrate safely; customized files require merging.
Read the complete generated guide for SSR, static builds, client navigation, API protection and runtime restrictions. Validate the actual deployed routing, caching and provider configuration before production use. Browser verification does not prove human identity.
Framework implementation examples
SSR apps use the SSR framework selector, not the optional browser-demo
selector. Framework SSR instructions
and examples/ssr contain matching server code:
| SSR app | Server connection |
| --- | --- |
| Angular | AngularAppEngine Fetch entry or Express before AngularNodeAppEngine |
| React Router | Existing Express entry; single-fetch .data aliases use page policy |
| Custom React / Vue | Guard before the existing Node renderer; keep hydration |
| Nuxt 3/4 (Nitro 2/H3 1) | Root Node server middleware before rendering/APIs |
| SvelteKit | handle before resolve(event); canonical page check for data requests |
| Astro | onRequest before next() with an existing Node adapter |
| SolidStart 1 / 2 | Version-specific request middleware before continuation |
| Custom Node / plain JavaScript | Root Express middleware or complete Fetch wrapper |
| Next.js | Dedicated @stopnetic/next guide |
Merge existing files/context, preserve public routes and keep protected output out of prerender/static/CDN caches. These manual snippets are not automatic framework installers. Node compatibility does not validate a live provider. Only dashboard rules decide verification; Angular-specific flags are not instructions for other frameworks. React/Vue libraries without a server use the static option.
To test server continuation contracts after npm run build, run
node tests/prepare-ssr-guide-tests.mjs, then node tests/ssr-guide-check.mjs.
Dependencies remain in ignored test-results. Native H3 1/2 and Express exercise
signed access, control isolation, data aliases and unread POST bodies. Astro,
SvelteKit and SolidStart factory imports use contract shims, not full framework
builds. Angular's separate fixture compiles/renders real SSR. Live routing,
prerender/cache behavior and complete framework/provider builds still need validation.
The guide and examples/frontend contain complete, locally tested OPTIONAL
browser-only SPA examples. The default document-navigation integration needs
the server guard and normal links, not these full-layout browser gates. Keep SSR
and hydration; do not apply the browser demos to an existing SSR root. A runtime
incompatibility needs a deployment decision, not an automatic conversion to CSR.
Dashboard policies decide which paths require verification; demo paths are not
instructions to protect every page. SPA checks still evaluate public targets
asynchronously and can show a brief empty pending state. The examples are:
| Frontend | Example | | --- | --- | | React | BrowserRouter, full-layout StopneticBoundary, Routes and effect-based API loading | | Angular | Route guard/service, layout signals, RouterOutlet and HttpClient | | Vue | Vue Router beforeEach/afterEach, gated App.vue and API loading | | Svelte | onMount, History API/popstate navigation and abort cleanup | | Solid | Router root, useLocation, reactive Show boundary and API loading | | Plain JavaScript | DOM rendering, checked navigation and API loading |
Each example includes initial access, later navigation, verification redirect,
empty pending content, denied UI and cancellation. Backend page/API guards remain necessary.
No framework library is added as a required SDK dependency. The generated guide
embeds the source; use its file paths and helper instructions when copying it.
The dashboard's frontend selector shows the same files. For maintenance, run
npm run build in this SDK, then npm run sync:guides in dashboard.
To rerun the frontend compilation/browser fixtures, build the SDK and run
node tests/prepare-framework-tests.mjs in this directory. This packs the SDK
and installs pinned test-only libraries inside ignored test-results. Then run
.venv/Scripts/python.exe integrations/javascript/tests/framework-browser-check.py
from the repository root. The Angular fixture uses an existing Angular install:
angular-guide-check.py <project-path>; it copies source into the workspace,
compiles the examples and tests browser/Node SSR without changing that project.
For local testing, build and pack from this repository:
cd integrations/javascript
npm run build
npm packInstall the resulting stopnetic-js-0.1.0.tgz into the website project using
npm install <absolute-path-to-tarball>. The installed package has no Next.js or
other runtime dependency. The build packages the same signed decision implementation
as the Next.js adapter. Keep @stopnetic/next for its Next.js 16 guided installer.
Reusable client/server setup
Follow the hosting guide for the full Netlify, Vercel or Node procedure, server settings, page HTTP 404 enforcement, frontend/API wiring and diagnosis. The dashboard's Integration and Documentation pages offer the same provider choices and complete copyable page-guard examples.
These commands and subpath exports are included in this release. Older 0.0.1-pilot installations must upgrade before using them.
Run setup at the app's package.json, choose hosting, and preview before writing:
npm exec --no -- stopnetic-js init --platform netlify --dry-run
npm exec --no -- stopnetic-js init --platform netlifyUse --platform vercel or --platform node for the other adapters. Netlify/Vercel
configuration files can select a platform automatically; ambiguous projects need
explicit selection. Next.js projects use @stopnetic/next. This SDK needs a
Node.js 22+ runtime; it does not run as a Deno/Workers/Edge function.
Setup prepares a server control endpoint, .env.stopnetic.example, an ignored
.env.local cookie secret, a setup manifest and stopnetic-client-guide.md.
It does not install dependencies, edit your React app, overwrite existing files,
contact providers or deploy. Changed generated files require manual merging.
Custom Netlify base directories require review; custom functions directories are
preserved. Vercel configuration is preserved. Put runtime settings in the hosting
provider's server/Functions environment; plain netlify.toml build env is not a
Functions secret store. Copy the generated cookie secret without committing it.
Wire the server page guard and EACH protected API following the generated guide.
Browser route checks are optional for retained SPA navigation.
Run npm exec --no -- stopnetic-js check after setting credentials (--local-http
for a loopback local test). This check validates configuration and generated files
offline; it cannot prove deployment routing, API coverage or browser integration.
Deploy once, then dashboard changes apply on subsequent guarded requests.
Test locally with netlify dev or vercel dev, which must run both the frontend
and server functions; Vite alone does not provide the generated server endpoints.
For Node, use node --env-file=.env.local your-server.mjs or your existing
server environment loader. Production credentials belong in runtime settings.
Shared browser startup and Node SSR
The generated framework-independent guide defaults to server document navigation: leave browser startup/layout intact, use normal links, and let the server apply dashboard policies. It also gives an OPTIONAL browser startup gate and separate OPTIONAL SPA router demos. Do not combine the alternatives or apply browser-only gates to an SSR hydration root.
For Node SSR use the generated Express helper before static files/rendering,
or createStopneticAppHandler around a Fetch-style application entry (0.0.3).
The wrapper handles access/verification control endpoints and guards pages and
APIs before calling the renderer. It preserves context, streams and request
bodies. Framework hooks with another signature still need their continuation
adapted; accepting Request/Response does not make an Edge runtime supported.
The guide includes Angular SSR/rendering and client router examples. Angular SSR compiled with the installed Angular 22 dependencies and served real HTML after verification in an isolated local fixture. That does not validate a live provider deployment. Netlify's automatic Angular SSR uses Edge; the Node SDK has no drop-in integration for that runtime. Preserve SSR and obtain a supported Node deployment/gateway decision instead of silently converting the app. Do not publish protected prerendered HTML. For other frameworks use the same Node server entry and verify their actual deployed runtime. React's boundary remains an optional convenience.
Optional React Router boundary
The optional React entry uses Hooks. The core/browser modules need no React. Use this only for explicitly retained browser SPA navigation, inside BrowserRouter, around the full target layout. Server document navigation does not require it:
import { useLocation } from 'react-router-dom';
import { StopneticBoundary } from '@stopnetic/js/react';
function RouteGuard({ children }) {
const location = useLocation();
return <StopneticBoundary path={location.pathname + location.search} pending={null}>
{children}
</StopneticBoundary>;
}
// <BrowserRouter><RouteGuard><Layout/></RouteGuard></BrowserRouter>
// Layout includes the target header, routes and footer.The boundary hides children while checking, ignores canceled/stale checks and
redirects to same-origin verification when required. Block/outage displays a
denial; customize pending and blocked with your UI. Vue, Angular, Svelte and
plain JS can await checkAccess(path) from @stopnetic/js/browser and use
result.allowed, result.reason and result.verificationUrl in their router.
This is a UI check, never an authorization credential for a data request.
Netlify / Vercel APIs
Import server helpers only in server files. Wrap every protected function before it loads/returns data, keeping your application's own login/data permissions:
import { withStopnetic } from '@stopnetic/js/server';
const api = withStopnetic(async request => {
// Enforce application permissions, then retrieve private data here.
return Response.json({ example: true });
});
// Netlify function:
export default api;
export const config = { path: '/api/private', preferStatic: false };
// Vercel api/private.mjs instead: export default { fetch: api };Netlify's default /.netlify/functions/ aliases are rejected by this wrapper
so they cannot bypass rules on a custom path. Use custom config.path endpoints
in your frontend and Policies. Keep the generated /api/stopnetic/access and
/api/stopnetic/verify endpoints reachable before any SPA catch-all. The access
endpoint checks the requested page using signed decisions, establishes browser
verification and redirects back to the same origin. It never returns private
content or an API authorization token. API wrappers independently check every
request and preserve ordinary request bodies. Denials/outages return empty
404/503 responses; approved data responses use private, no-store.
For a real HTTP 404 on a Netlify SPA page, use the optional page-serving function
in examples/netlify-spa-pages.mjs and follow
docs/netlify-spa-page-requests.md.
It checks signed access before reading the bundled entry HTML. The browser
boundary alone cannot change the initial document's HTTP status. This example
uses the server helpers included in this release; the setup CLI does not install it
automatically. Keep static bundles public and private data in guarded APIs.
For Vercel page responses, the manual Node routing middleware in
examples/vercel-spa-middleware.mjs checks pages before static HTML, using
@vercel/functions and the existing SDK. The CLI's generated Vercel control
function alone does not guard page documents. Follow the hosting guide for
middleware, API/asset routing and SPA rewrite configuration. Local signed tests
cover it; live Vercel deployment validation remains required.
Node servers on other hosts
Import installStopnetic from generated stopnetic.server.mjs, then call
installStopnetic(app) before Express parsers, static files, SPA fallback and
API handlers. This checks the control endpoints and the rest of the server.
It can run on any host supporting Node.js 22+, not just Netlify/Vercel. For a
custom Fetch-based Node framework, use the shared createStopneticAccessHandler
and withStopnetic in its request hooks. Existing low-level adapters remain
available below with their original default-blocking behavior.
Rules and the protection boundary
The generated setup opts into dashboard-managed mode: unmatched guarded requests are public. Add explicit Page / Automatic browser flow rules and API / Require verified session rules. Keep private data exclusively in guarded APIs. An API fetch does not start browser verification; it needs the established same-origin cookie. Static HTML/JavaScript, routes without a browser boundary, external APIs, unwrapped handlers, alternate hosts and previously loaded content are not made private by this setup. No installer can hide content already in a public bundle. Browser automation can still complete verification. Test your actual provider, custom-domain routing, redirects, cookies, caches and direct API access before rollout. Hosting contracts are simulated locally; live provider tests are pending.
Which installation fits my site?
| Site | Where to install |
| --- | --- |
| Angular, Vue, React, Svelte SPA or plain HTML/JS | Node/Express server serving its built output, before static files and SPA fallback |
| Existing Node/Express server, including Express 4 and 5 | First root middleware, before body parsers, routes and data handlers |
| Node-hosted framework with Web Request/Response hooks | createStopneticHandler before the framework renders or serves data |
| Next.js 16 App Router | Published @stopnetic/next and its guided installer |
| Older Next.js with a customer-owned Node server | Generic middleware before the Next request handler; native older Next installers are not supplied |
| Static-only hosting / browser-only scripts | Needs a server or compatible edge/proxy in front, or a separately protected content API |
Do not import this SDK in browser components or add a public <script> containing
your installation key. Hiding a screen cannot protect HTML, JS bundles or data that
the hosting provider already serves publicly. This package uses node:crypto; it
is not a Cloudflare Workers/browser/Edge-runtime adapter. Framework-native SSR
adapters and serverless platform wiring need their own deployment validation.
Server configuration
Add a site in Stopnetic, verify its domain, and create an installation key in Integration. Site ID identifies your website; the private installation key authenticates it. Copy the service public signing key from Integration too. Set these server-only environment variables, using your actual URLs and keys:
STOPNETIC_WEBSITE_ORIGIN=https://www.example.com
STOPNETIC_SITE_ID=<generated-site-id>
STOPNETIC_API_URL=https://<your-stopnetic-service>/api/integration/decision
STOPNETIC_API_TOKEN=<your-one-time-installation-key>
STOPNETIC_DECISION_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n<service-public-key>\n-----END PUBLIC KEY-----\n"
STOPNETIC_COOKIE_SECRET=<separate-random-secret-at-least-32-characters>Generate the cookie secret with
node -e "console.log(require('node:crypto').randomBytes(32).toString('base64url'))".
Never use frontend prefixes such as VITE_, NEXT_PUBLIC_ or REACT_APP_ for secrets.
The optional STOPNETIC_DECISION_ADDITIONAL_PUBLIC_KEYS is a JSON array of at most
two explicitly trusted public PEMs for staged signing-key rotation.
The origin is the public website's protocol and hostname, with an optional port.
No /home, credentials, query or fragment. HTTPS is required except on localhost.
Configure your TLS proxy to preserve the public Host header and keep the backend
private. The adapter uses the configured origin and never trusts forwarded host or
protocol headers. HTTPS origins always produce secure cookies; HTTP localhost
origins automatically use development cookies.
Node/Express middleware
import express from 'express';
import { createStopneticMiddleware, parseAdditionalDecisionPublicKeys } from '@stopnetic/js';
const app = express();
app.set('case sensitive routing', true);
app.set('strict routing', true);
app.use(createStopneticMiddleware({
origin: process.env.STOPNETIC_WEBSITE_ORIGIN,
siteId: process.env.STOPNETIC_SITE_ID,
apiUrl: process.env.STOPNETIC_API_URL,
apiToken: process.env.STOPNETIC_API_TOKEN,
decisionPublicKey: process.env.STOPNETIC_DECISION_PUBLIC_KEY?.replaceAll('\\n', '\n'),
additionalDecisionPublicKeys: parseAdditionalDecisionPublicKeys(process.env.STOPNETIC_DECISION_ADDITIONAL_PUBLIC_KEYS),
cookieSecret: process.env.STOPNETIC_COOKIE_SECRET,
}));
// Your existing application follows the guard.
app.use(express.json());
app.post('/api/private', yourExistingApiHandler);
app.use(express.static('dist', { dotfiles: 'deny', cacheControl: false }));
// Add your existing guarded SPA fallback here.
app.listen(8080);Install express separately in your website if needed. The repository includes
integrations/javascript/examples/express-server.mjs for a complete SPA server.
Serve only the build output directory (dist, build, or Angular's browser output),
never your project root. A framework-independent fallback after your static middleware:
app.use((req, res) => {
if (req.method === 'GET' && req.get('accept')?.includes('text/html')
&& !req.path.startsWith('/api/') && !req.path.split('/').some(p => p.startsWith('.'))) {
res.sendFile('index.html', { root: absoluteBuildDirectory, cacheControl: false });
} else res.status(404).end();
});Place the guard first at the root, before body parsers and before anything can
send bytes. It checks every request except explicitly configured publicPaths.
Ordinary POST bodies stay intact for your application's parser. The same middleware
handles /__stopnetic/verify; do not mount it only on /members.
With Node's built-in HTTP server, the same middleware works without Express:
import { createServer } from 'node:http';
const protect = createStopneticMiddleware(options);
createServer((req, res) => {
void protect(req, res, () => existingApplication(req, res)).catch(() => res.destroy());
}).listen(8080);For an older CommonJS application, initialize at startup with
const { createStopneticMiddleware } = await import('@stopnetic/js') inside an async
function. Only the server runtime needs to meet the minimum version; the frontend
framework can remain unchanged.
Request/Response hook
import { createStopneticHandler } from '@stopnetic/js';
const protect = createStopneticHandler(options); // same options, including origin
export async function handle({ request, resolve }) {
return protect(request, checkedRequest => resolve(checkedRequest));
}Wire this function to the Node-hosted framework's actual request hook. For SvelteKit,
the hook receives an event, so use
protect(event.request, () => resolve(event)) in hooks.server.js. The request URL
must contain the configured public origin. Configure the framework's trusted proxy
settings explicitly. This is a generic hook example, not a tested matrix of all
SvelteKit/Nuxt/framework releases.
Policies, failures and hosting
In Policies, choose public, automatic browser flow, verified session, or blocked routes. Automatic flow serves a short verification page first, then a cookie-bound session; requests from ordinary fetchers and non-navigation clients get no protected bytes. Browser-capable automation may complete the challenge. Your application must still enforce its own sign-in, membership and purchase permissions. Entitlement rules are not implemented and withhold content.
Both adapters automatically route verification, bound its body to 4 KiB and five
seconds, and reject foreign origins. Protected requests fail closed with empty
404/503 responses on denial, malformed signed decisions or service outages. Approved
application responses receive Cache-Control: private, no-store; middleware users
must keep that header in their downstream handlers. Prevent CDN, prerendered files,
service workers or another origin from serving protected content before the guard.
WebSockets/upgrades are not covered; protect them separately or disable them.
By default all paths are checked, including assets and APIs. Optional local
publicPaths: ['/health', '/public/*'] bypass the policy service even during outages.
Use only paths deliberately intended to remain public. Ambiguous encoded separators,
dot segments, encoded ASCII characters, repeated slashes and malformed targets are
rejected by the Node middleware. Policy paths are case sensitive. Match your router's
case/slash behavior to the policies, cover directory-index and other aliases, and
keep the service's default blocking behavior. Avoid a broad public catch-all that
could expose an alternative path to protected content.
An installed guard's first valid request starts the site's account trial. Key rotation/revocation and domain/account/subscription gates are checked by the service. Access reports are best effort and cannot establish delivery guarantees. With the updated dashboard, the SDK links same-page challenges, verification and the first reported access result. The Challenge outcomes panel distinguishes allowed, blocked, waiting, not completed and verified without an access result; older evidence may be unavailable. It does not identify humans or bots. Install the updated SDK on your server after deploying the dashboard update.
Validate your actual hosting, cache rules, browser flow, API paths and provider configuration before onboarding paying customers.
Maintainer checks and release
From this folder: npm ci, npm test, npm run test:package. Tests include real
Node HTTP traffic, Express 4/5 static/SPA/API serving, challenges, body preservation,
signed decision failures and package consumption from ESM, CommonJS and TypeScript.
The tarball includes runtime/types in dist, the setup CLI in bin, the public
frontend/SSR examples, README, MIT license and manifest. No secrets, tests, build
scripts or dashboard are published. Build from the repository
before packing; published consumers do not need the repository or build scripts.
Publishing is a separate owner step after these checks:
npm publish --access public --tag latest --registry=https://registry.npmjs.org/Publishing this package does not update the existing @stopnetic/next release.
