From a1ea71de815dfb15d0b12cf03437ac181742ddf7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 17 Sep 2026 09:36:47 +0000 Subject: [PATCH] fix(harbor): refuse non-loopback capture CLI without an admin key Stop mint-and-print of admin keys (credential exposure). Default --host to 127.0.0.1; require --admin-key or $OPENENV_CAPTURE_ADMIN_KEY for non-loopback binds. Never print or log the key. Co-authored-by: benjamin.burtenshaw --- src/openenv/core/harness/capture/server.py | 22 ++++++++--------- tests/envs/test_harbor_capture_server.py | 28 ++++++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/openenv/core/harness/capture/server.py b/src/openenv/core/harness/capture/server.py index 59d661984..8aa30ca9e 100644 --- a/src/openenv/core/harness/capture/server.py +++ b/src/openenv/core/harness/capture/server.py @@ -1376,9 +1376,9 @@ def main() -> None: ) parser.add_argument( "--host", - default="0.0.0.0", - help="bind address (default: 0.0.0.0). Non-loopback binds mint an admin key when none is " - "set so /sessions* is never left open on a reachable interface.", + default="127.0.0.1", + help="bind address (default: 127.0.0.1). Non-loopback binds require --admin-key or " + "$OPENENV_CAPTURE_ADMIN_KEY so /sessions* is never left open on a reachable interface.", ) parser.add_argument("--port", type=int, default=8100) parser.add_argument( @@ -1415,8 +1415,7 @@ def main() -> None: "--admin-key", default=os.environ.get("OPENENV_CAPTURE_ADMIN_KEY", ""), help="key the session-management routes (/sessions*) require (defaults to " - "$OPENENV_CAPTURE_ADMIN_KEY). Required for non-loopback binds: if unset there, a random " - "key is minted and printed. Loopback-only binds may leave it unset.", + "$OPENENV_CAPTURE_ADMIN_KEY). Required for non-loopback binds. Never printed or logged.", ) args = parser.parse_args() @@ -1443,15 +1442,14 @@ def main() -> None: for fix in report.param_fixes: print(f" upstream compat: {fix}") - # Flag/env win; otherwise mint when the bind is reachable from outside. Leaving the key unset - # with --host 0.0.0.0 (the CLI default) would publish /sessions* ungated — the same open - # control plane `run_batch` already refuses. Loopback stays fail-open for private local use. + # Flag/env only — never mint-and-print (that would log a credential). Loopback may leave the + # key unset for private local use; non-loopback must supply one explicitly. admin_key = args.admin_key or None if not admin_key and not _is_loopback_host(args.host): - admin_key = secrets.token_urlsafe(32) - print( - f"capture admin key (minted for --host={args.host}; " - f"set --admin-key or $OPENENV_CAPTURE_ADMIN_KEY to pin): {admin_key}" + raise SystemExit( + f"--admin-key or $OPENENV_CAPTURE_ADMIN_KEY is required when --host={args.host} " + "(non-loopback bind would publish /sessions* ungated). " + "Pass --host 127.0.0.1 for a private local port, or set a key." ) uvicorn.run( diff --git a/tests/envs/test_harbor_capture_server.py b/tests/envs/test_harbor_capture_server.py index ccbf05aa1..b4af7dc60 100644 --- a/tests/envs/test_harbor_capture_server.py +++ b/tests/envs/test_harbor_capture_server.py @@ -171,29 +171,31 @@ def test_cli_admin_key_defaults_to_the_env_var(monkeypatch): assert seen["admin_key"] == "from-env" -def test_cli_mints_admin_key_for_default_non_loopback_host(monkeypatch): - """Default `--host 0.0.0.0` must never leave `/sessions*` ungated.""" +def test_cli_default_loopback_leaves_admin_key_unset(monkeypatch, capsys): + """Default `--host 127.0.0.1` may leave `/sessions*` ungated on a private local port.""" monkeypatch.delenv("OPENENV_CAPTURE_ADMIN_KEY", raising=False) seen = run_cli(monkeypatch) - assert seen["admin_key"], "default non-loopback bind must mint an admin key" - assert len(seen["admin_key"]) >= 32 + assert seen["admin_key"] is None + out = capsys.readouterr().out + assert "admin key" not in out.lower() -def test_cli_mints_admin_key_for_explicit_non_loopback_host(monkeypatch): +def test_cli_refuses_non_loopback_without_admin_key(monkeypatch): + """Non-loopback without a key must fail closed — never mint-and-print a credential.""" monkeypatch.delenv("OPENENV_CAPTURE_ADMIN_KEY", raising=False) - seen = run_cli(monkeypatch, "--host", "0.0.0.0") - - assert seen["admin_key"] - assert len(seen["admin_key"]) >= 32 + with pytest.raises(SystemExit, match="OPENENV_CAPTURE_ADMIN_KEY"): + run_cli(monkeypatch, "--host", "0.0.0.0") -def test_cli_leaves_admin_key_unset_on_loopback_without_flag_or_env(monkeypatch): - """A private loopback port stays as convenient as before; no key is minted.""" +def test_cli_non_loopback_accepts_explicit_admin_key(monkeypatch, capsys): monkeypatch.delenv("OPENENV_CAPTURE_ADMIN_KEY", raising=False) - seen = run_cli(monkeypatch, "--host", "127.0.0.1") + seen = run_cli(monkeypatch, "--host", "0.0.0.0", "--admin-key", "explicit") - assert seen["admin_key"] is None + assert seen["admin_key"] == "explicit" + out = capsys.readouterr().out + assert "explicit" not in out + assert "admin key" not in out.lower()