Skip to content

feat: add request-based access policy to server boundaries - #468

Closed
mukundshah wants to merge 1 commit into
devframes:mainfrom
mukundshah:feat/request-access-policy
Closed

mukundshah wants to merge 1 commit into
devframes:mainfrom
mukundshah:feat/request-access-policy

Conversation

@mukundshah

Copy link
Copy Markdown

Summary

Add an independent, server-only request access policy to initDevframe and initHub.
This is the prerequisite for Vite DevTools' requested internalIPs and request-time enabled callback. Refs #467.

initHub({
  access: {
    internalIPs: ['127.0.0.1', '::1'],
    authorize: async ({ remoteAddress }) => checkInternalAccess(remoteAddress),
  },
})

The shared shell gates HTTP routes before dispatch, including discovery, connection metadata, UI bootstrap, SSE, and enabled MCP. Shared-server, caller-driven, and sidecar WebSocket bindings check admission and recheck each inbound message. A rejected message closes the socket, including connections already admitted through authentication. authorizeRequest(req) lets a host apply the same policy to metadata served outside the hub base.

Behavior And Compatibility

  • Omitting access preserves existing behavior.
  • Configuring access requires a known peer address. Missing or invalid metadata denies access before the callback runs.
  • internalIPs uses exact normalized address matching. An empty list denies everyone. IPv4-mapped IPv6 matches IPv4; IPv6 spellings are canonicalized.
  • An allowlist and callback must both pass. Callbacks return boolean or Promise<boolean> and run during authorization, never configuration resolution.
  • The policy does not change authentication. Trusted tokens and auth: false cannot override it. Callback errors remain visible; WebSocket failures close/reject the socket and report DF0082 with the original cause.
  • MCP keeps its own origin and authorization checks after this gate.
  • Changes are server-only. Browser metadata carries neither callbacks nor policy values.

Downstream Vite Contract

This PR supplies enforcement primitives, not the Vite public API. The Vite change must wait for an available upstream release and cover its externally mounted metadata routes with authorizeRequest.

Vite configuration Request policy Authentication
Neither new setting Existing behavior Existing behavior
enabled: false No initialization No server
Callback only Callback must pass Existing authentication stays active
internalIPs only Exact normalized address match Explicitly bypass interactive authentication
Both Address and callback must pass Explicitly bypass interactive authentication
Empty internalIPs Deny everyone No token or authentication opt-out bypass

Downstream restrictions must also survive clientAuth: false, the authentication-disable environment variable, trusted/build tokens, live standalone mode, and enabled MCP. These Vite-specific settings are not implemented or verified by this prerequisite.

Security Considerations

  • Use actual peer metadata. Forwarding headers are ignored. A reverse proxy's address identifies the proxy, not its callers; allowing that proxy requires a separate trusted boundary or retained authentication.
  • Preserve WebSocket origin checks and apply Host/Origin checks on protected HTTP routes to prevent hostile websites from reaching localhost capabilities.
  • Fetch hosts must preserve trusted server request metadata. A plain Request without a peer fails closed. Owned native listeners obtain metadata from Bun/Deno, and native WebSocket adapters use their runtime peer metadata.
  • Recheck on each request/inbound WebSocket message; this does not proactively cancel idle connections or retract already-sent data.
  • Static build files need serving-web-server access control. External ws.url servers own their transport and need their own policy. Routes outside the mounted instance must explicitly call the authorization helper.
  • No Nuxt changes.

Verification

Passed:

  • pnpm lint
  • pnpm knip (exit 0; existing UnoCSS missing-config warnings)
  • pnpm test --run: 158 files, 1,784 tests passed, 13 skipped
  • pnpm typecheck: 43 tasks passed, including typecheck coverage verification
  • pnpm build: 29 tasks passed
  • Focused evaluator and hub suites: 49 tests passed
  • Export snapshots: 246 passed, nine skipped; six snapshots updated
  • pnpm test:runtime:bun: native WebSocket and shared-server SSE smoke checks passed
  • Final hub typecheck, lint, and git diff --check

Focused coverage includes defaults, empty and denied allowlists, IPv4/IPv6 normalization and scopes, missing peers, spoofed forwarding headers, sync/async callbacks, combined restrictions, callback errors in the evaluator, auth enabled/disabled, HTTP metadata/bootstrap, hostile Host/Origin, MCP, SSE, shared/sidecar WebSocket admission, connection rechecks, and the external-route helper.

Native-runtime policy paths are typechecked. Bun's existing transport smoke tests passed; the new policy-specific integration tests run on Node. Deno is unavailable, so its runtime execution remains unverified. Vite-specific integration remains blocked on this prerequisite. The repository-required i-have-adhd and simple-english skills were unavailable; the written contribution rules were followed and the available antfu-design skill was loaded.

@vercel

vercel Bot commented Oct 29, 2026

Copy link
Copy Markdown

@mukundshah is attempting to deploy a commit to the NuxtLabs Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

Copy link
Copy Markdown

