Add UUID format validation for progress tokens before store lookup#159
Conversation
Agent-Logs-Url: https://github.com/CyberSecDef/NovelForge/sessions/09263cbe-efde-43b1-a9bb-bcb76cb62c7d Co-authored-by: CyberSecDef <17597068+CyberSecDef@users.noreply.github.com>
Agent-Logs-Url: https://github.com/CyberSecDef/NovelForge/sessions/09263cbe-efde-43b1-a9bb-bcb76cb62c7d Co-authored-by: CyberSecDef <17597068+CyberSecDef@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR centralizes progress-token UUID-format validation and applies it across generation/export endpoints so malformed tokens are rejected with a clear 400 "Invalid progress token." before any progress-store lookup.
Changes:
- Added
_UUID_RE+_is_valid_token()helper innovelforge/routes/generation/_shared.py. - Guarded progress-token-consuming endpoints (
/progress/*,/revise_chapter,/export*,/generate_illustrations) with early format validation. - Added a new token-validation test suite and updated existing tests to use UUID-shaped tokens.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
novelforge/routes/generation/_shared.py |
Introduces shared UUID-format validation helper/regex for progress tokens. |
novelforge/routes/generation/chapters.py |
Adds early token validation to /progress/<token> and /progress/<token>/full. |
novelforge/routes/generation/revision.py |
Adds early token validation to /revise_chapter after missing-token guard. |
novelforge/routes/export.py |
Adds early token validation to /export, /export_editors_notes, /generate_illustrations. |
tests/test_token_validation.py |
New unit + route-level tests verifying invalid tokens get 400 "Invalid progress token.". |
tests/test_integration.py |
Updates hardcoded tokens to UUID-format to pass new route-level validation. |
tests/test_illustration_job.py |
Updates hardcoded tokens to UUID-format to pass new route-level validation. |
tests/test_export_snapshot.py |
Updates hardcoded tokens to UUID-format to pass new route-level validation. |
tests/test_export_escaping.py |
Updates hardcoded tokens to UUID-format to pass new route-level validation. |
tests/test_coverage.py |
Updates hardcoded tokens to UUID-format to pass new route-level validation. |
tests/test_app.py |
Updates hardcoded tokens to UUID-format to pass new route-level validation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| _UUID_RE = re.compile( | ||
| r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' | ||
| ) | ||
|
|
||
|
|
||
| def _is_valid_token(token: str) -> bool: | ||
| """Return True iff *token* matches the UUID v4 format used by this app.""" | ||
| return bool(token and _UUID_RE.match(token)) |
There was a problem hiding this comment.
Docstring says the helper validates “UUID v4 format”, but the regex only checks canonical UUID text form (and even accepts non-v4 values like all-zeros). Either tighten the regex to enforce v4/variant bits, or adjust the docstring to describe what is actually validated (e.g., lowercase canonical UUID string).
| @@ -105,6 +106,9 @@ def export_novel() -> Response | tuple[Response, int]: | |||
| data = request.get_json(silent=True) or {} | |||
| token = data.get("token", "") | |||
|
|
|||
There was a problem hiding this comment.
These routes now treat a missing/empty token (default "") as “Invalid progress token.” while other endpoints (e.g., /revise_chapter) return “Missing progress token.” for the same condition. Consider adding an explicit if not token: ... Missing progress token ... guard before format validation to keep API error semantics consistent.
| if not token: | |
| return jsonify({"error": "Missing progress token."}), 400 |
| @@ -130,6 +134,9 @@ def export_editors_notes() -> Response | tuple[Response, int]: | |||
| data = request.get_json(silent=True) or {} | |||
| token = data.get("token", "") | |||
|
|
|||
There was a problem hiding this comment.
Same as /export: this endpoint currently returns “Invalid progress token.” for an empty/missing token (because token defaults to ""). If the API intends to distinguish missing vs malformed, add a if not token guard before _is_valid_token() for consistency with /revise_chapter.
| if not token: | |
| return jsonify({"error": "Missing progress token."}), 400 |
| @@ -544,6 +551,9 @@ def generate_illustrations() -> Response | tuple[Response, int]: | |||
| data = request.get_json(silent=True) or {} | |||
| token = data.get("token", "") | |||
|
|
|||
There was a problem hiding this comment.
Same as the other export endpoints: missing token (no "token" field) becomes "" and is reported as “Invalid progress token.”. If you want to preserve the existing “Missing progress token.” semantics used elsewhere, add a separate empty check before _is_valid_token().
| if not token: | |
| return jsonify({"error": "Missing progress token."}), 400 |
| from novelforge.routes.generation._shared import _is_valid_token, _UUID_RE | ||
| from novelforge.progress import progress_manager |
There was a problem hiding this comment.
_UUID_RE and progress_manager are imported here but not used anywhere in this test module. Consider removing them to keep the test focused and avoid confusion about what is under test.
| from novelforge.routes.generation._shared import _is_valid_token, _UUID_RE | |
| from novelforge.progress import progress_manager | |
| from novelforge.routes.generation._shared import _is_valid_token |
Malformed progress tokens bypassed format validation and reached
progress_manager.get(), returning the misleading "Novel generation not complete." error instead of a clear "Invalid progress token." rejection.Changes
Validation helper (
novelforge/routes/generation/_shared.py)Single source of truth for token validation —
export.pyimports from here rather than duplicating:Endpoints guarded
All five endpoints that accept a progress token now validate format first, returning
400 "Invalid progress token."before touching the store:GET /progress/<token>and/progress/<token>/full(chapters.py)POST /revise_chapter(revision.py)POST /export,/export_editors_notes,/generate_illustrations(export.py)Tests
tests/test_token_validation.py(new, 50 tests): unit tests for_is_valid_token; parametrized route tests confirming malformed tokens →400 "Invalid progress token."and valid-but-unknown UUIDs still yield the original domain errors (404/"not complete")Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
api.openai.com/usr/bin/python python -m pytest tests/test_export_snapshot.py tests/test_app.py tests/test_illustration_job.py tests/test_coverage.py tests/test_export_escaping.py -q(dns block)/usr/bin/python python -m pytest tests/test_export_snapshot.py tests/test_app.py tests/test_illustration_job.py tests/test_coverage.py tests/test_export_escaping.py(dns block)/usr/bin/python python -m pytest --tb=short -q(dns block)If you need me to access, download, or install something from one of these locations, you can either: