Replace ReverseProxied with werkzeug ProxyFix #126

Merged
jgroman merged 1 commit from feature/112-replace-reverseproxied into develop 2026-06-26 13:20:39 +00:00
Owner
  • replace ReverseProxied with werkzeug ProxyFix
  • add middleware for managing duplicate headers (needed even with ProxyFix)
  • trust levels are configurable via PROXYFIX_X_* config attributes
  • add tests of ProxyFix configuration and header de-duplicator
- replace ReverseProxied with werkzeug ProxyFix - add middleware for managing duplicate headers (needed even with ProxyFix) - trust levels are configurable via PROXYFIX_X_* config attributes - add tests of ProxyFix configuration and header de-duplicator
jgroman self-assigned this 2026-06-12 13:32:42 +00:00
- replace ReverseProxied with werkzeug ProxyFix
- trust levels are configurable via PROXYFIX_X_* config attributes
- add tests of ProxyFix configuration

Fixes #112

Assisted-By: Claude Code
Add middleware for managing duplicate headers
All checks were successful
Run tests / test (pull_request) Successful in 35s
AI Code Review / ai-review (pull_request_target) Successful in 47s
72e8ba60b9
jgroman changed title from WIP: Replace ReverseProxied with werkzeug ProxyFix to Replace ReverseProxied with werkzeug ProxyFix 2026-06-12 13:36:04 +00:00

AI Code Review

📋 MR Summary

Replaces the custom ReverseProxied middleware with Werkzeug's ProxyFix and adds a custom middleware to deduplicate HAProxy headers.

  • Key Changes:
    • Removed custom ReverseProxied middleware.
    • Added Werkzeug ProxyFix middleware configurable via PROXYFIX_X_* app configurations.
    • Introduced _ForwardedHeaderFix middleware to normalize duplicate X-Forwarded-* headers before ProxyFix execution.
  • Impact: testdays/app.py, testdays/config.py, tests/unit/test_proxy_fix.py
  • Risk Level: 🟡 Medium - Incorrect WSGI middleware wrapping order causes ProxyFix to run before the deduplication middleware, defeating the purpose of the fix and potentially causing request rejections in newer Werkzeug versions.

Detailed Code Review

The migration to Werkzeug's ProxyFix and the logic for deduplicating HAProxy headers are generally well-implemented. However, there is a critical logical flaw in how the WSGI middleware stack is constructed. WSGI middlewares execute outside-in (the last applied wrapper executes first on incoming requests). The current implementation wraps _ForwardedHeaderFix with ProxyFix, meaning ProxyFix processes the raw, duplicated headers before the deduplication logic runs, which contradicts the stated intent.

📂 File Reviews

📄 `testdays/app.py` - Adds configuration for Werkzeug ProxyFix and a custom header deduplication middleware.
  • Critical [Logic]: The WSGI middleware wrapping order is reversed. Because ProxyFix is applied last, it becomes the outermost middleware and executes first on the request. This means ProxyFix processes the duplicated headers before _ForwardedHeaderFix can normalize them.
    • Suggestion: Reverse the wrapping order so _ForwardedHeaderFix is applied last and becomes the outermost wrapper.
    if os.getenv("IS_OPENSHIFT"):
        app.wsgi_app = ProxyFix(
            app.wsgi_app,
            x_for=int(app.config["PROXYFIX_X_FOR"]),
            x_proto=int(app.config["PROXYFIX_X_PROTO"]),
            x_host=int(app.config["PROXYFIX_X_HOST"]),
            x_port=int(app.config["PROXYFIX_X_PORT"]),
            x_prefix=int(app.config["PROXYFIX_X_PREFIX"]),
        )
        # Apply deduplicator OUTSIDE ProxyFix so it runs first on incoming requests
        app.wsgi_app = _ForwardedHeaderFix(app.wsgi_app)
📄 `tests/unit/test_proxy_fix.py` - Adds tests verifying the middleware application and header deduplication behavior.
  • Critical [Testing]: The test asserting the middleware stack order enforces the incorrect order where ProxyFix is outermost.
    • Suggestion: Update the test to expect _ForwardedHeaderFix as the outermost middleware, wrapping ProxyFix.
    def test_forwarded_header_fix_in_middleware_stack(self, openshift_app):
        """
        GIVEN IS_OPENSHIFT is set
        WHEN the app is created
        THEN the middleware stack should be:
             _ForwardedHeaderFix -> ProxyFix -> ComponentsMiddleware (WhiteNoise)
        """
        header_fix = openshift_app.wsgi_app
        assert isinstance(header_fix, _ForwardedHeaderFix)
        proxy_fix = header_fix.app
        assert isinstance(proxy_fix, ProxyFix)
        assert isinstance(proxy_fix.app, ComponentsMiddleware)

Summary

  • Overall Assessment: Critical logic error found in middleware ordering. The fix is straightforward but required to ensure the correct execution sequence.
  • Priority Issues:
    • Fix the WSGI middleware wrapping order in testdays/app.py so _ForwardedHeaderFix is evaluated before ProxyFix.
    • Update test_forwarded_header_fix_in_middleware_stack to assert the corrected middleware execution order.
  • Minor Suggestions:
    • Consider making header normalization case-insensitive in _ForwardedHeaderFix to handle edge cases if HAProxy modifies header casing, though not strictly necessary if strictly handling identical duplicates.

🤖 AI Code Review | Generated with ai-code-review | Model: gemini-3.1-pro-preview

⚠️ AI-generated suggestions may be incorrect. Verify before applying. Not a replacement for human review.

## AI Code Review ### 📋 MR Summary Replaces the custom ReverseProxied middleware with Werkzeug's ProxyFix and adds a custom middleware to deduplicate HAProxy headers. - **Key Changes:** - Removed custom `ReverseProxied` middleware. - Added Werkzeug `ProxyFix` middleware configurable via `PROXYFIX_X_*` app configurations. - Introduced `_ForwardedHeaderFix` middleware to normalize duplicate `X-Forwarded-*` headers before ProxyFix execution. - **Impact:** testdays/app.py, testdays/config.py, tests/unit/test_proxy_fix.py - **Risk Level:** 🟡 Medium - Incorrect WSGI middleware wrapping order causes ProxyFix to run before the deduplication middleware, defeating the purpose of the fix and potentially causing request rejections in newer Werkzeug versions. ### Detailed Code Review The migration to Werkzeug's ProxyFix and the logic for deduplicating HAProxy headers are generally well-implemented. However, there is a critical logical flaw in how the WSGI middleware stack is constructed. WSGI middlewares execute outside-in (the last applied wrapper executes first on incoming requests). The current implementation wraps `_ForwardedHeaderFix` with `ProxyFix`, meaning `ProxyFix` processes the raw, duplicated headers before the deduplication logic runs, which contradicts the stated intent. #### 📂 File Reviews <details> <summary><strong>📄 `testdays/app.py`</strong> - Adds configuration for Werkzeug ProxyFix and a custom header deduplication middleware.</summary> - **Critical** [Logic]: The WSGI middleware wrapping order is reversed. Because `ProxyFix` is applied last, it becomes the outermost middleware and executes first on the request. This means `ProxyFix` processes the duplicated headers *before* `_ForwardedHeaderFix` can normalize them. - *Suggestion:* Reverse the wrapping order so `_ForwardedHeaderFix` is applied last and becomes the outermost wrapper. ```` if os.getenv("IS_OPENSHIFT"): app.wsgi_app = ProxyFix( app.wsgi_app, x_for=int(app.config["PROXYFIX_X_FOR"]), x_proto=int(app.config["PROXYFIX_X_PROTO"]), x_host=int(app.config["PROXYFIX_X_HOST"]), x_port=int(app.config["PROXYFIX_X_PORT"]), x_prefix=int(app.config["PROXYFIX_X_PREFIX"]), ) # Apply deduplicator OUTSIDE ProxyFix so it runs first on incoming requests app.wsgi_app = _ForwardedHeaderFix(app.wsgi_app) ```` </details> <details> <summary><strong>📄 `tests/unit/test_proxy_fix.py`</strong> - Adds tests verifying the middleware application and header deduplication behavior.</summary> - **Critical** [Testing]: The test asserting the middleware stack order enforces the incorrect order where `ProxyFix` is outermost. - *Suggestion:* Update the test to expect `_ForwardedHeaderFix` as the outermost middleware, wrapping `ProxyFix`. ```` def test_forwarded_header_fix_in_middleware_stack(self, openshift_app): """ GIVEN IS_OPENSHIFT is set WHEN the app is created THEN the middleware stack should be: _ForwardedHeaderFix -> ProxyFix -> ComponentsMiddleware (WhiteNoise) """ header_fix = openshift_app.wsgi_app assert isinstance(header_fix, _ForwardedHeaderFix) proxy_fix = header_fix.app assert isinstance(proxy_fix, ProxyFix) assert isinstance(proxy_fix.app, ComponentsMiddleware) ```` </details> ### ✅ Summary - **Overall Assessment:** Critical logic error found in middleware ordering. The fix is straightforward but required to ensure the correct execution sequence. - **Priority Issues:** - Fix the WSGI middleware wrapping order in `testdays/app.py` so `_ForwardedHeaderFix` is evaluated before `ProxyFix`. - Update `test_forwarded_header_fix_in_middleware_stack` to assert the corrected middleware execution order. - **Minor Suggestions:** - Consider making header normalization case-insensitive in `_ForwardedHeaderFix` to handle edge cases if HAProxy modifies header casing, though not strictly necessary if strictly handling identical duplicates. --- 🤖 **AI Code Review** | Generated with [ai-code-review](https://gitlab.com/redhat/edge/ci-cd/ai-code-review) | **Model:** `gemini-3.1-pro-preview` ⚠️ *AI-generated suggestions may be incorrect. Verify before applying. Not a replacement for human review.*
jgroman changed title from Replace ReverseProxied with werkzeug ProxyFix to WIP: Replace ReverseProxied with werkzeug ProxyFix 2026-06-12 13:37:33 +00:00
Fix AI review finding: reverse middleware ordering
All checks were successful
Run tests / test (pull_request) Successful in 32s
AI Code Review / ai-review (pull_request_target) Successful in 22s
49dcc26d22
jgroman changed title from WIP: Replace ReverseProxied with werkzeug ProxyFix to Replace ReverseProxied with werkzeug ProxyFix 2026-06-12 13:45:41 +00:00

AI Code Review

📋 MR Summary

Replaces a custom ReverseProxied middleware with Werkzeug's official ProxyFix middleware and adds a custom wrapper to handle duplicate header bugs specific to OpenShift HAProxy.

  • Key Changes:
    • Replaced ReverseProxied with werkzeug.middleware.proxy_fix.ProxyFix.
    • Added _ForwardedHeaderFix middleware to normalize identically duplicated X-Forwarded-* headers before they reach ProxyFix.
    • Introduced configurable trust levels via PROXYFIX_X_* config variables.
    • Added extensive unit test coverage for the new proxy middlewares.
  • Impact: testdays.app, testdays.config, tests.unit.test_proxy_fix
  • Risk Level: 🟢 Low - The changes rely on a widely used standard library (ProxyFix) for proxy parsing. The custom middleware handles a very specific edge case safely without interfering with standard proxy chains. Security defaults are set strictly.

Detailed Code Review

The implementation is solid and addresses the duplicate header issue elegantly. Replacing custom middleware with Werkzeug's ProxyFix improves security and standardizes the application's reverse-proxy handling. The execution order of WSGI middleware is correct, ensuring headers are normalized before parsing. Test coverage is thorough and accurately validates both normal operation and edge cases.

📂 File Reviews

📄 `testdays/app.py` - Implements the new header-fixing middleware and configures ProxyFix in the app factory.
  • Minor [Suggestion]: Using int() to parse config variables during application startup will result in a ValueError if environment variables are injected with non-numeric values. While failing fast on startup is acceptable, you might consider using a safe fallback or explicit parsing.
    • Suggestion: Add error handling or use a type-coercing config loader if user-provided environment overrides are common.

Summary

  • Overall Assessment: The changes are well-implemented, secure, and fully tested. No critical issues were identified.
  • Minor Suggestions:
    • Consider adding try/except blocks or utilizing a config parsing utility for the PROXYFIX_X_* environment variables to prevent ugly tracebacks if misconfigured.

🤖 AI Code Review | Generated with ai-code-review | Model: gemini-3.1-pro-preview

⚠️ AI-generated suggestions may be incorrect. Verify before applying. Not a replacement for human review.

