Skip to content

Add UUID format validation for progress tokens before store lookup#159

Merged
CyberSecDef merged 3 commits into
mainfrom
copilot/validate-progress-token-format
Apr 8, 2026
Merged

Add UUID format validation for progress tokens before store lookup#159
CyberSecDef merged 3 commits into
mainfrom
copilot/validate-progress-token-format

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 8, 2026

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.py imports from here rather than duplicating:

_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 bool(token and _UUID_RE.match(token))

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")
  • Updated hardcoded test tokens in six existing test files from human-readable strings to UUID-format literals so they continue to pass route-level validation

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
    • Triggering command: /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)
    • Triggering command: /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)
    • Triggering command: /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:

Copilot AI linked an issue Apr 8, 2026 that may be closed by this pull request
Copilot AI requested review from Copilot and removed request for Copilot April 8, 2026 23:03
Copilot AI requested review from Copilot and removed request for Copilot April 8, 2026 23:22
Copilot AI requested review from Copilot and removed request for Copilot April 8, 2026 23:26
Copilot AI changed the title [WIP] Fix progress token format validation Add UUID format validation for progress tokens before store lookup Apr 8, 2026
Copilot AI requested a review from CyberSecDef April 8, 2026 23:26
@CyberSecDef CyberSecDef marked this pull request as ready for review April 8, 2026 23:30
Copilot AI review requested due to automatic review settings April 8, 2026 23:30
@CyberSecDef CyberSecDef merged commit 50457c4 into main Apr 8, 2026
10 checks passed
@CyberSecDef CyberSecDef deleted the copilot/validate-progress-token-format branch April 8, 2026 23:30
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 in novelforge/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.

Comment on lines +11 to +18
_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))
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
@@ -105,6 +106,9 @@ def export_novel() -> Response | tuple[Response, int]:
data = request.get_json(silent=True) or {}
token = data.get("token", "")

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
if not token:
return jsonify({"error": "Missing progress token."}), 400

Copilot uses AI. Check for mistakes.
@@ -130,6 +134,9 @@ def export_editors_notes() -> Response | tuple[Response, int]:
data = request.get_json(silent=True) or {}
token = data.get("token", "")

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
if not token:
return jsonify({"error": "Missing progress token."}), 400

Copilot uses AI. Check for mistakes.
@@ -544,6 +551,9 @@ def generate_illustrations() -> Response | tuple[Response, int]:
data = request.get_json(silent=True) or {}
token = data.get("token", "")

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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().

Suggested change
if not token:
return jsonify({"error": "Missing progress token."}), 400

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +18
from novelforge.routes.generation._shared import _is_valid_token, _UUID_RE
from novelforge.progress import progress_manager
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

_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.

Suggested change
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

Copilot uses AI. Check for mistakes.
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.

No Progress Token Format Validation

3 participants