@dianshuv/copilot-api 0.10.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +16 -10
  2. package/dist/main.mjs +43 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -57,7 +57,7 @@ make down
57
57
  | Option | Description | Default |
58
58
  |--------|-------------|---------|
59
59
  | `--port`, `-p` | Port to listen on | 4141 |
60
- | `--host`, `-H` | Host/interface to bind to | (all interfaces) |
60
+ | `--host`, `-H` | Host/interface to bind to (`0.0.0.0` = all interfaces) | `127.0.0.1` |
61
61
  | `--verbose`, `-v` | Enable verbose logging | false |
62
62
  | `--account-type`, `-a` | Account type (individual, business, enterprise) | individual |
63
63
  | `--manual` | Manual request approval mode | false |
@@ -200,17 +200,23 @@ The startup banner prints whether auth is on/off and, when on, the source
200
200
 
201
201
  ### Network binding
202
202
 
203
- The proxy **binds all interfaces (`0.0.0.0`) by default**, so a key-less
204
- instance is reachable from anywhere on your network. For a local-only,
205
- unauthenticated instance, bind loopback explicitly:
203
+ The proxy **binds loopback (`127.0.0.1`) by default**, so an unconfigured
204
+ instance is reachable only from the local machine. To expose it on your network
205
+ behind auth, a tunnel, or inside a container — opt into an all-interfaces
206
+ bind explicitly:
206
207
 
207
208
  ```sh
208
- copilot-api start --host 127.0.0.1
209
+ copilot-api start --host 0.0.0.0
209
210
  ```
210
211
 
211
- The startup banner reports the **real** bind address (`0.0.0.0` for an
212
- all-interfaces bind, or the narrowed host you passed), so a wide-open bind is
213
- never hidden.
212
+ The bind host resolves with **`--host` flag > `HOST` env > `127.0.0.1`**. Note
213
+ that a `HOST` env var inherited from your shell or a container also widens
214
+ the bind: `HOST=0.0.0.0` opens all interfaces just like the flag. A *blank*
215
+ `HOST` (`HOST=`, or `HOST=$UNSET`) is ignored and falls back to the loopback
216
+ default, so an accidentally-empty env var can't silently expose the proxy. The
217
+ startup banner always reports the **real** bind address (`127.0.0.1` for the
218
+ loopback default, `0.0.0.0` for an all-interfaces bind, or the narrowed host you
219
+ passed), so a wide-open bind is never hidden.
214
220
 
215
221
  ### Choosing a key
216
222
 
@@ -277,8 +283,8 @@ a custom header, so when auth is on they stop working:
277
283
 
278
284
  Workarounds:
279
285
 
280
- - Run a **separate local, unauthenticated instance** (`--host 127.0.0.1` with no
281
- `--api-key`) for the browser UI, or
286
+ - Run a **separate local, unauthenticated instance** (no `--api-key`; the
287
+ default `127.0.0.1` bind keeps it local) for the browser UI, or
282
288
  - Hit the history JSON API directly with a key, e.g.
283
289
  `curl -H "Authorization: Bearer $COPILOT_API_KEY" http://127.0.0.1:4141/history/api/entries`.
284
290
 