## AI Code Review ### 📋 MR Summary Replaces a custom `ReverseProxied` middleware with Werkzeug's official `ProxyFix` middleware and adds a custom wrapper to handle duplicate header bugs specific to OpenShift HAProxy. - **Key Changes:** - Replaced `ReverseProxied` with `werkzeug.middleware.proxy_fix.ProxyFix`. - Added `_ForwardedHeaderFix` middleware to normalize identically duplicated `X-Forwarded-*` headers before they reach `ProxyFix`. - Introduced configurable trust levels via `PROXYFIX_X_*` config variables. - Added extensive unit test coverage for the new proxy middlewares. - **Impact:** testdays.app, testdays.config, tests.unit.test_proxy_fix - **Risk Level:** 🟢 Low - The changes rely on a widely used standard library (`ProxyFix`) for proxy parsing. The custom middleware handles a very specific edge case safely without interfering with standard proxy chains. Security defaults are set strictly. ### Detailed Code Review The implementation is solid and addresses the duplicate header issue elegantly. Replacing custom middleware with Werkzeug's `ProxyFix` improves security and standardizes the application's reverse-proxy handling. The execution order of WSGI middleware is correct, ensuring headers are normalized before parsing. Test coverage is thorough and accurately validates both normal operation and edge cases. #### 📂 File Reviews <details> <summary><strong>📄 `testdays/app.py`</strong> - Implements the new header-fixing middleware and configures ProxyFix in the app factory.</summary> - **Minor** [Suggestion]: Using `int()` to parse config variables during application startup will result in a `ValueError` if environment variables are injected with non-numeric values. While failing fast on startup is acceptable, you might consider using a safe fallback or explicit parsing. - *Suggestion:* Add error handling or use a type-coercing config loader if user-provided environment overrides are common. </details> ### ✅ Summary - **Overall Assessment:** The changes are well-implemented, secure, and fully tested. No critical issues were identified. - **Minor Suggestions:** - Consider adding try/except blocks or utilizing a config parsing utility for the `PROXYFIX_X_*` environment variables to prevent ugly tracebacks if misconfigured. --- 🤖 **AI Code Review** | Generated with [ai-code-review](https://gitlab.com/redhat/edge/ci-cd/ai-code-review) | **Model:** `gemini-3.1-pro-preview` ⚠️ *AI-generated suggestions may be incorrect. Verify before applying. Not a replacement for human review.*
jgroman changed title from Replace ReverseProxied with werkzeug ProxyFix to WIP: Replace ReverseProxied with werkzeug ProxyFix 2026-06-12 13:47:51 +00:00
Owner

I'm not convinced that _ForwardedHeaderFix is correct/necessary/a good idea. I think using ProxyFix with correct configuration should avoid the duplicated header issue; the trick is configuring it with exactly the correct number of expected proxies. We should look at other cases where this has been done for a reference.

I'm not convinced that `_ForwardedHeaderFix` is correct/necessary/a good idea. I *think* using `ProxyFix` with correct configuration should avoid the duplicated header issue; the trick is configuring it with exactly the correct number of expected proxies. We should look at other cases where this has been done for a reference.
Bump X-Forwarded-For trust to 2 proxies
Some checks failed
Run tests / test (pull_request) Failing after 34s
a71ff8c9dc
Update X-Forwarded-Proto to 2 for consistency with X-Forwarded-For.
Some checks failed
Run tests / test (pull_request) Failing after 33s
228a5155a0
Update test to reflect new config values
All checks were successful
Run tests / test (pull_request) Successful in 32s
a545cd50d8
Author
Owner

Setting trusted proxies count to 2 enables correct originating IP values in app access log.

Setting trusted proxies count to 2 enables correct originating IP values in app access log.
Update fixed test docstring
All checks were successful
Run tests / test (pull_request) Successful in 31s
03815fdd95
Remove _ForwardedHeaderFix middleware
All checks were successful
Run tests / test (pull_request) Successful in 32s
a0fc826122
- likely not required anymore with X-Forwarded-For set to 2
Update comments
All checks were successful
Run tests / test (pull_request) Successful in 33s
AI Code Review / ai-review (pull_request_target) Successful in 30s
c88196b355
jgroman changed title from WIP: Replace ReverseProxied with werkzeug ProxyFix to Replace ReverseProxied with werkzeug ProxyFix 2026-06-24 11:55:04 +00:00

