feat(core): cookie exchange endpoint#21
Conversation
feat: add header check for authentication
chore: fix Str|None issue with client IP
|
@copilot review this PR - I want early feedback |
- Fix is_safe_redirect to reject protocol-relative URLs (//evil.com) that start with / but resolve to external hosts - Use jsonify() explicitly in cookie_crumbling_protocol_v2 to ensure Content-Type: application/json and satisfy static analysis - Add unit test for protocol-relative URL rejection Agent-Logs-Url: https://github.com/MrSecure/homelab-auth/sessions/6352a153-440e-4f21-8fa8-b3b06d3e6f7a Co-authored-by: MrSecure <1155636+MrSecure@users.noreply.github.com>
…odeQL alerts - Replace is_safe_redirect() with get_safe_redirect_url() that takes user URL and a safe default, returning the validated URL or the default in one call - This eliminates the two-step validate-then-maybe-reassign pattern that CodeQL could not trace as a proper sanitization - Add explicit http/https scheme check and protocol-relative URL rejection - Use jsonify() in cookie_crumbling_protocol_v2 for explicit Content-Type - Update tests to match renamed helper function Agent-Logs-Url: https://github.com/MrSecure/homelab-auth/sessions/6352a153-440e-4f21-8fa8-b3b06d3e6f7a Co-authored-by: MrSecure <1155636+MrSecure@users.noreply.github.com>
- Tests now call the actual function instead of duplicating inline logic - Covers: relative paths, protocol-relative URLs, absolute domain matching, invalid schemes (javascript:, data:), external domain rejection - Uses load_main_module + test_request_context for proper Flask context Agent-Logs-Url: https://github.com/MrSecure/homelab-auth/sessions/6352a153-440e-4f21-8fa8-b3b06d3e6f7a Co-authored-by: MrSecure <1155636+MrSecure@users.noreply.github.com>
Here's early feedback on the PR. I've also addressed the two CodeQL alerts while reviewing. Security fixes (commits
Remaining CodeQL URL-redirect alerts (pre-existing, false positives): Lines 459, 476, 487, 530 are all flagged because CodeQL's interprocedural taint analysis sees the user-supplied Code quality observations
|
|
@copilot have all the GHAS findings been addressed? |
Both findings are addressed:
|
fix: issues identified by bandit fix: restore missing security-scan task in Taskfile fix: update some task references in CLAUDE.md
… from remote source' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds support for cookie/header-based token exchange and hardens redirect handling, plus updates container entrypoint behavior and project tooling/docs to support the new flows.
Changes:
- Added safe redirect normalization (
get_safe_redirect_url) and introduced/cookie-crumbling-protocol-v2plus header-token fallback for/loginand/verify. - Updated Docker entrypoint/CI integration tests to support
--check-config, and added gunicorn control-socket directory setup. - Added configuration knobs (
cookie.exchange_enabled,header.name), expanded endpoint documentation, and updated pre-commit/tooling (Bandit task + config).
Reviewed changes
Copilot reviewed 17 out of 18 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/main.py |
Adds safe redirect helper, header-token fallback, and cookie exchange endpoint; improves Jinja auto-escaping and logging. |
tests/test_main.py |
Adds module loader helper and new unit tests around safe redirects and header-token login behavior. |
tests/test_integration.py |
Adjusts Docker run invocation to match the image entrypoint and adds timeouts. |
tests/test_cookie_crumbling_protocol.py |
Introduces tests intended to validate the new cookie exchange endpoint and related config behavior. |
support/entrypoint.sh |
Adds --check-config handling and passes additional gunicorn flags. |
Dockerfile |
Creates /app/.gunicorn directory needed for the entrypoint’s socket path. |
examples/config.yaml |
Adds cookie.exchange_enabled and header.name example defaults. |
support/config.yaml |
Adds cookie.exchange_enabled and header.name defaults. |
support/wsgi-config.yaml |
Adds cookie.exchange_enabled and header.name defaults. |
docs/ENDPOINTS.md |
Adds endpoint-by-endpoint behavioral documentation, including cookie exchange semantics. |
README.md |
Updates the API endpoints list to include new/clarified endpoints. |
Taskfile.yml |
Adds a Bandit security scan task and re-enables integration tests in task test. |
pyproject.toml |
Adds Bandit configuration to match the new security-scan task. |
.pre-commit-config.yaml |
Updates pinned tool revisions (ruff/actionlint/openapi validator/cspell). |
.github/etc/dictionary.txt |
Adds healthz to the spellcheck dictionary. |
.github/actions/bootstrap/action.yml |
Adds optional Node.js setup via a node-version input. |
CLAUDE.md |
Removes references to a task format command. |
| default_url = f"https://{cfg['redir']['default_destination']}{domain}" | ||
| target_url = get_safe_redirect_url(request.args.get("rd", ""), default_url) | ||
|
|
||
| login_url = f"https://{cfg['redir']['external_name']}{domain}" | ||
| return redirect(f"{login_url}/login?rd={target_url}", code=307) |
There was a problem hiding this comment.
The redirect to the external login host interpolates target_url directly into the rd query param without URL-encoding. If target_url contains reserved characters (e.g., &, #, or ?), it can break the query string or allow parameter injection. Build the redirect URL using proper query encoding (e.g., urllib.parse.urlencode / quote) so the rd value is always a single parameter.
There was a problem hiding this comment.
Done in commit 269790a. Added quote from urllib.parse and changed the redirect to ?rd={quote(target_url, safe='')} so any reserved characters in the validated redirect URL are percent-encoded before being appended to the query string.
Update tests/test_cookie_crumbling_protocol.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…t tests Agent-Logs-Url: https://github.com/MrSecure/homelab-auth/sessions/6596910e-0050-4494-918c-18a685b59804 Co-authored-by: MrSecure <1155636+MrSecure@users.noreply.github.com>
Agent-Logs-Url: https://github.com/MrSecure/homelab-auth/sessions/6596910e-0050-4494-918c-18a685b59804 Co-authored-by: MrSecure <1155636+MrSecure@users.noreply.github.com>
Agent-Logs-Url: https://github.com/MrSecure/homelab-auth/sessions/7b56371d-bb13-483e-bd5e-b950e4a4dcfe Co-authored-by: MrSecure <1155636+MrSecure@users.noreply.github.com>
Agent-Logs-Url: https://github.com/MrSecure/homelab-auth/sessions/7b56371d-bb13-483e-bd5e-b950e4a4dcfe Co-authored-by: MrSecure <1155636+MrSecure@users.noreply.github.com>
Both original GHAS findings are resolved:
The remaining CodeQL |
|
|
||
| login_url = f"https://{cfg['redir']['external_name']}{domain}" | ||
| return redirect(f"{login_url}/login?rd={target_url}", code=307) | ||
| return redirect(f"{login_url}/login?rd={quote(target_url, safe='')}", code=307) |
There was a problem hiding this comment.
@copilot - this is not a viable suggestion for the system. Refactor it to allow redirects to any host that is a subdomain of get_cookie_subdomain(). If the configuration includes a list of cookie.allowed_hosts, further constrain the redirection to require an exact match to one of those FQDNs.
Contributor Comments
header.nameconfig.cookie.exchange_enabled: true)--check-configand pass it through to gunicorn--check-configto workPull Request Checklist
Thank you for submitting a contribution to homelab-auth!
Please address the following items: