npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@syengup/tunnel-edge

v0.3.1

Published

FridayTunnel public-surface edge: backend routing reverse proxy with path whitelisting, request marking, and App Attest gating.

Readme

@syengup/tunnel-edge

FridayTunnel public-surface edge: a dependency-free (node built-ins only) routing reverse proxy that sits between a TLS-terminating public ingress (frpc in Phase 1) and one or more local agent backends.

frpc ─▶ tunnel-edge(:edgePort)
           │  path/backend routing + public marker + App Attest gate
           ├─ /friday-next*        ─▶ OpenClaw core(:corePort)
           └─ /cap*                ─▶ Conductor(:conductorPort)

The package deliberately imports nothing from openclaw-fridaynext-channel, fridaynext-conductor, or OpenClaw. Phase 1 embedded it in the OpenClaw channel plugin; Phase 2 adds a standalone CLI (fridaynext-tunnel-edge) so the same routing semantics run either in-process or as a managed external process.

Public API

import {
  startTunnelEdge,
  resolveBackendForPath,
  edgeAttestGateDecision,
  normalizedPath,
  matchesPrefix,
  PUBLIC_MARKER,
  ATTEST_HEADER,
  ATTEST_COOKIE,
  ATTEST_REJECTION_BODY,
} from "@syengup/tunnel-edge";
import type {
  TunnelBackend,
  TunnelEdge,
  TunnelEdgeOptions,
  ProxyAttestGate,
} from "@syengup/tunnel-edge";

type TunnelBackend = {
  id: string;
  pathPrefixes: string[]; // e.g. ["/friday-next", "/gateway", "/__openclaw__"]
  localPort: number;
  requiresAttest?: boolean; // default true
  denyPrefixes?: string[]; // DENY beats ALLOW for this backend
  allowedPaths?: string[]; // optional extra whitelist, IN ADDITION to pathPrefixes
  attestExemptPaths?: string[]; // paths exempt from the edge App Attest gate
};

const edge = startTunnelEdge({
  listenPort: 18790,
  backends: [
    {
      id: "openclaw",
      pathPrefixes: ["/friday-next", "/friday-next-admin", "/gateway", "/__openclaw__"],
      localPort: 18789,
      requiresAttest: true,
      denyPrefixes: [
        "/__openclaw__/control",
        "/__openclaw__/config",
        "/__openclaw__/api",
        "/__openclaw__",
      ],
      attestExemptPaths: [
        "/friday-next/attest",
        "/friday-next/health",
        "/friday-next/status",
        "/friday-next/plugin/info",
        "/friday-next/public-access/pairing",
        "/friday-next/pair/claim",
      ],
    },
    {
      id: "conductor",
      pathPrefixes: ["/cap"],
      localPort: 24080,
      requiresAttest: true,
      allowedPaths: [
        "/cap/hello",
        "/cap/health",
        "/cap/events",
        "/cap/models",
        "/cap/cancel",
        "/cap/files",
        "/cap/approvals",
        "/cap/sessions",
        "/cap/workspaces",
      ],
      attestExemptPaths: ["/cap/health"],
    },
  ],
  attestGate: {
    enabled: () => true,
    verify: (token) => token === "good-token",
  },
  log: (message) => console.log(message),
});

const port = await edge.port; // actual bound port (useful when listenPort is 0)
await edge.close();

CLI

fridaynext-tunnel-edge --config /path/to/tunnel-edge.json
# or: fridaynext-tunnel-edge -c /path/to/tunnel-edge.json
node dist/cli.js --config /path/to/tunnel-edge.json
  • Reads the config, starts the edge, prints edge ready on 127.0.0.1:<port> to stdout.
  • SIGTERM/SIGINT close the edge cleanly and exit 0.
  • Config/usage errors print to stderr and exit 2; missing --config exits 3.
  • --help/-h prints usage and exits 0.

Config file

{
  "listenPort": 18790,
  "backends": [
    {
      "id": "openclaw",
      "pathPrefixes": ["/friday-next", "/gateway"],
      "localPort": 18789,
      "requiresAttest": true,
      "attestExemptPaths": ["/friday-next/health"]
    }
  ],
  "logLevel": "info",
  "attest": {
    "url": "http://127.0.0.1:18789/friday-next/edge/verify-attest",
    "header": "x-fridaynext-attest"
  }
}
  • listenPort — integer 0-65535. 0 binds an ephemeral port (the CLI prints the real port).
  • backendsTunnelBackend[], validated exactly like startTunnelEdge (duplicate ids, invalid entries, and overlapping prefixes all throw).
  • logLevel"debug" | "info" | "silent" (optional). "silent" suppresses edge lifecycle messages.
  • attest — optional localhost-only verifier for the standalone CLI. url must be http(s) on localhost/127.0.0.1 (anything else is rejected); header defaults to x-fridaynext-attest. The edge sends the caller's attest token there and accepts only HTTP 200 — every other status or network error fails closed.