AI Code Review

📋 MR Summary

This MR replaces the custom ReverseProxied middleware with Werkzeug's standard ProxyFix middleware to better handle reverse proxy headers in the OpenShift deployment. It also introduces configuration variables to control the trust levels for various X-Forwarded-* headers.

  • Key Changes:
    • Removed custom ReverseProxied class and its ad-hoc duplicate header fixing logic.
    • Added Werkzeug's ProxyFix middleware wrapped around the WSGI application when IS_OPENSHIFT is enabled.
    • Introduced PROXYFIX_X_* configuration values with default trust levels (e.g., 2 for FOR and PROTO).
    • Added unit tests to verify ProxyFix initialization, middleware ordering, and configuration parsing.
  • Impact: testdays/app.py, testdays/config.py, tests/unit/test_proxy_fix.py, .ai_review/project.md
  • Risk Level: 🟢 Low - The changes rely on a well-tested, standard library (Werkzeug) for handling proxy headers instead of custom logic. The trust levels are explicitly configured, reducing the risk of IP spoofing when properly aligned with the deployment environment.

Detailed Code Review

The implementation cleanly migrates away from custom middleware to Werkzeug's standard ProxyFix, which is the recommended approach for Flask/Werkzeug applications behind a reverse proxy.

One minor observation is that while the PR description and context notes mention a DuplicateHeaderFix middleware, the actual diff relies on ProxyFix natively handling the HAProxy append mode (as noted in the updated inline comments). This is generally acceptable as ProxyFix parses comma-separated lists correctly according to RFCs.

Ensure that environment variables mapping to PROXYFIX_X_* configurations are always valid integer strings, as the current int(app.config[...]) casting will raise a ValueError or TypeError if they are empty strings or None in overridden settings.

📂 File Reviews

📄 `testdays/app.py` - Replaces the custom proxy middleware with Werkzeug's ProxyFix and wires up the configuration.
  • Minor [Logic]: Casting config values directly to int without fallback or validation can crash the app on startup if a user overrides these with invalid strings (e.g., empty strings from env vars) or None.
    • Suggestion: Consider using app.config.get(..., default_value) and safely casting to int to ensure application resilience against misconfiguration.
        app.wsgi_app = ProxyFix(
            app.wsgi_app,
            x_for=int(app.config.get("PROXYFIX_X_FOR", 2)),
            x_proto=int(app.config.get("PROXYFIX_X_PROTO", 2)),
            x_host=int(app.config.get("PROXYFIX_X_HOST", 0)),
            x_port=int(app.config.get("PROXYFIX_X_PORT", 0)),
            x_prefix=int(app.config.get("PROXYFIX_X_PREFIX", 0)),
        )

Summary

  • Overall Assessment: The migration to ProxyFix is solidly implemented with proper testing. No critical issues were identified.
  • Minor Suggestions:
    • Add fallback defaults and error handling when casting PROXYFIX_X_* configuration values to integers to prevent startup crashes on misconfiguration.
    • Update the PR description to accurately reflect that the DuplicateHeaderFix middleware was dropped in favor of native ProxyFix list parsing.

🤖 AI Code Review | Generated with ai-code-review | Model: gemini-3.1-pro-preview

⚠️ AI-generated suggestions may be incorrect. Verify before applying. Not a replacement for human review.

