fix(security): uvicorn-CLI bind-parity shim so the SEC-F22 guard sees the real --host (SEC-F27)#434
Merged
Conversation
…sees the real --host (SEC-F27) canopy's lifespan calls enforce_loopback_bind_guard(settings.server.host), but a `uvicorn main:app --host X` launch binds X without ever setting JUNIPER_CANOPY_SERVER__HOST, so the guard read the loopback default and passed while uvicorn bound a public socket -- an undefended bypass (the root cause of SEC-F23/F24). juniper-cascor already closes this via _settings_with_uvicorn_cli_bind; canopy had no equivalent. Add settings_with_uvicorn_cli_bind() to security.py (with _cli_option_value), mirror cascor's shim adapted to canopy's nested settings.server + main:app entrypoint, and apply it in main.py right after get_settings() so every consumer (guard, logs, app.state) sees the real bind. A `python main.py` launch has no uvicorn CLI args, so it is a no-op there. Generic defense: closes the bypass class, not just the three launchers fixed in the loopback-default PR. Tests: 7 new cases in TestUvicornCliBindParity (no-op path, --host/--port overlay in 3 argv forms, non-integer --port ignored, and end-to-end guard-refuses-on-CLI-host). Full bind-guard suite green; black/isort/flake8/mypy/bandit clean. See juniper-ml notes/JUNIPER_2026-07-06_JUNIPER-ECOSYSTEM_LAUNCH-PATH-BIND-AUDIT.md (SEC-F27). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LX5ToumBaABH3QutQC86sM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Launch-path bind audit SEC-F27 (reclassified). canopy's lifespan calls
enforce_loopback_bind_guard(settings.server.host, ...), but auvicorn main:app --host Xlaunch bindsXwithout ever settingJUNIPER_CANOPY_SERVER__HOST— so the guard read the loopback default and passed while uvicorn bound a public socket. That was the undefended root cause of SEC-F23/F24. juniper-cascor already closes this with_settings_with_uvicorn_cli_bind; canopy had no equivalent — this adds it.What changed
src/security.py: newsettings_with_uvicorn_cli_bind(settings, argv=None)(+_cli_option_valuehelper), mirroring cascor's shim adapted to canopy's nestedsettings.server.host/portandmain:appentrypoint. Overlays a uvicorn CLI--host/--portonto a transient settings copy so the guard evaluates the real bind.src/main.py: apply it right afterget_settings(), so every consumer (guard, startup logs,app.state) sees the real bind. Apython main.pylaunch carries no uvicorn CLI args → no-op.This is the generic defense: it closes the bypass class (any
uvicorn main:app --host X), not just the three launchers fixed in #433.Why loopback-default launchers and the shim
#433 makes the shipped launchers bind loopback by default (honest posture). This shim is defense-in-depth: even if a future launcher passes
--host 0.0.0.0, the guard now evaluates it (and hard-fails without attestation) instead of being blind.Testing
TestUvicornCliBindParity: no-op onpython main.py;--hostoverlay in 3 argv forms (--host X,--host=X,python -m uvicorn);--portoverlay; non-integer--portignored; and the end-to-end guard-refuses-on-CLI-host case (the real point).test_bind_guard.pysuite green inJuniperCanopy1;pre-commit(black/isort/flake8/mypy/bandit) clean on all three files.Note
The cascor docstring at
src/api/app.py:100is not changed — it correctly documents cascor's own defense. See the SEC-F27 reclassification in juniper-ml PR #630 §4/§7.See juniper-ml
notes/JUNIPER_2026-07-06_JUNIPER-ECOSYSTEM_LAUNCH-PATH-BIND-AUDIT.md(SEC-F27).🤖 Generated with Claude Code
https://claude.ai/code/session_01LX5ToumBaABH3QutQC86sM