# WRNexusJS — Security & Power Improvement Plan Prepared for WorkRoot · covers `E:\WrNexus` (framework v0.8.4) This plan is scoped and sequenced the way the repo's own roadmap docs are (`ROADMAP-V1.md`, `IMPLEMENTATION-ROADMAP-0.8.md`): version-gated phases, one package/file set per item, with implementation, tests, and doc updates called out per item so each phase can ship as a real release with a `bun run validate:0.X` gate, like 0.7 and 0.8 did. Every proposed API below follows conventions already in the repo (workspace `package.json` shape, `Middleware`/`Context` typing from `@wrnexus/core`, ASVS row format in `docs/SECURITY-ASVS-5.md`, audit-sink pattern from `packages/authz/src/audit.ts`) rather than inventing new patterns. --- ## Phasing overview | Phase | Version | Theme | New packages | Est. effort | | ----- | ------------- | ----------------------------------------------- | ---------------------------------------------- | ----------- | | 1 | 0.8.5 (patch) | Security default fixes, no breaking changes | none | 1–2 weeks | | 2 | 0.9 | Distributed rate limiting + live security audit | `@wrnexus/ratelimit-redis` | 2–3 weeks | | 3 | 0.10 | Product-critical DX packages | `@wrnexus/mail`, `@wrnexus/flags` | 4–6 weeks | | 4 | 0.11 | Search + AI pairing | `@wrnexus/search` | 3–4 weeks | | 5 | 0.12 | Enterprise/government readiness | SAML in `@wrnexus/auth`, `@wrnexus/compliance` | 5–7 weeks | | 6 | 1.0 | Monetization + ecosystem | `@wrnexus/billing`, public release strategy | 6–10 weeks | Total: roughly 6–8 months at a small-team pace, phased so each release is independently shippable and dogfoodable on `workroot.in` / `wrnexusjs.dev` / the WRNexus SaaS itself before the next phase starts. --- ## Phase 1 — v0.8.5: Security default fixes No new packages. Pure hardening of existing code, all changes are config-default flips, so they are non-breaking for anyone who already sets these fields explicitly and only change behavior for people relying on the current default. ### 1.1 Trusted Types default allowlist **File:** `packages/core/src/headers.ts`, function `applyTrustedTypesDirectives` **Problem:** `policyNames` defaults to `["*"]` in production, so any script — including an injected one — can register a Trusted Types policy. This defeats the XSS mitigation Trusted Types exists for. **Change:** ```ts // Before const policyNames = typeof trustedTypes === "object" && trustedTypes.policyNames?.length ? trustedTypes.policyNames : ["*"]; // After const policyNames = typeof trustedTypes === "object" && trustedTypes.policyNames?.length ? trustedTypes.policyNames : ["wrnexus", "default"]; ``` Update the `TrustedTypesConfig.policyNames` doc comment to explain the new default and how to opt back into `["*"]` for apps with third-party extensions that need it. **Tests:** update `packages/core/test/headers.test.ts` assertions that currently expect `*`. **Docs:** update the "Production configuration baseline" block in `docs/SECURITY-PERFORMANCE-0.7.md` and add a migration note to `docs/UPGRADE-0.8.3.md`-style upgrade doc for 0.8.5. ### 1.2 HSTS `preload` default **File:** `packages/core/src/headers.ts`, function `serializeHsts` **Problem:** `preload: true` is on by default whenever `mode === "production"`. Preload-list submission is a long-lived commitment (removal takes months across browsers); defaulting it on for every production build is a footgun for teams not ready to guarantee HTTPS on every subdomain permanently. **Change:** ```ts function serializeHsts(config: HstsConfig): string { const parts = [`max-age=${config.maxAge ?? 31536000}`]; if (config.includeSubDomains !== false) parts.push("includeSubDomains"); if (config.preload === true) parts.push("preload"); // was: !== false return parts.join("; "); } ``` `includeSubDomains` can stay default-on (safe, reversible); only `preload` flips to opt-in. **Tests:** update `packages/core/test/headers.test.ts`. **Docs:** update `SECURITY-ASVS-5.md` row for `v5.0.0-3.4.1` evidence note and the production config baseline example (explicitly show `hsts: { preload: true }` as something apps opt into, with a one-line warning comment). ### 1.3 Live security-header verification (`--url` mode) **File:** `packages/cli/src/security-command.ts` **Problem:** `securityAudit()` already exists and is solid — it loads the local app config, builds a synthetic request/response, and runs `withSecurityHeaders` to check what headers _would_ be emitted. It never checks what a _deployed_ site is actually serving, so a misconfigured reverse proxy, missing env var, or config drift between local and production is invisible to `wrnexus security audit` today. **Change:** add a second code path that takes a URL instead of an app root: ```ts export interface SecurityAuditOptions { appRoot?: string; /** Fetch a live deployment and audit its actual response headers instead of a local config. */ url?: string; } export async function securityAudit(options: SecurityAuditOptions): Promise { const headers = options.url ? await fetchLiveHeaders(options.url) : await securityHeaders(resolve(options.appRoot ?? ".")); // same checks[] logic runs against either header source ... } async function fetchLiveHeaders(url: string): Promise> { const res = await fetch(url, { method: "HEAD", redirect: "manual" }); return Object.fromEntries(res.headers.entries()); } ``` CLI surface: `wrnexus security audit --url=https://workroot.in` — same `SecurityAuditCheck[]` table output as the local mode, so it's a drop-in mental model for anyone who's already used the local version. **Tests:** `packages/cli/test/security-command.test.ts` — mock `fetch`, assert the same check IDs run against a header map built from a fake `Response`. **Docs:** update `SECURITY-ASVS-5.md`'s intro line ("Run `bun run security:asvs` ... `wrnexus security audit`") to mention the `--url` mode explicitly. --- ## Phase 2 — v0.9: Distributed rate limiting ### 2.1 `@wrnexus/ratelimit-redis` **Problem:** `packages/core/src/ratelimit.ts` is honestly documented as process-local by default, with a clean `RateLimitStore` interface for swapping in a shared store — but no first-party implementation ships. Every team running more than one instance has to write their own Redis `INCR`/`PEXPIRE` bucket store before rate limiting actually works in production. **New package layout** (mirrors `packages/captcha`'s `stores/redis.ts` pattern, which already exists for CAPTCHA — this is literally copying a pattern you've already built once): ``` packages/ratelimit-redis/ package.json src/ index.ts # createRedisRateLimitStore() client.ts # thin ioredis/bun-redis wrapper, injectable client test/ store.test.ts # against a real or mocked Redis README.md ``` **API:** ```ts import { createRedisRateLimitStore } from "@wrnexus/ratelimit-redis"; import { rateLimit } from "@wrnexus/core"; const store = createRedisRateLimitStore({ url: process.env.REDIS_URL! }); app.use(rateLimit({ store, max: 100, windowMs: 60_000 })); ``` Implementation: one atomic Lua script (`INCR` + conditional `PEXPIRE`) to avoid a race between the increment and the expiry set — same correctness bar as the in-memory store's atomicity within a single process. **package.json:** ```json { "name": "@wrnexus/ratelimit-redis", "version": "0.9.0", "type": "module", "main": "./src/index.ts", "exports": { ".": "./src/index.ts" }, "dependencies": { "@wrnexus/core": "workspace:*" } } ``` **Tests:** `store.test.ts` covering window rollover, concurrent-hit correctness (fire N parallel `hit()` calls, assert exact count), and store failure fallback behavior (Redis down → fail open with a warning log, documented explicitly so nobody is surprised). **Docs:** add a row to `SECURITY-SUPPORT-MATRIX.md` under a new "Rate limiting" area, and link it from the `RateLimitStore` doc comment in `packages/core/src/ratelimit.ts`. --- ## Phase 3 — v0.10: Product-critical DX packages ### 3.1 `@wrnexus/mail` **Problem:** there's no first-party way to actually send email. The queue example (`app/queues/welcome-email.ts`) shows _scheduling_ an email job but nothing implements delivery. **Package layout:** ``` packages/mail/ package.json src/ index.ts send.ts # sendMail(), core envelope type providers/ resend.ts ses.ts postmark.ts smtp.ts dev-inbox.ts # captures mail in dev instead of sending; wrnexus dev shows it in DevToolbar components/ # optional .wrn email-template partials, reusing the compiler test/ send.test.ts dev-inbox.test.ts README.md SECURITY.md ``` **API (mirrors the `SafeUrlPolicy`/provider-adapter shape from `@wrnexus/captcha`'s providers):** ```ts export interface MailProvider { send(message: MailMessage): Promise; } export interface MailMessage { to: string | string[]; from: string; subject: string; html?: string; text?: string; replyTo?: string; headers?: Record; } export function createMailer(provider: MailProvider): { send(m: MailMessage): Promise }; // providers/resend.ts export function resendProvider(opts: { apiKey: string }): MailProvider; ``` **Dev-mode behavior:** when `mode !== "production"`, `createMailer` wraps any provider with `devInboxProvider()`, which stores messages in memory and surfaces them in the DevToolbar (new panel, same pattern as the existing SQL/queue/realtime DevToolbar providers listed in `SECURITY-PERFORMANCE-0.7.md` §18) instead of actually sending — this is the single highest-value DX win in this package, since it removes the "did my email actually work" debugging loop entirely. **Security notes for `SECURITY-SUPPORT-MATRIX.md`:** framework guarantees provider-secret handling stays server-side and never serializes into hydration payloads (reuse the existing serialization redaction from `@wrnexus/security/serialization.ts`); app/operator responsibility covers SPF/DKIM/DMARC DNS records and provider account reputation. **Effort:** ~2–3 weeks for `resend` + `smtp` providers, dev inbox, and DevToolbar panel; `ses` and `postmark` can follow as a fast-follow since they share the same `MailProvider` interface. ### 3.2 `@wrnexus/flags` **Problem:** no feature-flag primitive. Every team building past MVP eventually needs gradual rollout, and right now they'd bolt on a third-party SDK with no integration into WRNexusJS's `.wrn` reactivity or SSR model. **API:** ```ts export interface FlagsStore { get(key: string, ctx: FlagContext): Promise; } export function createFlags(store: FlagsStore): { isEnabled(key: string, ctx: FlagContext): Promise; variant(key: string, ctx: FlagContext): Promise; }; // stores/memory.ts, stores/db.ts (reuses @wrnexus/db), stores/percentage.ts (deterministic hash rollout) ``` `.wrn` integration: expose `load server { const enabled = await flags.isEnabled("new-dashboard", ctx) }` so flags flow into `props`/`state` the same way `load server` results already do — no new compiler syntax needed, just a helper package. **Effort:** ~1–2 weeks; the DB-backed store reuses `@wrnexus/db` migration patterns already in the repo, so most of the work is the percentage-rollout hashing and the DevToolbar panel. --- ## Phase 4 — v0.11: Search, paired with `@wrnexus/ai` ### 4.1 `@wrnexus/search` **Problem:** no first-party search story, despite already having `@wrnexus/ai` for embeddings/LLM calls — search and RAG are the two things most SaaS apps need `@wrnexus/ai` _for_, so this is the natural pairing package. **Package layout:** ``` packages/search/ src/ index.ts adapters/ postgres-fts.ts # tsvector + GIN index helpers, generated migration sqlite-fts5.ts pgvector.ts # embeddings via @wrnexus/ai, cosine-distance query helper typesense.ts # optional hosted adapter test/ ``` **API:** ```ts export function createSearchIndex(adapter: SearchAdapter, table: string, columns: string[]); const results = await search.query("customer onboarding", { limit: 10 }); // pairs directly with @wrnexus/ai import { embed } from "@wrnexus/ai"; const vector = await embed(text); await search.upsertVector(id, vector); ``` **Effort:** ~3–4 weeks — Postgres FTS and SQLite FTS5 adapters first (no new infra dependency, reuses `@wrnexus/db`'s existing driver abstraction), pgvector and Typesense as fast-follows. --- ## Phase 5 — v0.12: Enterprise & government readiness This phase is prioritized specifically because `workroot.in` markets to "enterprises & governments" — these two items are the actual procurement blockers for that buyer, more than any generic framework feature would be. ### 5.1 SAML support in `@wrnexus/auth` **Problem:** OAuth/OIDC is implemented (per `SECURITY-SUPPORT-MATRIX.md`), but large-enterprise and government IT departments frequently mandate SAML 2.0 specifically for SSO procurement, regardless of OIDC's technical merits. **Location:** `packages/auth/src/saml/` — new subdirectory alongside the existing OAuth/OIDC code, same `AuthProvider` interface shape so it plugs into the existing account/session engine (`packages/auth/src/engine.ts`) without a parallel auth system. **Scope:** SP-initiated SSO, signed assertion validation, configurable IdP metadata (Okta, Azure AD, Google Workspace, ADFS as the four IdPs to certify against first — that covers the large majority of enterprise/government IT estates). **Security notes:** reuse the existing `AUTH_SECURITY_EVENT_TYPES` vocabulary for SAML-specific events (assertion replay attempt, signature validation failure, clock-skew rejection) so they flow into the same audit pipeline as every other auth event — no parallel logging system. **Effort:** ~4–5 weeks; SAML assertion validation is fiddly (XML canonicalization, signature wrapping attacks) and deserves a dedicated security review pass before release, not just unit tests. ### 5.2 `@wrnexus/compliance` **Problem:** `packages/authz/src/audit.ts` already has a well-built `AuthzAuditSink` interface with log-injection-safe formatting (`logSafe()`) and memory/console sinks — but no durable, exportable store. For SOC 2-style evidence or India's DPDP Act data-processing records, teams need retained, queryable, exportable audit trails, not console lines. **Package layout:** ``` packages/compliance/ src/ index.ts sinks/ db.ts # durable AuthzAuditSink + auth security-event sink, reusing @wrnexus/db export.ts # CSV/JSON export with retention-window filtering retention.ts # configurable retention policy + scheduled purge test/ ``` **API:** ```ts import { dbAuditSink } from "@wrnexus/compliance"; authz.configure({ auditSink: dbAuditSink({ retentionDays: 365 }) }); const report = await compliance.exportAuditTrail({ from, to, format: "csv" }); ``` This is the package I'd actually build _first_ internally for the WRNexus SaaS itself, since any enterprise/government customer of WRNexus will ask WorkRoot for exactly this evidence during their own procurement review — dogfooding it validates the design before it ships to other developers. **Effort:** ~2 weeks on top of the existing audit-sink groundwork, since most of the hard part (safe event formatting, sink interface) is already done. --- ## Phase 6 — v1.0: Monetization and ecosystem ### 6.1 `@wrnexus/billing` **Problem:** no payments package, despite WRNexus itself being a billed SaaS product — this is the package where dogfooding value is highest. **API:** ```ts export interface BillingProvider { createCheckoutSession(params): Promise<{ url: string }>; verifyWebhook(req: Request): Promise; getSubscription(customerId: string): Promise; } // providers/stripe.ts, providers/razorpay.ts (India-relevant) ``` Webhook verification reuses `@wrnexus/security`'s constant-time comparison helpers (same primitive already used in `packages/core/src/csrf.ts`'s `timingSafeEqual`) for signature checks. Usage metering hooks into `@wrnexus/observability`'s existing counters/gauges rather than a new metrics system. **Effort:** ~5–6 weeks for Stripe + Razorpay, subscription lifecycle, and webhook handling with proper idempotency-key handling (a real source of billing bugs if skipped). ### 6.2 Public release strategy — open-core **Problem:** `@wrnexus/*` is currently private, so nobody outside WorkRoot can `bun install` any of it. This is the actual ceiling on "developer power," not any single missing feature. **Recommended split:** | Tier | Packages | License | | ----------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | | Open (public npm) | `core`, `ssr`, `compiler`, `syntax`, `router`, `store`, `reactive`, `security`, `ui`, `cli`, `dev-server`, `dev-toolbar` | MIT or Apache-2.0 | | Paid/enterprise | `billing`, `compliance`, SAML in `auth`, `authz` advanced policy engine | Commercial license, distributed via the private registry you already run | **Rollout steps:** 1. Audit the `open` tier packages for any WorkRoot-specific secrets/config baked in (`scripts/` already has `generate-sbom.mjs` and `check-public-api.mjs` — extend `check-public-api.mjs` to also flag internal-only references before a package is promoted to the public tier). 2. Publish under the `@wrnexus` npm org with the existing `PUBLISHING.md` process, starting with `core` + `cli` (the minimum to `bunx @wrnexus/cli create my-app` publicly). 3. Public GitHub repo for the open tier only (mirrored from the monorepo via the existing `stage:packages` script's publish pipeline, not a manual copy). 4. Keep `docs/ROADMAP.md`-style public roadmap visible so early external adopters see what's coming. **Effort:** ~4–6 weeks of packaging/licensing/CI work, separate from any new feature work above — this can run in parallel with Phase 5. ### 6.3 Lean into `@wrnexus/ai` + `@wrnexus/mcp` **Problem:** these packages already exist and are ahead of most frameworks, but aren't positioned as a headline feature anywhere in the marketing (`wrnexusjs.dev` homepage doesn't mention AI/MCP at all per the live screenshot taken earlier). **Recommendation:** ship an official MCP server (`packages/mcp/src/index.ts` already has a `stdio.ts` transport — check whether it currently exposes framework introspection, e.g. route listing, `.wrn` component schema, or `wrnexus doctor` output as MCP tools) so agentic coding tools (Claude Code, Cursor, etc.) can scaffold and modify WRNexusJS apps with structured tool calls instead of guessing at the `.wrn` syntax from grepped examples. This is a low-cost, high-differentiation move given how much of the ecosystem is moving toward agent-built apps. **Effort:** ~2–3 weeks to wrap existing CLI commands (`inspect`, `doctor`, `routes`, `generate`) as MCP tools, since the underlying logic already exists in `packages/cli/src`. --- ## Cross-cutting requirements for every phase - Every new/changed package needs a `SECURITY-SUPPORT-MATRIX.md` row (framework guarantee vs. app/operator responsibility) before release, matching the existing table format. - Every new package needs an ASVS evidence row in `SECURITY-ASVS-5.md` if it touches auth, secrets, network requests, or user input — same two-column (implementation evidence / verification evidence) format already used. - `bun run validate:0.X` release gate (`scripts/validate-0.8.mjs`-style) should get a new `validate-0.9.mjs` etc. per phase, following the existing per-minor-version validation script pattern rather than one growing script. - `CHANGELOG.md` entries per release, same format as the 0.8.3/0.8.0 entries already there. - Each phase should ship an example page/route in `examples/basic-app` (mirroring `auth-showcase`, `captcha-showcase`, `i18n-showcase`) — e.g. `examples/mail-showcase`, `examples/billing-showcase` — so the roadmap's own "executable examples" discipline (`ROADMAP-COMPLETION-REPORT.md`) continues. --- ## Suggested sequencing rationale Phase 1 ships first because it's the only phase with zero new surface area — pure default fixes that improve every existing deployment (including `workroot.in` and `wrnexusjs.dev` themselves) without anyone changing their code. Phases 2–4 build developer-facing power in the order teams actually hit the wall (rate limiting under real traffic → needing email → needing flags → needing search once an app has enough data to search). Phase 5 is prioritized ahead of Phase 6 despite being harder, because it directly unblocks revenue-relevant deals in WorkRoot's actual pipeline (enterprise/government procurement). Phase 6's public release is last on purpose — it should launch once there's a stronger package lineup behind it, so the first public impression of `@wrnexus/*` is "batteries-included," not "promising but thin."