## AI Code Review ### 📋 MR Summary This MR replaces the custom `ReverseProxied` middleware with Werkzeug's standard `ProxyFix` middleware to better handle reverse proxy headers in the OpenShift deployment. It also introduces configuration variables to control the trust levels for various `X-Forwarded-*` headers. - **Key Changes:** - Removed custom `ReverseProxied` class and its ad-hoc duplicate header fixing logic. - Added Werkzeug's `ProxyFix` middleware wrapped around the WSGI application when `IS_OPENSHIFT` is enabled. - Introduced `PROXYFIX_X_*` configuration values with default trust levels (e.g., `2` for FOR and PROTO). - Added unit tests to verify `ProxyFix` initialization, middleware ordering, and configuration parsing. - **Impact:** testdays/app.py, testdays/config.py, tests/unit/test_proxy_fix.py, .ai_review/project.md - **Risk Level:** 🟢 Low - The changes rely on a well-tested, standard library (Werkzeug) for handling proxy headers instead of custom logic. The trust levels are explicitly configured, reducing the risk of IP spoofing when properly aligned with the deployment environment. ### Detailed Code Review The implementation cleanly migrates away from custom middleware to Werkzeug's standard `ProxyFix`, which is the recommended approach for Flask/Werkzeug applications behind a reverse proxy. One minor observation is that while the PR description and context notes mention a `DuplicateHeaderFix` middleware, the actual diff relies on `ProxyFix` natively handling the HAProxy append mode (as noted in the updated inline comments). This is generally acceptable as `ProxyFix` parses comma-separated lists correctly according to RFCs. Ensure that environment variables mapping to `PROXYFIX_X_*` configurations are always valid integer strings, as the current `int(app.config[...])` casting will raise a `ValueError` or `TypeError` if they are empty strings or `None` in overridden settings. #### 📂 File Reviews <details> <summary><strong>📄 `testdays/app.py`</strong> - Replaces the custom proxy middleware with Werkzeug's ProxyFix and wires up the configuration.</summary> - **Minor** [Logic]: Casting config values directly to `int` without fallback or validation can crash the app on startup if a user overrides these with invalid strings (e.g., empty strings from env vars) or `None`. - *Suggestion:* Consider using `app.config.get(..., default_value)` and safely casting to `int` to ensure application resilience against misconfiguration. ```` app.wsgi_app = ProxyFix( app.wsgi_app, x_for=int(app.config.get("PROXYFIX_X_FOR", 2)), x_proto=int(app.config.get("PROXYFIX_X_PROTO", 2)), x_host=int(app.config.get("PROXYFIX_X_HOST", 0)), x_port=int(app.config.get("PROXYFIX_X_PORT", 0)), x_prefix=int(app.config.get("PROXYFIX_X_PREFIX", 0)), ) ```` </details> ### ✅ Summary - **Overall Assessment:** The migration to `ProxyFix` is solidly implemented with proper testing. No critical issues were identified. - **Minor Suggestions:** - Add fallback defaults and error handling when casting `PROXYFIX_X_*` configuration values to integers to prevent startup crashes on misconfiguration. - Update the PR description to accurately reflect that the `DuplicateHeaderFix` middleware was dropped in favor of native `ProxyFix` list parsing. --- 🤖 **AI Code Review** | Generated with [ai-code-review](https://gitlab.com/redhat/edge/ci-cd/ai-code-review) | **Model:** `gemini-3.1-pro-preview` ⚠️ *AI-generated suggestions may be incorrect. Verify before applying. Not a replacement for human review.*
Author
Owner

Re AI Review: defaults are set in Config, no need to duplicate elsewhere. Startup crash on misconfiguration with wildly incorrect values is actually preferred here instead of silent fallback to defaults.

Re AI Review: defaults are set in Config, no need to duplicate elsewhere. Startup crash on misconfiguration with wildly incorrect values is actually preferred here instead of silent fallback to defaults.
Author
Owner

Ready for review

Ready for review
Owner

OK. Putting it in config is good because this is deployment-dependent. In theory if someone wanted to run testdays-web in some other context, they could/should change it. Thanks.

OK. Putting it in config is good because this is deployment-dependent. In theory if someone wanted to run testdays-web in some other context, they could/should change it. Thanks.
Owner

(I don't intend to review this)

(I don't intend to review this)
jgroman force-pushed feature/112-replace-reverseproxied from c88196b355
All checks were successful
Run tests / test (pull_request) Successful in 33s
AI Code Review / ai-review (pull_request_target) Successful in 30s
to d807212543
All checks were successful
Run tests / test (pull_request) Successful in 35s
2026-06-26 13:08:33 +00:00
Compare
jgroman force-pushed feature/112-replace-reverseproxied from d807212543
All checks were successful
Run tests / test (pull_request) Successful in 35s
to 5797f0ed5e
All checks were successful
Run tests / test (pull_request) Successful in 33s
2026-06-26 13:15:43 +00:00
Compare
jgroman merged commit 5797f0ed5e into develop 2026-06-26 13:20:39 +00:00
jgroman deleted branch feature/112-replace-reverseproxied 2026-06-26 13:20:39 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
quality/testdays-web!126
No description provided.