Library helpers:

import {
  loadEdgeConfigFile,
  writeEdgeConfigFile,
  startEdgeFromConfigFile,
} from "@syengup/tunnel-edge";
import type { EdgeConfigFile } from "@syengup/tunnel-edge";

const cfg = loadEdgeConfigFile("/path/to/tunnel-edge.json"); // throws descriptive errors
writeEdgeConfigFile("/path/to/tunnel-edge.json", cfg); // atomic tmp+rename, mode 0600
const edge = startEdgeFromConfigFile("/path/to/tunnel-edge.json", (msg) => console.log(msg));

Updating the routing table at runtime

TunnelEdge.updateBackends(backends) replaces the live routing table without restarting the listener. It is in-process only; external edge processes are restarted by their manager after the config file is rewritten.

const edge = startTunnelEdge({
  listenPort: 0,
  backends: [{ id: "a", pathPrefixes: ["/a"], localPort: 1 }],
});

edge.updateBackends([{ id: "b", pathPrefixes: ["/b"], localPort: 2 }]); // atomic swap
edge.updateBackends([
  { id: "c", pathPrefixes: ["/c"], localPort: 2 },
  { id: "d", pathPrefixes: ["/c/health"], localPort: 3 },
]); // throws — overlapping prefixes; the old table keeps serving

Validation is identical to construction, and a failed update leaves the current routing table untouched.

In-process vs external modes

The same routing table can be run two ways:

| Mode | Process | Manager updates routing by | | ---------- | ------------------------------------------------------- | --------------------------------------------- | | in-process | embedded in the host (e.g. the OpenClaw channel plugin) | TunnelEdge.updateBackends() | | external | standalone fridaynext-tunnel-edge CLI child process | rewrite tunnel-edge.json, restart the child |

Both modes produce identical routing, marker, and attest behavior because they run the same startTunnelEdge/CLI code over the same backend table.

Routing semantics

  • Paths are normalized before matching exactly like the legacy filter proxy: URL resolution, decodeURIComponent retry, and // collapsing.
  • Prefix matching is segment-boundary: /cap matches /cap, /cap/, /cap/hello but NOT /capsule (matchesPrefix).
  • Backends are matched in declaration order; first match wins.
  • Unknown paths 404 not found.
  • On the matched backend, denyPrefixes are checked first and return 404. DENY beats ALLOW.
    • A deny prefix that is identical to one of the backend's own pathPrefixes is an exact-root deny (the bare path and its trailing-slash form only), not a whole-subtree deny. This is how the bare /__openclaw__ index is denied while /__openclaw__/a2ui/* canvas traffic stays routable.
  • allowedPaths, when present, is an additional whitelist: the path must also match one of those prefixes (segment-boundary).
  • Overlapping pathPrefixes between two backends throw at construction with both backend ids in the message — no silent shadowing.
  • Requests always get x-fridaynext-public: 1 stamped after any client-supplied marker is stripped (HTTP and upgrade paths).
  • HTTP is proxied to 127.0.0.1:<backend.localPort> with the raw path/query, headers, marker, upstream.setTimeout(0) for SSE, bidirectional piping, and 502 on upstream error.
  • WebSocket/upgrade handling mirrors the legacy proxy: denied paths destroy the socket, attest rejections write a 403 before closing, allowed upgrades strip/stamp the marker and tie both socket lifetimes together.