👁️‍🗨️ Review this pull request with grouped, summarized diffs at:
👉 https://pulls.review/gh/devframes/devframe/468?from=github-actions[bot]

raw result

💭 analyzed by vercel-ai-gateway/anthropic/claude-sonnet-5
🕰️ 2026-10-29 05:24 UTC
🔗 head 67c0d13
🤖 automated by pulls.review

{
  "headSha": "67c0d13b0e04d9ba2483ddfcb56acdcd65a2c37f",
  "result": {
    "overallSummary": "Adds an independent, server-only request access policy (allowlisted peer addresses + optional async authorize callback) to `initDevframe`/`initHub`, enforced across HTTP routes and WebSocket admission/messages ahead of existing authentication, as a prerequisite for Vite DevTools' `internalIPs`/`enabled` options.",
    "groups": [
      {
        "key": "request-access-policy",
        "label": "Request access policy",
        "summary": "Introduces a server-only `access` policy (allowlisted peer addresses plus an optional async authorize callback) enforced in the shared instance shell before HTTP routes dispatch and in every shared/caller-driven/sidecar WebSocket binding, both at upgrade and on each inbound message. `authorizeRequest` exposes the same check for routes mounted outside the instance, and a new DF0082 diagnostic reports callback failures.",
        "category": "security",
        "filePaths": [
          "packages/devframe/src/node/request-access.ts",
          "packages/devframe/src/node/instance-shell.ts",
          "packages/devframe/src/rpc/transports/ws-server.ts",
          "packages/devframe/src/rpc/transports/ws-bun.ts",
          "packages/devframe/src/rpc/transports/ws-deno.ts",
          "packages/devframe/src/adapters/initiate.ts",
          "packages/hub/src/node/initiate.ts",
          "packages/devframe/src/node/diagnostics.ts",
          "packages/devframe/src/internal/index.ts",
          "packages/devframe/src/node/index.ts"
        ],
        "critical": true,
        "fileNotes": [
          {
            "path": "packages/devframe/src/node/request-access.ts",
            "text": "Core policy: normalizes IPv4/IPv6 addresses (including IPv4-mapped IPv6 and zone-aware canonicalization), builds an allowlist, and combines it with an optional async callback. Missing peer metadata or an empty allowlist denies by default."
          },
          {
            "path": "packages/devframe/src/node/instance-shell.ts",
            "text": "Wires the policy into the h3 app (host/origin checks plus peer authorization middleware), the Node HTTP fallback path, and all WS binding modes; also derives peer metadata for native Bun/Deno fetch handlers via `request.context.clientAddress`.",
            "critical": true
          },
          {
            "path": "packages/devframe/src/rpc/transports/ws-server.ts",
            "text": "Rechecks authorization on every inbound WebSocket message, even for already-admitted connections, and closes the socket with DF0082 on callback failure.",
            "critical": true
          }
        ]
      },
      {
        "key": "tests",
        "label": "Tests and type snapshots",
        "summary": "Unit tests for address normalization and policy evaluation, end-to-end hub tests covering HTTP/MCP/SSE/WebSocket admission and spoofed-header resistance, and the exported-type snapshots updated to reflect the new public `access`/`authorizeRequest` surface.",
        "category": "tests",
        "filePaths": [
          "packages/devframe/src/node/__tests__/request-access.test.ts",
          "packages/hub/src/node/__tests__/initiate.test.ts",
          "tests/__snapshots__/tsnapi/@devframes/hub/initiate.snapshot.d.ts",
          "tests/__snapshots__/tsnapi/devframe/initiate.snapshot.d.ts",
          "tests/__snapshots__/tsnapi/devframe/internal.snapshot.d.ts",
          "tests/__snapshots__/tsnapi/devframe/node.snapshot.d.ts",
          "tests/__snapshots__/tsnapi/devframe/rpc/transports/ws-bun.snapshot.d.ts",
          "tests/__snapshots__/tsnapi/devframe/rpc/transports/ws-deno.snapshot.d.ts"
        ]
      },
      {
        "key": "docs",
        "label": "Access policy docs",
        "summary": "Documents the new `access` option, its security model (forwarding headers ignored, proxy caveats), compatibility guarantees, and the new DF0082 error.",
        "category": "docs",
        "filePaths": [
          "docs/content/2.adapters/1.initiate.md",
          "docs/content/6.errors/DF0082.md"
        ]
      },
      {
        "key": "deps",
        "label": "Dependency updates",
        "summary": "Adds the `ws` dependency to the hub package (used by its new WebSocket-based access tests) and refreshes the lockfile.",
        "category": "deps",
        "filePaths": [
          "packages/hub/package.json",
          "pnpm-lock.yaml"
        ]
      }
    ],
    "schemaVersion": 1,
    "source": "llm",
    "generatedAt": "2026-10-29T05:24:41.636Z",
    "model": "vercel-ai-gateway/anthropic/claude-sonnet-5",
    "locale": "en"
  }
}

@mukundshah
mukundshah marked this pull request as draft October 29, 2026 05:25
@mukundshah mukundshah closed this Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant