coleam00/Archon · critical · Error
github_app.internal_endpoint_public_bind_rejected
github_app.internal_endpoint_public_bind_rejected
Error message
GitHub App mode is active but the server is bound to a non-loopback interface (${hostname}). The /internal/git-credential endpoint hands out live installation tokens — exposing it would leak credentials to the network. Either bind to 127.0.0.1 (HOST=127.0.0.1), or, if your reverse proxy already drops /internal/* and the upstream needs a non-loopback bind, opt out by setting ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1. What it means
In GitHub App mode, startServer refuses to bind the HTTP server to a non-loopback interface because /internal/git-credential hands out live installation tokens. This fatal check (getLog().fatal + throw) prevents the credential endpoint from ever being network-exposed, unless the operator explicitly acknowledges the risk via ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1.
Source
Thrown at packages/server/src/index.ts:893
const hostname = process.env.HOST || '0.0.0.0';
// Security guardrail: /internal/git-credential hands out live installation
// access tokens. Fail fast (not just WARN) when App mode is active and the
// server is bound to a non-loopback interface — a WARN line in startup
// logs is too easy to scroll past, and the failure mode is "anyone on the
// network who can hit the port pulls a live token". Operators who deliberately
// firewall externally (so loopback bind would block their reverse proxy's
// upstream) can opt out via ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1.
//
// Runs BEFORE Bun.serve so a rejected config never opens the listening
// socket — even briefly — and `server_listening` is never logged.
if (githubAppAuthProvider && hostname !== '127.0.0.1' && hostname !== 'localhost') {
if (process.env.ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND === '1') {
getLog().warn({ hostname }, 'github_app.internal_endpoint_exposed_acknowledged');
} else {
getLog().fatal({ hostname }, 'github_app.internal_endpoint_public_bind_rejected');
throw new Error(
'GitHub App mode is active but the server is bound to a non-loopback ' +
`interface (${hostname}). The /internal/git-credential endpoint hands out ` +
'live installation tokens — exposing it would leak credentials to the network. ' +
'Either bind to 127.0.0.1 (HOST=127.0.0.1), or, if your reverse proxy already ' +
'drops /internal/* and the upstream needs a non-loopback bind, opt out by ' +
'setting ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1.'
);
}
}
// Security guardrail (advisory): the web identity header (ARCHON_WEB_AUTH_HEADER,
// default X-Archon-User) is trusted as-is — Archon attributes web requests to
// whoever the header names. That is only sound when Archon is reachable SOLELY
// through a reverse proxy that authenticates and sets the header (loopback bind).
// On a non-loopback bind any client that can reach the port can forge it:
// cosmetic misattribution without per-user GitHub, but in per-user mode a forged
// header can read/disconnect another user's GitHub connection or bind a
// device-flow token under their identity. WARN (not fatal) so existing exposedView on GitHub (pinned to 0773b97458)
Solutions
- Set HOST=127.0.0.1 so the server binds to loopback
- If a trusted reverse proxy already strips /internal/* and a non-loopback bind is required, set ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1 — only after verifying the proxy blocks that path
- Verify the proxy config actually drops /internal/* before opting out; a leaked git-credential endpoint exposes live installation tokens
- Check the startup error/log for the offending hostname value and correct the deployment's bind configuration
Example fix
// before: binds 0.0.0.0 with GitHub App mode HOST=0.0.0.0 archon serve // after: bind loopback, or acknowledge exposure only behind a proxy that drops /internal/* HOST=127.0.0.1 archon serve # or, deliberately: HOST=0.0.0.0 ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1 archon serve
Defensive patterns
Strategy: validation
Validate before calling
const isLoopback = ['127.0.0.1', 'localhost', '::1'].includes(process.env.HOST ?? '');
const githubAppMode = Boolean(process.env.GITHUB_APP_ID);
if (githubAppMode && !isLoopback && process.env.ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND !== '1') {
throw new Error('GitHub App mode requires HOST=127.0.0.1 (or explicit ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1)');
} Type guard
null
Try / catch
try {
await startServer(config);
} catch (e) {
if (String(e.message).includes('internal_endpoint_public_bind_rejected') || String(e.message).includes('non-loopback')) {
console.error('Bind rejected: set HOST=127.0.0.1, or verify your proxy drops /internal/* and set ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1');
process.exit(1);
}
throw e;
} Prevention
- Default HOST=127.0.0.1 for all deployments running in GitHub App mode
- Only set ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1 after verifying the reverse proxy drops /internal/*
- Never expose the Archon process directly on a public interface in GitHub App mode
- Include the internal-endpoint bind check in deployment smoke tests so misconfiguration fails in CI, not production
When it happens
Trigger: Server starts with a GitHub App auth provider configured while the HOST/hostname is anything other than 127.0.0.1 or localhost, and ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND is not '1'. serveCommand -> startServer throws before the listening log is emitted.
Common situations: Running in Docker/Kubernetes where the default bind is 0.0.0.0; exposing Archon through a reverse proxy; switching to GitHub App mode on an existing deployment that already used a public bind.
Related errors
- GitHub App mode misconfigured: GITHUB_APP_ID and WEBHOOK_SEC
- GitHub App mode is active but the server is bound to a non-l
- Checksum mismatch: expected ${expectedHash}, got ${actualHas
- Untrusted source URL for '${slug}': ${entry.sourceUrl} Only
- Detached run control path is not a directory: ${directory}
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/e734eab4644c3080.
Report an issue: GitHub.