Skip to content

fix(harbor): refuse non-loopback capture CLI without an admin key - #1194

Merged
cursor[bot] merged 1 commit into
mainfrom
cursor/harbor-capture-cli-no-leak
Sep 17, 2026
Merged

cursor[bot] merged 1 commit into
mainfrom
cursor/harbor-capture-cli-no-leak

Conversation

@cursor

@cursor cursor Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #1193 / release hold for 0.5.0.

#1193 closed the ungated /sessions* control plane, but the CLI path minted a random admin key and printed it to stdout — that violates the repo invariant "Never log API keys, tokens, or secrets" (CI/service managers commonly persist stdout).

Change

  • Default --host to 127.0.0.1 (private local port).
  • Non-loopback binds (0.0.0.0, etc.) require --admin-key or $OPENENV_CAPTURE_ADMIN_KEY and exit otherwise.
  • Never mint-and-print a key.
  • Regressions: default loopback leaves key unset with no key in stdout; non-loopback without key raises SystemExit; explicit key on non-loopback is accepted and not printed.

run_batch() is unchanged (still always resolves a non-empty key in-process without printing it).

Test plan

  • PYTHONPATH=src:envs uv run pytest tests/envs/test_harbor_capture_server.py tests/envs/test_harbor_runner_lifecycle.py -v
  • Exact-head CI green
  • Review by @adithya-s-k (or Ben)

Do not cut v0.5.0 until this lands on main.

Open in Web View Automation 

Note

Medium Risk
Changes secure defaults and startup behavior for the capture CLI; misconfiguration now fails at launch instead of minting a printed key, while programmatic run_batch/Harbor paths are unchanged.

Overview
Hardens the capture server CLI so admin credentials are never written to stdout, aligning with the repo rule against logging secrets.

Default bind is now 127.0.0.1 instead of 0.0.0.0, so a bare serve stays on a private local port. For non-loopback hosts (0.0.0.0, etc.), the process exits unless --admin-key or $OPENENV_CAPTURE_ADMIN_KEY is set—it no longer auto-generates a key and prints it. Loopback may still run with no admin key for local debugging.

Help text and tests were updated: default loopback leaves admin_key unset with no key in output; non-loopback without a key raises SystemExit; an explicit key on non-loopback is accepted and not echoed.

Reviewed by Cursor Bugbot for commit a1ea71d. Bugbot is set up for automated code reviews on this repo. Configure here.

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 <benjamin.burtenshaw@huggingface.co>
@cursor
cursor Bot requested a review from adithya-s-k September 17, 2026 09:37
@cursor
cursor Bot marked this pull request as ready for review September 17, 2026 09:41
@cursor
cursor Bot merged commit c969dcc into main Sep 17, 2026
11 checks passed
cursor Bot pushed a commit that referenced this pull request Sep 17, 2026
Includes #1194 capture CLI no-leak (loopback default; refuse non-loopback
without an explicit admin key).

Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
cursor Bot pushed a commit that referenced this pull request Sep 17, 2026
Re-trigger for post-#1194 0.5.0 candidate (no-leak CLI).

Co-authored-by: benjamin.burtenshaw <benjamin.burtenshaw@huggingface.co>
@burtenshaw burtenshaw added bug Something isn't working size: small Small pull request labels Sep 17, 2026 — with Cursor

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alignment Review Report

Security-hardening follow-up to #1193 on the standalone capture CLI (python -m openenv.core.harness.capture.server). Reviewed the diff 32b782a...a1ea71d against OpenEnv principles, invariants, and open RFCs.

Automated Checks

  • Lint: PASSruff format --check, ruff check, and usort check all pass on the two changed files and the full src/ tests/ scope. Removing secrets.token_urlsafe(...) does not orphan the secrets import (still used by secrets.compare_digest at server.py:704).
  • Debug code: CLEAN — nothing new introduced; this PR actually removes a print(...) that logged a minted credential. (check-debug.sh only surfaces pre-existing prints/TODOs elsewhere, none from this diff.)
  • Tests: PASS — all 11 tests in tests/envs/test_harbor_capture_server.py pass, including the 4 rewritten CLI tests that pin the new fail-closed behavior and assert nothing is printed.

Open RFCs Context

  • RFC 012 – Harbor capture providers (In Review): the closest RFC (capture proxy control plane), but it scopes capture purpose / provider fidelity / session ownership — it does not specify the CLI bind default or admin-key handling. No conflict.
  • RFC 002 – Env spec (In Review), clause S4 (Least privilege & secret hygiene): "Providers do not put secrets, tokens, or ingress URLs in logs or exceptions." This change supports S4.

Tier 1: Fixes Required

None.

Tier 2: Alignment Discussion

Principle Conflicts

None identified — the change strengthens alignment:

  • "No credential exposure" invariant (INVARIANTS.md → Security: never log secrets, never include credentials in error messages). The old path minted a random admin key and printed it to stdout (a logged credential); the new path fails closed with a message that contains no secret value (only the env-var name and the host).
  • "Network access must be explicitly configured" invariant — flipping the default bind to loopback makes external exposure an explicit opt-in (secure by default).

RFC Conflicts

None identified.

Minor (non-blocking) notes

Two optional polish items left as inline comments — neither blocks merge:

  1. Default --host moves 0.0.0.0127.0.0.1: a behavior change for the standalone CLI worth a release-note callout (pre-1.0 breaking changes are acceptable per INVARIANTS.md).
  2. The admin-key precondition is evaluated after the validate_llm upstream probe; consider validating right after arg-parsing so a missing key fails fast before any network call.

Summary

  • 0 mechanical issues to fix (Tier 1)
  • 0 alignment points requiring human review (Tier 2) — the change is well-aligned and improves credential hygiene
  • 0 RFC conflicts
  • 2 minor, optional polish notes (inline)

Suggested reviewer for the credential-hygiene invariant this touches: @Darktex (author of the "No credential exposure" invariant).

Open in Web View Automation 

Sent by Cursor Automation: Pre-review

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",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor / non-blocking. This flips the standalone CLI's default bind from 0.0.0.0 to loopback-only 127.0.0.1 — a sensible secure-by-default that aligns with the "network access must be explicitly configured" invariant.

Do note it's a behavior change for anyone running python -m openenv.core.harness.capture.server who relied on the all-interfaces default (e.g. inside a container); they'll now need --host 0.0.0.0 --admin-key ... explicitly. Pre-1.0 breaking changes are acceptable per INVARIANTS.md, so no code change required — just worth a release-note callout.

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(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor / non-blocking. This admin-key precondition runs after the validate_llm upstream probe above (the if not level: block), so a non-loopback invocation without a key will make a network round-trip and print the capture level before failing here. Consider moving this check to just after args = parser.parse_args() so a config error fails fast before any network call.

Note the tests pass --capture-level, which bypasses the probe, so this ordering isn't currently exercised.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size: small Small pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants