diff --git a/README.md b/README.md
index d92ed5b..47876e1 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ rate is why Opus is opt-in.
```bash
npx pxpipe-proxy # proxy on 127.0.0.1:47821
-ANTHROPIC_BASE_URL=http://localhost:47821 claude # point Claude Code at it
+ANTHROPIC_BASE_URL=http://127.0.0.1:47821 claude # point Claude Code at it
```
Dashboard at : tokens saved, every text→image
diff --git a/src/node.ts b/src/node.ts
index a274064..5a24459 100644
--- a/src/node.ts
+++ b/src/node.ts
@@ -37,6 +37,11 @@ import {
* control. No CLI flags beyond --help/--version. */
interface RuntimeConfig {
port: number;
+ /** Interface to bind. Defaults to 127.0.0.1 (loopback only) — the dashboard
+ * is unauthenticated and serves captured request context, so it must not be
+ * exposed to the LAN by default. Set HOST=0.0.0.0 to opt into all interfaces
+ * (e.g. reaching the dashboard from another device / the host of a container). */
+ host: string;
upstream: string;
openAIUpstream: string;
openAIApiKey?: string;
@@ -101,6 +106,8 @@ function parseCli(argv: string[]): RuntimeConfig {
const sharedUpstream = process.env.PXPIPE_UPSTREAM;
return {
port: Number(process.env.PORT ?? 47821),
+ // Loopback by default; opt into all-interfaces exposure explicitly via HOST.
+ host: process.env.HOST?.trim() || '127.0.0.1',
upstream: process.env.ANTHROPIC_UPSTREAM ?? sharedUpstream ?? 'https://api.anthropic.com',
openAIUpstream: process.env.OPENAI_UPSTREAM ?? sharedUpstream ?? 'https://api.openai.com',
openAIApiKey: process.env.OPENAI_API_KEY,
@@ -140,6 +147,9 @@ Flags:
Environment:
PORT listen port (default 47821)
+ HOST interface to bind (default 127.0.0.1, loopback only).
+ Set 0.0.0.0 to expose the dashboard off-host — note it
+ is unauthenticated and serves captured request context.
PXPIPE_UPSTREAM upstream API base for every API family
ANTHROPIC_UPSTREAM Anthropic API base; overrides PXPIPE_UPSTREAM
(default https://api.anthropic.com)
@@ -1055,8 +1065,19 @@ async function main(): Promise {
});
});
- server.listen(opts.port, () => {
- console.log(`[pxpipe] listening on http://127.0.0.1:${opts.port}`);
+ // IPv6 literals need bracket notation to form a valid URL (http://[::1]:47821).
+ const displayHost = opts.host.includes(':') ? `[${opts.host}]` : opts.host;
+ const isLoopbackHost =
+ opts.host === '127.0.0.1' || opts.host === 'localhost' || opts.host === '::1';
+ server.listen(opts.port, opts.host, () => {
+ console.log(`[pxpipe] listening on http://${displayHost}:${opts.port}`);
+ if (!isLoopbackHost) {
+ console.warn(
+ `[pxpipe] WARNING: bound to ${opts.host} — the unauthenticated dashboard ` +
+ `(captured request context + kill switch) is reachable off-host. ` +
+ `Unset HOST to restrict to loopback.`,
+ );
+ }
const routes = resolveUpstreams(config);
console.log(`[pxpipe] anthropic upstream → ${routes.anthropic}`);
console.log(`[pxpipe] openai upstream → ${routes.openai}`);