fix(transport): enforce a shared loopback-only plaintext HTTP policy (#1319) - #1344
Open
larry-zy wants to merge 10 commits into
Open
fix(transport): enforce a shared loopback-only plaintext HTTP policy (#1319)#1344larry-zy wants to merge 10 commits into
larry-zy wants to merge 10 commits into
Conversation
…r and Client The ready-to-run Server and the primary Python Client did not enforce the network safety policy already applied by the Agent integrations, allowing two unsafe transports: - ServerSettings accepted a non-loopback bind (e.g. 0.0.0.0) while bearer authentication was disabled, silently exposing an unauthenticated Server. - ClientSettings / PowerContextClient accepted a non-loopback http:// URL and would send a bearer token over plaintext. Introduce a single shared transport-policy module (powercontext.transport) and consume it everywhere: - ClientSettings rejects non-loopback plaintext http:// Server URLs. - PowerContextClient refuses to send a bearer token over an unencrypted non-loopback connection. - ServerSettings rejects an unauthenticated non-loopback bind unless the operator opts in via POWERCONTEXT_SERVER_ALLOW_UNAUTHENTICATED_NON_LOOPBACK (for TLS-terminated / controlled-network deployments). - The `server run` CLI applies the same guard, since model_copy bypasses the settings validator on --host overrides. Add a cross-surface contract test suite and update the EN/ZH configuration reference. Closes oceanbase#1319
# Conflicts: # docs/en/docs/reference/configuration.md # docs/zh/docs/reference/configuration.md # src/powercontext/server/settings.py
Load the Codex plugin's settings module by path and assert its private loopback host set and _http_base_url behavior match the shared transport contract, so the isolated plugin copy cannot silently drift. Tighten the configuration docs to note the plaintext-URL rule applies to configured Server URLs.
The loopback check only matched the three named hosts 127.0.0.1, ::1 and localhost, so plaintext HTTP to any other 127.0.0.0/8 address (e.g. 127.0.0.2) was wrongly rejected as non-loopback. Detect loopback via ipaddress.ip_address(...).is_loopback, which covers the whole IPv4 loopback block, and route the Client CLI through the shared helper. Both agent plugins ship isolated and cannot import powercontext, so each vendors its own copy of the check; parametrised drift-guard tests pin both copies to the shared contract. Surface ServerSettings validation errors as a typer.BadParameter instead of a raw traceback.
…ports The bearer-token-over-plaintext guard is only meaningful for the transport the client opens itself, where base_url's scheme reflects what crosses the wire. A caller-supplied http_client owns its own transport (ASGI in-process, a Unix socket, a TLS-terminating proxy -- all carrying an http:// label), so the scheme is no longer a reliable signal and enforcing it only produced false positives, breaking the authenticated in-memory e2e flow. Enforce the guard only when the client creates the transport.
The Docker image binds POWERCONTEXT_SERVER_HTTP_HOST=0.0.0.0 so the port is reachable from the harness container, but the unauthenticated-non-loopback guard now refuses that bind and the Server fails to start. The Compose network is isolated, so opt in via POWERCONTEXT_SERVER_ALLOW_UNAUTHENTICATED_NON_LOOPBACK to restore startup.
larry-zy
force-pushed
the
fix/1319-transport-policy
branch
from
August 25, 2026 08:24
8448ed2 to
8615e7d
Compare
Teingi
reviewed
Aug 25, 2026
Teingi
reviewed
Aug 25, 2026
Teingi
reviewed
Aug 25, 2026
AlexStocks
reviewed
Aug 25, 2026
review - client: gate the plaintext-token guard on an explicit trust_transport_security opt-in; supplying an http_client is no longer treated as evidence the transport is safe, so a bearer token is still refused over unencrypted non-loopback HTTP. - docker: declare POWERCONTEXT_SERVER_ALLOW_UNAUTHENTICATED_NON_LOOPBACK in the image itself so the documented `docker run` starts out of the box, document the network-exposure trade-off, and drop the masking env from the e2e compose so it exercises the real image contract; add a Dockerfile smoke test. - server cli: merge --host/--port before validation so a safe override can repair an unsafe environment bind, and translate the actionable settings failures (unauthenticated non-loopback bind, missing bearer token) into friendly CLI errors recognised by exception identity rather than raw validation text.
- cli: unpack the ServerSettings kwargs so the partial ``http`` mapping is not type-checked against the full HttpConfig annotation (ty invalid-argument-type). - tests: pin a wide COLUMNS in the friendly-error tests so the rich panel does not hard-wrap the asserted --host / env-var tokens on a narrow CI terminal.
…ntract Pi's isLoopback only accepted localhost, 127.0.0.1 and [::1], so its production resolveConfig rejected http://127.0.0.2:8000 as "must use HTTPS outside loopback" while the shared policy and the Python plugins already trust the whole 127.0.0.0/8 block. Rewrite it to mirror is_loopback_host (strip brackets, lowercase, accept 127.0.0.0/8, localhost and ::1). Add a Pi Vitest drift guard and a JSON fixture of loopback/non-loopback host vectors that both the Python drift guard and the Pi suite consume, so a future divergence from the shared contract fails in at least one language.
…error assertions pretty-format-json rewrites the fixture with one array element per line; apply that formatting so the quality hook passes in CI. The friendly-error CLI tests asserted tokens (--host, the opt-in env var) against typer's rich error panel. Under GitHub Actions typer forces force_terminal=True and the runner's TERM=dumb pins rich to 80 columns while ignoring COLUMNS, so the panel hyphen-breaks --host and force-splits the long env var. Replace the COLUMNS override with a fixture that neutralises the forced terminal and sets an explicit wide width, making the assertions independent of the terminal environment.
52 tasks
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.
Background / Motivation
Closes #1319
PowerContext has several surfaces that open or configure HTTP connections
(the Python Client, the CLI, the Server, and the Agent plugins), but each one
implemented the "plaintext HTTP is only trusted on loopback" rule on its own,
with inconsistent behaviour that could be bypassed or misjudged. This PR
consolidates that rule into one shared implementation and fixes two bugs found
along the way.
What changed
1. Unified transport safety policy (
transport.py)sent over an unencrypted non-loopback connection; an unauthenticated Server
must not bind to a routable address without an explicit opt-in.
is_loopback_host/is_plaintext_non_loopbackhelpers.2. Fix loopback detection that only matched three named hosts
127.0.0.1/::1/localhost, so any otheraddress in
127.0.0.0/8(e.g.127.0.0.2) was wrongly classified asnon-loopback and its plaintext requests were rejected.
ipaddress.ip_address(host).is_loopback, covering thewhole IPv4 loopback block.
powercontext, so each vendors its own copy of the check; new parametriseddrift-guard tests pin both copies to the shared contract.
3. Fix false positive of the token guard for caller-supplied transports
for a caller-supplied
http_client, which broke the authenticated e2e flowthat uses an in-process
httpx.ASGITransport(base_url=http://testserver)with a token.
caller's responsibility (ASGI in-process, a Unix socket, or a TLS-terminating
proxy all carry an
http://label, so the scheme is no longer a reliablesignal). The guard now applies only to the transport the client opens itself.
4. Friendlier CLI error
server runfails to load/validateServerSettings, it now raises atyper.BadParameterinstead of a raw traceback.Testing
uv run pytest: 738 passed, 9 skippeduv run ruff check: clean127.0.0.2,127.1.2.3),Client/Server policy, parametrised drift guards for both plugins, and a case
allowing a token over a caller-supplied transport.
Notes
pi(TypeScript) has the sameisLoopbackbug; it is leftout of this PR and will be handled separately.
that is existing design and out of scope here.