Skip to content

feat(cli): refuse non-loopback bind when QWENPAW_AUTH_ENABLED is unset#4038

Open
huangcheng wants to merge 2 commits intoagentscope-ai:mainfrom
huangcheng:refuse-unauth-public-bind
Open

feat(cli): refuse non-loopback bind when QWENPAW_AUTH_ENABLED is unset#4038
huangcheng wants to merge 2 commits intoagentscope-ai:mainfrom
huangcheng:refuse-unauth-public-bind

Conversation

@huangcheng
Copy link
Copy Markdown

@huangcheng huangcheng commented May 5, 2026

Description

Closes #4037.

qwenpaw app exposes an HTTP gateway that can invoke host-affecting tools (shell, file IO, external APIs). Authentication is opt-in via QWENPAW_AUTH_ENABLED, so the current default lets an operator silently expose a tool-enabled agent on the public internet by passing --host 0.0.0.0 with no warning at startup.

This PR makes that configuration impossible by accident:

  • If --host is non-loopback and QWENPAW_AUTH_ENABLED is unset/falsy and the new --allow-unauth-public flag (or QWENPAW_ALLOW_UNAUTH_PUBLIC env var) is not set, the CLI exits with a clear message pointing to three safe paths:
    1. Bind 127.0.0.1 and front with a reverse proxy / VPN / Tailscale (recommended).
    2. export QWENPAW_AUTH_ENABLED=true to use the built-in auth.
    3. Pass --allow-unauth-public (or set QWENPAW_ALLOW_UNAUTH_PUBLIC=true in the service environment) when upstream auth is enforced and the trade-off is accepted.
  • The override path proceeds but prints a loud multi-line warning so the choice is at least visible in logs.
  • Loopback binds (127.0.0.1, ::1, localhost) and any bind with auth enabled are unchanged — no friction for the recommended configuration.

Loopback detection handles IPv4/IPv6 literals and a small allowlist of well-known hostnames; arbitrary hostnames are resolved via socket.getaddrinfo and treated as loopback only when every resolved address is loopback.

Type of Change

  • Feature (CLI safety guard)
  • Bug fix
  • Breaking change
  • Documentation
  • Refactoring

Not a breaking change for the recommended configuration (loopback bind) or for any deployment that already sets QWENPAW_AUTH_ENABLED=true. Operators currently relying on --host 0.0.0.0 without auth will see a one-time refusal with instructions for the override flag.

Component(s) Affected

  • CLI (src/qwenpaw/cli/app_cmd.py)
  • Tests (tests/unit/cli/test_cli_app_safety.py)

Why this and not just docs

The auth system is already implemented end-to-end (login UI, JWT, AuthGuard). The gap is purely defaults plus a CLI guard. SECURITY.md and the qwenpaw init SECURITY_WARNING are valuable, but they live in places an operator who runs pip install qwenpaw && supervisorctl start qwenpaw may never see. The CLI is the last place to catch the mistake before it commits.

For consistency with comparable agent CLIs (Claude Code, Codex, OpenCode all gate any network-exposed gateway with a token/auth or require explicit opt-out), this brings QwenPaw to the same default posture.

Out of scope (intentionally)

  • Not changing the default value of QWENPAW_AUTH_ENABLED (would silently change behavior for existing local deployments).
  • Not removing the operator-override path.
  • Not adding in-process sandboxing of skills.
  • Not claiming prompt injection itself is a vulnerability — it isn't, per the documented trust model.

Testing

20 new unit tests in tests/unit/cli/test_cli_app_safety.py cover:

  • _host_is_loopback classifier across IPv4/IPv6 literals, hostnames, and the empty string.
  • _enforce_unauth_public_bind_safety for: loopback (passes), public + auth-on (passes), public + flag (passes with warning), public + env override (passes with warning), public + neither (exits with code 2).
  • CliRunner-driven integration of app_cmd invocations using mocked uvicorn.run and is_auth_enabled, asserting the refuse path emits the expected guidance and that the recommended/override/auth-enabled paths invoke uvicorn.run.

Checklist

  • Run and pass pre-commit run --all-files (on changed files — see verification below)
  • Run and pass relevant tests
  • Update documentation if needed (none required — SECURITY.md already documents the trust model; the CLI itself now surfaces the guidance)

Local Verification Evidence

$ pre-commit run --files src/qwenpaw/cli/app_cmd.py tests/unit/cli/test_cli_app_safety.py
check python ast.........................................................Passed
check docstring is first.................................................Passed
fix python encoding pragma...............................................Passed
detect private key.......................................................Passed
trim trailing whitespace.................................................Passed
Add trailing commas......................................................Passed
mypy.....................................................................Passed
black....................................................................Passed
flake8...................................................................Passed
pylint...................................................................Passed
prettier.............................................(no files to check)Skipped
$ pytest tests/unit/cli/test_cli_app_safety.py -q
....................                                                     [100%]
20 passed in 2.38s

The HTTP gateway can invoke host-affecting tools (shell commands, file
IO, external APIs). Auth is opt-in via QWENPAW_AUTH_ENABLED, so binding
to a non-loopback host with auth disabled silently exposes a tool-
enabled agent on the network with no gate.

Refuse this configuration with a helpful message that points to the
three safe options:

  1. Bind 127.0.0.1 + reverse proxy / VPN / Tailscale (recommended).
  2. Set QWENPAW_AUTH_ENABLED=true to enable built-in auth.
  3. Pass --allow-unauth-public (or QWENPAW_ALLOW_UNAUTH_PUBLIC=true)
     when upstream auth is enforced and you accept the trade-off.

Loopback binds and auth-enabled binds are unchanged. The override flag
prints a loud warning but proceeds.

Closes agentscope-ai#4037

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-project-automation github-project-automation Bot moved this to Todo in QwenPaw May 5, 2026
@github-actions github-actions Bot added the first-time-contributor PR created by a first time contributor label May 5, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Welcome to QwenPaw! 🐾

Hi @huangcheng, thank you for your first Pull Request! 🎉

📋 About PR Template

To help maintainers review your PR faster, please make sure to include:

  • Description - What this PR does and why
  • Type of Change - Bug fix / Feature / Breaking change / Documentation / Refactoring
  • Component(s) Affected - Core / Console / Channels / Skills / CLI / Documentation / Tests / CI/CD / Scripts
  • Checklist:
    • Run and pass pre-commit run --all-files
    • Run and pass relevant tests (pytest or as applicable)
    • Update documentation if needed
  • Testing - How to test these changes
  • Local Verification Evidence:
    pre-commit run --all-files
    # paste summary result
    
    pytest
    # paste summary result

Complete PR information helps speed up the review process. You can edit the PR description to add these details.

🙌 Join Developer Community

Thanks so much for your contribution! We'd love to invite you to join the official QwenPaw developer group! You can find the Discord and DingTalk group links under the "Developer Community" section on our docs page:
https://qwenpaw.agentscope.io/docs/community

We truly appreciate your enthusiasm—and look forward to your future contributions! 😊

We'll review your PR soon.

- Extract _addr_is_loopback helper to drop _enforce_unauth_public_bind_safety / _host_is_loopback below the too-many-return-statements threshold.
- Disable protected-access and redefined-outer-name in the new test module (intentional pytest patterns: testing the underscore helpers, and the click runner fixture).
- Re-run black + add-trailing-comma sweep on touched files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

first-time-contributor PR created by a first time contributor

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

Tool-enabled HTTP gateway is unauthenticated by default — refuse non-loopback bind unless QWENPAW_AUTH_ENABLED is set

1 participant