Security invariants

  • The public marker is only ever set by the edge; client-supplied values are stripped first.
  • DENY beats ALLOW; unknown paths 404.
  • Attest is fail-closed: if the injected verify (or enabled) throws, the request is rejected.
  • Attest token is read from x-fridaynext-attest only, except /__openclaw__/* which also accepts the fn_attest cookie (canvas WKWebView sub-resources cannot set headers).
  • attestExemptPaths is per backend; paths not exempt are gated whenever the backend's requiresAttest !== false and the injected gate is enabled.

TunnelRuntime (standalone tunnel lifecycle)

TunnelRuntime owns the full FridayTunnel lifecycle — relay bootstrap, stable subdomain allocation, certificate issuance/renewal, the standby long-poll against the control plane, frpc download/checksum/child management, and the public-surface edge (in-process or as the standalone CLI child). It is host-agnostic: the host supplies a backend-table builder, an App Attest gate (or an external localhost verifier URL), voucher storage, and a logger.

import { startTunnelRuntime } from "@syengup/tunnel-edge";
import type { TunnelRuntimeConfig, TunnelRuntimeHost } from "@syengup/tunnel-edge";

const runtime = startTunnelRuntime(
  {
    dataDir: "/var/lib/fridaynext-tunnel",
    corePort: 18789, // edge listens on corePort + 1
    authToken: "gateway-bearer-token", // sha256(authToken) is the relay gatewayKey
    lanUrl: "http://192.168.1.20:18789",
    controlPlaneUrl: "https://gw.syengup.host",
    allocatorUrl: "https://gw.syengup.host/gw-alloc/allocate",
    certSignUrl: "https://gw.syengup.host/gw-alloc/sign-cert",
    subDomainHost: "bj.gw.syengup.host",
    // subdomain: "my-fixed-sub",     // optional explicit override
    // relayAddr: "frps.example:7000", // optional explicit frps override
    // relayToken: "shared-secret",    // optional explicit frps override
    edgeMode: "in-process", // or "external"
    edgeLogLevel: "info",
  },
  {
    buildBackends: () => [
      { id: "openclaw", pathPrefixes: ["/friday-next", "/gateway"], localPort: 18789 },
    ],
    attestGate: () => ({ enabled: () => true, verify: (token) => token === "good-token" }),
    externalAttestUrl: () => "http://127.0.0.1:18789/friday-next/edge/verify-attest",
    vouchers: myVoucherStore, // host-owned; the runtime never mints/claims vouchers itself
    log: (message) => console.log(message),
  },
);

const pairing = await runtime.start(); // null when no relay credentials/subdomain are available
await runtime.reconcile(["base-subdomain"]); // activate the tunnel; [] returns to standby
runtime.status(); // { state, subdomain, publicUrl, ready, publicKeyPin }
runtime.stop();
  • start() brings up standby only — it allocates/validates the stable subdomain, ensures a certificate (Let's Encrypt via the relay signer, self-signed fallback), caches relay bootstrap credentials, registers the gateway with the control plane, and waits for an authoritative desired-subdomain set. It does not download or spawn frpc until a non-empty set arrives.
  • reconcile(subdomains, backends?) applies the authoritative desired set. A non-empty set installs the pinned frpc binary, starts the edge, writes frpc.toml, and starts frpc; an empty set kills frpc + edge and returns to standby. When the set is unchanged, only the edge routing table is reconciled (updateBackends in-process, config rewrite + edge-child restart in external mode) — frpc is never touched.
  • Pairing info is cached by start() and returned by pairingInfo() while running; stop() clears it. v: 2, publicUrl is the ROOT https://<sub>.<subDomainHost> (hosts may append their own path prefix).
  • The health watchdog probes https://<baseSub>.<subDomainHost>/friday-next/health and restarts frpc after repeated failures with exponential backoff (see TunnelWatchdogPolicy).
  • The runtime logs only through host.log; it never calls the voucher port.

Data-dir layout

<dataDir>/
  relay-bootstrap.json   # cached relay addr/token/subDomainHost/region (0600)
  frpc-0.69.1           # pinned, checksum-verified frpc binary (0755)
  frpc.version          # version marker — a version bump re-installs the binary
  frpc.toml             # generated frpc config (0600, contains the relay token)
  frpc.pid              # managed frpc child pidfile
  frpc.log              # frpc's own log target
  gateway-key.pem       # shared RSA 2048 gateway keypair (0600, all certs share it)
  gateway-fullchain.pem # Let's Encrypt fullchain from the relay signer (when available)
  gateway-cert.pem      # self-signed fallback leaf
  gateway.csr           # CSR generated for the relay signer
  sub-<sub>.pem         # self-signed leaves for additional per-Apple-ID subdomains
  subdomain.txt         # persisted relay allocation
  subdomain.key         # sha256(authToken) the allocation was made under
  tunnel-edge.json      # standalone edge config (external mode, 0600 atomic)
  tunnel-edge.pid       # managed edge child pidfile (external mode)

Pure helpers exported for consumers

isValidSubdomainLabel, normalizedServedSubdomains, shouldClearRecordedFrpcPid, shouldClearRecordedEdgePid, pluginFrpcPidsFromProcessList, parseControlPlaneBackends, backendTablesEqual, TunnelHealthTracker, isTunnelHealthyStatus, StandbyLoopGuard, TunnelWatchdogPolicy, and the frpc download-source constants/helpers.

The OpenClaw channel plugin currently ships its own pre-runtime frpc/standby/edge manager (openclaw-fridaynext-channel/src/public-access/frpc-manager.ts) and will be migrated to consume this runtime in a follow-up.

Publishing note

openclaw-fridaynext-channel consumes this package from the npm registry. The CLI subpath is part of the package exports, so the plugin can resolve @syengup/tunnel-edge/dist/cli.js for external-process mode. Keep the plugin on a published semver range (never a file: reference) so its tarball does not carry a path that cannot resolve from the npm registry.