Skip to content

fix: disable debug=True / Werkzeug debugger exposure (closes #9)#20

Merged
wpak-ai merged 4 commits intomasterfrom
fix/disable-debug-werkzeug-9
May 8, 2026
Merged

fix: disable debug=True / Werkzeug debugger exposure (closes #9)#20
wpak-ai merged 4 commits intomasterfrom
fix/disable-debug-werkzeug-9

Conversation

@timon0305
Copy link
Copy Markdown
Collaborator

@timon0305 timon0305 commented May 7, 2026

Problem

app.py was hard-coding debug=True in its app.run(...) call. The Werkzeug debugger that ships with Flask debug mode is a documented remote-code-execution primitive — anyone reaching the listening port (and guessing the PIN, which is generated from machine-stable inputs) can execute arbitrary Python in the server process. The default 127.0.0.1 bind is the only thing standing between the operator and RCE; a misconfigured --host, an SSH tunnel, or a careless reverse proxy is enough to expose it.

There was also no way to opt out — the literal was unconditional.

Change

Three files:

  • app.py — removed debug=True. Default debug now off. Opt-in via either --debug CLI flag or FLASK_DEBUG=1 env var. When debug is enabled, prints a stderr WARNING naming the RCE risk + reminding the operator to bind only to loopback. Auto-reloader gated on the same flag.
  • utils/debug_flag.py — new tiny module with resolve_debug_flag(env_value, cli_flag). Lives outside app.py so it can be unit-tested without importing Flask (matching the existing _build_app_parser mirror convention in the test suite). Truthy env values: "1", "true", "yes" — case-insensitive, whitespace-tolerant. CLI flag wins.
  • tests/test_cli_args.py — new TestDebugFlagGating class: 8 cases covering env-truthy / env-falsey / env-unset / CLI-override + argparse --debug default & explicit + a source-level guard that fails if debug=True is ever re-introduced as a literal in app.py.

Test plan

Unit: python3 -m unittest tests.test_cli_args42/42 OK (was 34, +8 new).

Live smoke (all four matrix cells, on a real flask>=3.0 install):

Trial Setting Debug mode WARNING Debugger is active!
A (default — no env, no flag) off
B --debug on
C FLASK_DEBUG=1 on
D FLASK_DEBUG=0 off

Bogus paths in trials A/D return a plain Flask 404 — not the orange Werkzeug debugger console. The WARNING prints once per process (parent + reloader child = 2× when the reloader is on, which is correct).

Severity

Critical — Werkzeug debugger exposure is a documented RCE pathway. Listed as a Critical / 1pt item in the eval week-1 plan for cppa-cursor-browser.

Closes #9.

Summary by CodeRabbit

  • New Features

    • Debug mode is now off by default; can be enabled explicitly via a new --debug CLI option or FLASK_DEBUG.
    • When enabled, a clear security warning about the remote debugger is shown.
  • Tests

    • Expanded test coverage for debug flag behavior and added a regression guard preventing always-on debug.
  • Documentation

    • README updated to document debugger behavior and how to enable it.

timon0305 added 2 commits May 4, 2026 23:24
The Werkzeug debugger is a documented remote-code-execution primitive.
app.py was hard-coding `debug=True`, which exposed RCE to anyone who
could reach the listening port — a misconfigured `--host`, an SSH
tunnel, or a careless reverse proxy was enough.

- Remove the `debug=True` literal from app.py.
- Default debug OFF. Opt-in via either `--debug` CLI flag or
  `FLASK_DEBUG=1` env var (truthy = "1" / "true" / "yes",
  case-insensitive, whitespace-tolerant).
- Print a stderr WARNING when debug is enabled, naming the RCE risk
  and reminding the operator to bind only to loopback.
- Gate the auto-reloader on the same flag.

Live-tested all four matrix cells: (default off / --debug / FLASK_DEBUG=1
/ FLASK_DEBUG=0). Bogus paths under debug-off return a plain Flask 404,
not the Werkzeug debugger console.

Helper `resolve_debug_flag(env_value, cli_flag)` lives in
`utils/debug_flag.py` so it can be unit-tested without importing Flask
(matching the existing test convention in tests/test_cli_args.py).

Regression coverage in tests/test_cli_args.py adds 8 cases:
- default-off, env-truthy, env-falsey, CLI override
- argparse `--debug` default + explicit
- source-level guard that fails if `debug=True` is reintroduced
Old guard: `self.assertNotIn("debug=True", src)` — substring match.
That misses cosmetic variants like `debug = True` (with spaces),
multi-line `debug=\n    True`, or any other form that produces the
same runtime semantics. CodeRabbit correctly flagged it as evadable.

Replaced with an `ast.walk(tree)` over the parsed app.py: find any
`ast.Call` whose keywords contain `debug=True` as a literal Constant.
Catches every cosmetic variant by definition.

Failure message includes the offending line number(s) and the
rationale (issue #9), so a future CI break is immediately
debuggable.

Verified by injecting `debug = True` (with spaces — the form the
old check missed) into app.py:
  - Old check: would have passed (false negative).
  - New check: failed with `[136]` and the issue-#9 message.
Then reverted the inject; test passes again.

42/42 tests still pass on the actual app.py.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32947aaf-0f53-4002-9e11-fe96e75ee81c

📥 Commits

Reviewing files that changed from the base of the PR and between c2b4a21 and 6dc182f.

📒 Files selected for processing (3)
  • README.md
  • app.py
  • tests/test_cli_args.py
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • app.py

📝 Walkthrough

Walkthrough

Replaces unconditional debug=True with an opt-in resolver and CLI flag; integrates resolve_debug_flag() into app startup, prints a security warning when enabled, updates app.run() behavior, adds unit tests and AST regression checks, and documents the new opt-in behavior.

Changes

Debug Mode Security: Opt-In Flag & Werkzeug Debugger Exposure Remediation

Layer / File(s) Summary
Debug Flag Resolution Contract
utils/debug_flag.py
Introduces resolve_debug_flag(env_value, cli_flag) implementing opt-in debug logic: CLI flag overrides; unset environment defaults to False; environment enables debug only for normalized truthy values ("1", "true", "yes").
CLI & Runtime Integration
app.py
Adds --debug CLI option, imports resolve_debug_flag, computes debug_enabled, prints security warning to stderr when enabled, and updates app.run() to set debug accordingly and conditionally enable reloader (excluding Windows).
Test Coverage & Regression Prevention
tests/test_cli_args.py
Extends argument parser with --debug boolean flag; adds TestDebugFlagGating test class validating env/CLI resolution paths; adds AST-based regression check ensuring debug=True literal never reappears in app.py.
Documentation Note
README.md
Clarifies Werkzeug debugger is disabled by default and must be enabled via --debug or FLASK_DEBUG=1; FLASK_ENV is not consulted.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI
  participant Parser
  participant Resolver
  participant App
  User->>CLI: run command (maybe --debug)
  CLI->>Parser: parse args (includes --debug)
  Parser->>Resolver: provide cli_flag and env FLASK_DEBUG
  Resolver->>App: return debug_enabled
  App->>App: print stderr warning if debug_enabled
  App->>App: call app.run(debug=debug_enabled, use_reloader=...)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A little hop, a cautious flag held tight,

I whisper to ops: "Only enable in light."
Tests sniff the AST, no naughty True in sight,
A warning on stderr to guard the night.
Hooray — opt-in debug, safe and polite.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: disabling the hardcoded debug=True and preventing Werkzeug debugger exposure, directly matching the main objective in the changeset.
Linked Issues check ✅ Passed The PR successfully addresses all coding requirements from issue #9: removes unconditional debug=True, implements opt-in debug via --debug flag and FLASK_DEBUG env var, adds stderr WARNING when enabled, gates auto-reloader, and includes regression and unit tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #9: app.py debug handling, utils/debug_flag.py resolution helper, test coverage, and README documentation. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/disable-debug-werkzeug-9

Comment @coderabbitai help to get the list of available commands and usage tips.

@timon0305 timon0305 self-assigned this May 7, 2026
@timon0305 timon0305 requested review from bradjin8 and clean6378-max-it and removed request for clean6378-max-it May 7, 2026 22:02
Copy link
Copy Markdown
Collaborator

@bradjin8 bradjin8 left a comment

Choose a reason for hiding this comment

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

READ.md - mention for the debug is off by default.

PR/issue are security-sensitive; a short note that debug is off by default and enabled only with --debug or FLASK_DEBUG would match operator expectations. Optional; you asked not to expand scope unless useful.

Comment thread tests/test_cli_args.py Outdated
Comment thread tests/test_cli_args.py
Comment thread app.py
Comment thread app.py
timon0305 added 2 commits May 8, 2026 21:58
…note

- AST guard now handles ast.NameConstant (Py3.7) and **{"debug":True}
  dict-spread bypass; helper extracted for unit testing.
- README: opt-in note for the Werkzeug debugger, including that
  FLASK_ENV=development is NOT consulted (only FLASK_DEBUG=1).
- Replace em dashes in app.py comments with ASCII to silence GitHub's
  non-ASCII banner on review.
README.md: combine both adjacent additions to the Quick Start area —
keep our FLASK_DEBUG opt-in note above the new Tests section that
landed on master via PR #23.
@timon0305 timon0305 requested a review from bradjin8 May 8, 2026 20:01
@bradjin8 bradjin8 requested a review from wpak-ai May 8, 2026 20:01
@wpak-ai wpak-ai merged commit bb02217 into master May 8, 2026
1 check was pending
@wpak-ai wpak-ai deleted the fix/disable-debug-werkzeug-9 branch May 8, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: disable debug=True / Werkzeug debugger exposure in app.py

3 participants