Files
WRNexusJS/docs/WRNexusJS-Improvement-Plan.md
ClintchizandClaude Opus 5 949cf78636
Quality / quality (ubuntu-latest) (push) Failing after 13m40s
Quality / quality (windows-latest) (push) Canceled after 0s
feat(ui): add DataTable and Toaster, drop the legacy Table, fix overlay dialogs
DataTable replaces the 20-line Table scaffold entirely: columns, sorting,
filtering, pagination, selection, bulk actions, comparison layout, sticky
first column, custom HTML cells, and a remote source driven by a `request`
output rather than a function prop (props travel as HTML attributes, so a
function arrives as its own source text).

Toaster replaces the hand-rolled status div: tone icons, actions, hover
pause/resume and a progress bar.

Overlays audit -- Modal and Drawer declared aria-modal="true" but nothing
ever moved focus into the panel, so the @keydown handler on their root
never ran and closeOnEscape did nothing. Focus, focus restore, a Tab trap
and a body scroll lock now live in the reactive runtime, shared by both.

ContextMenu placed pointer menus by subtracting a guessed 340x420 from the
viewport, which pushed every menu that was not that size away from the
pointer; it now positions at the pointer and lets the anchored clamp pull
it back once it can be measured.

The reactive runtime size budget moves 150k -> 175k to cover anchored
overlays, dialog behaviour, the toaster and the DataTable client half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 14:59:58 +05:30

21 KiB
Raw Permalink Blame History

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 12 weeks
2 0.9 Distributed rate limiting + live security audit @wrnexus/ratelimit-redis 23 weeks
3 0.10 Product-critical DX packages @wrnexus/mail, @wrnexus/flags 46 weeks
4 0.11 Search + AI pairing @wrnexus/search 34 weeks
5 0.12 Enterprise/government readiness SAML in @wrnexus/auth, @wrnexus/compliance 57 weeks
6 1.0 Monetization + ecosystem @wrnexus/billing, public release strategy 610 weeks

Total: roughly 68 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:

// 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:

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:

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:

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:

{
  "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):

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: ~23 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:

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: ~12 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:

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: ~34 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: ~45 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:

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:

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: ~56 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: ~46 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: ~23 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 24 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."