coleam00/Archon · critical
GitHub App mode is active but the server is bound to a non-l
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
When GitHub App mode is active, the server exposes /internal/git-credential, which hands out live GitHub installation tokens. startServer refuses to bind that endpoint on any non-loopback interface: it logs a fatal 'github_app.internal_endpoint_public_bind_rejected' and throws unless the operator explicitly acknowledges the risk with ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1. This prevents accidentally leaking credentials to the network.
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 (or 'localhost') so the server binds to loopback, then let a same-host reverse proxy forward traffic.
- If the reverse proxy already strips /internal/* and a non-loopback upstream bind is required, set ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1 to acknowledge the exposure.
- Verify the proxy actually blocks /internal/git-credential before enabling the opt-out — the endpoint issues live installation tokens.
- After changing HOST, restart the server; the check runs at bind time and must log 'server_listening' to succeed.
Example fix
# before HOST=0.0.0.0 # after: loopback bind behind a local proxy HOST=127.0.0.1 # or, explicitly acknowledged: # ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1
Defensive patterns
Strategy: validation
Validate before calling
const host = process.env.HOST ?? '127.0.0.1';
const loopback = host === '127.0.0.1' || host === 'localhost';
if (!loopback && process.env.GITHUB_APP_ID && process.env.ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND !== '1') {
throw new Error('App mode requires HOST=127.0.0.1 or ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1');
} Try / catch
try {
await startServer(config);
} catch (e) {
if (/internal endpoint public bind/i.test(e?.message ?? '')) {
logFatal('Bind HOST=127.0.0.1, or set ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1 behind a proxy that drops /internal/*.');
process.exit(1);
}
throw e;
} Prevention
- Default container deployments to HOST=127.0.0.1 and publish ports via a proxy.
- Never set ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND=1 unless the proxy provably blocks /internal/*.
- Include the bind-host check in deployment runbooks for App mode.
- Alert on the 'github_app.internal_endpoint_public_bind_rejected' fatal log.
When it happens
Trigger: GitHub App mode is enabled, the HOST env var binds the HTTP server to something other than 127.0.0.1/localhost (e.g. 0.0.0.0 or a LAN IP), and ARCHON_ALLOW_INTERNAL_ON_PUBLIC_BIND is not set to '1'.
Common situations: Running Archon in Docker with the container bound to 0.0.0.0; hosting behind a reverse proxy on another machine and setting HOST to a public/LAN interface; deploying to a VPS where the default HOST is the external IP; k8s port bindings using non-loopback addresses.
Related errors
- Failed to read GITHUB_APP_PRIVATE_KEY_PATH (${path}): ${(err
- GITHUB_APP_ID is set but no private key was provided. Set GI
- Provided value is not a valid PEM-encoded private key (missi
- GitHub App mode misconfigured: GITHUB_APP_ID and WEBHOOK_SEC
- github_app.internal_endpoint_public_bind_rejected
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/cae2506ee4a1f849.
Report an issue: GitHub.