package/dist/main.mjs CHANGED
@@ -1348,7 +1348,7 @@ const patchClaude = defineCommand({
1348
1348
 
1349
1349
  //#endregion
1350
1350
  //#region package.json
1351
- var version = "0.10.1";
1351
+ var version = "0.11.0";
1352
1352
 
1353
1353
  //#endregion
1354
1354
  //#region src/lib/adaptive-rate-limiter.ts
@@ -1762,6 +1762,44 @@ function resolveProxyApiKey(sources) {
1762
1762
  };
1763
1763
  }
1764
1764
  /**
1765
+ * Resolve the hostname the server will *actually* bind to, decided at the CLI
1766
+ * edge with flag-over-env precedence and a **safe loopback default** — the same
1767
+ * flag-over-env shape this codebase already uses to reconcile a CLI flag with
1768
+ * its env twin (`--api-key`/`COPILOT_API_KEY`, `--github-token`/`GH_TOKEN`).
1769
+ *
1770
+ * - `--host` flag wins when present; otherwise the `HOST` env; otherwise the
1771
+ * default `127.0.0.1`.
1772
+ * - The default is **loopback, not all-interfaces**: an unconfigured instance
1773
+ * must not expose `/token` (which echoes the plaintext Copilot token) and the
1774
+ * otherwise-unauthenticated API to the whole network. Binding every interface
1775
+ * is now an explicit opt-in — pass `--host 0.0.0.0` (or `HOST=0.0.0.0`).
1776
+ * - **flag vs env asymmetry on a blank value** (the security-critical part): an
1777
+ * explicit `--host` flag is the operator's deliberate choice, so a blank flag
1778
+ * (`--host ""` / whitespace) is taken as the wildcard-bind escape hatch and
1779
+ * canonicalized to an explicit `0.0.0.0` (rather than left as `""` to lean on
1780
+ * srvx's undocumented empty-string handling). But a *set-but-blank* `HOST` env
1781
+ * (`HOST=`, or `HOST=$UNSET` in a shell / compose where the var is unset →
1782
+ * empty — NOT a deliberate keystroke) is accidental plumbing, so it is treated
1783
+ * as **not provided** and falls through to the loopback default. This mirrors
1784
+ * `resolveProxyApiKey` trimming `""` to not-provided, so an empty `HOST` can't
1785
+ * silently reopen the all-interfaces-unauthenticated exposure the loopback
1786
+ * default exists to prevent.
1787
+ * - **Both sources are trimmed**: a padded `--host " 10.0.0.5 "` or
1788
+ * `HOST=" 10.0.0.5 "` would otherwise reach the socket bind verbatim and fail
1789
+ * with `ENOTFOUND`. Trimming also decides blank-ness for the rules above.
1790
+ *
1791
+ * `env` is passed in (not read here) to keep the function pure and unit-testable.
1792
+ */
1793
+ function resolveBindHost(flag, env) {
1794
+ if (flag !== void 0) {
1795
+ const trimmed = flag.trim();
1796
+ return trimmed === "" ? "0.0.0.0" : trimmed;
1797
+ }
1798
+ const envTrimmed = env?.trim() ?? "";
1799
+ if (envTrimmed !== "") return envTrimmed;
1800
+ return "127.0.0.1";
1801
+ }
1802
+ /**
1765
1803
  * Resolve the address the server will *actually* bind to for the startup banner
1766
1804
  * (Issue 04).
1767
1805
  *
@@ -9756,7 +9794,7 @@ async function runServer(options) {
9756
9794
  const visibleModels = allModels.filter((m) => !isHiddenModel(m.id, state.showAllModels));
9757
9795
  if (visibleModels.length === 0) consola.warn("All upstream models are filtered by the hardcoded blacklist. /v1/models will return an empty list, but explicit POSTs with a hidden id still pass through to upstream. Restart with --show-all-models to see the full catalogue.");
9758
9796
  else consola.info(`Available models:\n${visibleModels.map((m) => formatModelInfo(m)).join("\n")}`);
9759
- const serverUrl = `http://${resolveClientHost(options.host, process.env.HOST)}:${options.port}`;
9797
+ const serverUrl = `http://${resolveClientHost(options.host, void 0)}:${options.port}`;
9760
9798
  if (options.claudeCode) {
9761
9799
  if (visibleModels.length === 0) {
9762
9800
  consola.error("--claude-code interactive setup needs at least one visible model. Restart with --show-all-models or update src/lib/hidden-models.ts.");
@@ -9792,7 +9830,7 @@ async function runServer(options) {
9792
9830
  consola.box(`🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage${options.history ? `\n📜 History UI: ${serverUrl}/history` : ""}`);
9793
9831
  for (const line of buildStartupAuthLines({
9794
9832
  source: options.apiKeySource,
9795
- bindAddress: resolveBindAddress(options.host, process.env.HOST)
9833
+ bindAddress: resolveBindAddress(options.host, void 0)
9796
9834
  })) process.stdout.write(`${line}\n`);
9797
9835
  setupShutdownHandlers();
9798
9836
  setServerInstance(serve({
@@ -9842,7 +9880,7 @@ const start = defineCommand({
9842
9880
  host: {
9843
9881
  alias: "H",
9844
9882
  type: "string",
9845
- description: "Host/interface to bind to (e.g., 127.0.0.1 for localhost only, 0.0.0.0 for all interfaces)"
9883
+ description: "Host/interface to bind to. Default: 127.0.0.1 (loopback only); pass 0.0.0.0 to bind all interfaces. Falls back to the HOST env var when the flag is omitted."
9846
9884
  },
9847
9885
  verbose: {
9848
9886
  alias: "v",
@@ -9968,7 +10006,7 @@ const start = defineCommand({
9968
10006
  });
9969
10007
  return runServer({
9970
10008
  port: Number.parseInt(args.port, 10),
9971
- host: args.host,
10009
+ host: resolveBindHost(args.host, process.env.HOST),
9972
10010
  verbose: args.verbose,
9973
10011
  accountType: args["account-type"],
9974
10012
  manual: args.manual,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dianshuv/copilot-api",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!",
5
5
  "author": "dianshuv",
6
6
  "type": "module",