# WRNexusJS — Security & Power Improvement Plan

Prepared for WorkRoot · covers `E:\WireJS` (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<SecurityAuditReport> {
  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<Record<string, string>> {
  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<MailResult>;
}

export interface MailMessage {
  to: string | string[];
  from: string;
  subject: string;
  html?: string;
  text?: string;
  replyTo?: string;
  headers?: Record<string, string>;
}

export function createMailer(provider: MailProvider): { send(m: MailMessage): Promise<MailResult> };

// 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<boolean | string | number>;
}

export function createFlags(store: FlagsStore): {
  isEnabled(key: string, ctx: FlagContext): Promise<boolean>;
  variant(key: string, ctx: FlagContext): Promise<string | undefined>;
};

// 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<BillingEvent>;
  getSubscription(customerId: string): Promise<Subscription>;
}

// 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."
