Repository navigation
fix(logging): mask one-time codes in logged request URLs - #674
Merged
Merged
Conversation
uvicorn's access log, and nginx's in the bundled proxy, wrote each request URL whole, so a calendar sign-in callback left its code, state and session_state in a log Settings offers for download. Both now mask the values of those parameters, invitation codes and the usual credential names, and keep the path and every other parameter as they were. httpx's per-request INFO lines move to WARNING. They carried calendar ids, which for a Google account is its address, and the provider's sync cursor.
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 38915348 | Triggered | Generic High Entropy Secret | 2eafa21 | backend/tests/test_access_log_redaction.py | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request
Description
uvicorn's access log writes each request line with its query string, and the bundled nginx writes the request line and the Referer in full. Some request URLs carry a one-time code: the calendar sign-in callbacks (
code,state, and Microsoft'ssession_state), an invitation link (/register?invite=<code>, followed by/api/v1/invitations/validate/<code>), and Microsoft Graph's webhook validation (validationToken). Both logs are in the Settings log download, so they can end up in a bug report.What is masked. The value of each of these query parameters becomes
<redacted>, matched by name without regard to case:code,state,session_state,access_token,refresh_token,id_token,token,ticket,client_secret,api_key,validationToken,invite,invite_code. The code in/api/v1/invitations/validate/<code>is masked too. The path, every other parameter and the rest of the line stay as they were, and an empty value stays empty.Where.
backend/utils/url_redaction.pyholds the list andredact_url_secrets.SensitiveDataFilternow applies it to every string it already sanitises, so the API's and workers' own log lines mask such a URL in a message or an exception's text, and also the calendar sync cursors (syncToken,pageToken,$deltatoken,$skiptoken).install_log_redaction()attaches that filter touvicorn.accessand touvicorn.error, where uvicorn writes WebSocket handshake lines.backend/main.pycalls it at import, which covers the container'suvicorn backend.main:app,python -m backend.mainand--reload.setup_loggingcalls it as well.nginx/access-log.conf, included from thehttpblock ofnginx.conf, definesnojoin_combined: nginx'scombinedformat with the request line and the Referer passed through a chain of regex maps.httpx. Its per-request
HTTP Request:lines at INFO are now held at WARNING, asurllib3andrequestsalready are. No outgoing request carries a credential in its query string: the Gemini SDK sends its key in a header, and every token exchange is a POST body. The calendar lines did carry the provider's sync cursor, and the calendar id in the path, which for a Google calendar is the account's address. The calendar code already logs its own failures with the status and the provider's error, addresses masked.Query strings checked.
GET /api/v1/calendar/oauth/{google,microsoft}/callback:code,state,session_state. Masked by the API and nginx.POST /api/v1/calendar/webhooks/microsoft?validationToken=: masked by both.GET /api/v1/llm/models?api_key=: the API accepts it, the web app does not send it. Masked by both./register?invite=, in nginx's request line and in the Referer of the requests that page makes, andGET /api/v1/invitations/validate/<code>: masked by both./oauth/authorize?...&state=, in nginx's request line and Referer:statemasked.code_challengeis a hash meant to be public and stays./api/v1/oauth/authorize/infocarries no state./api/v1/system/logs/liveauthenticates with the session cookie and carries onlycontainer, capture and live transcript requests put no token in a URL, backup downloads use the cookie, the CLI sign-in takes its pasted code in a POST body, Google's webhook authenticates by header, and the MCP token endpoint takes a form body.Decision record
container=or a calendar result'sreason.--log-configfile. It covers every way the API starts, the dev runs included, with no Dockerfile or compose change.SensitiveDataFilteralready guards the root handlers, so a name added to the list reaches the application logs and uvicorn's together.write_token, a note's idempotency key, and names that merely contain a listed one (decode,states) are left alone. No route usesstateorcodefor anything else.Type of change
Checks run
scripts/check.py, every gate (lint, format, whitespace, filesize, blindexcept, heldpins, typecheck, docs, alembic) and the full pytest suite with Postgres, 3711 passed. Run before rebasing onto fix(notes): find a one-word quote inside a word only from three letters #666 and build: bump zod from 4.6.5 to 4.6.7 in /site #672, which touch none of these files.git diff --checkon the host.cd frontend && npm run lint && npm run check:contrast && npm run test && npm run buildcd site && npm ci && npm run buildpython3 scripts/validate_docs.pybackend/tests/test_access_log_redaction.pysends a Microsoft callback request through a real uvicorn server and checks its access line:code,stateandsession_statemasked,scopeand the path kept, and the line otherwise in uvicorn's own format. Other tests cover a request with nothing to mask (line unchanged), the WebSocket handshake line, httpx no longer logging each request,redact_url_secretscases, and the nginx maps replayed against a callback request, an invitation path and Referer, and a page with nothing to mask. The masking tests fail onmain.Migration impact
python3 scripts/check_migrations.pyrun against a throwaway Postgres.Documentation impact
.github/CONTRIBUTING.md, "Documentation Ownership"): an operator rule indocs/SECURITY.md, and item 9 of the forwarding checklist indocs/DEPLOYMENT.md.Security impact
docs/SECURITY.mdboundaries preserved and updated where behaviour changed.Manual verification
nginx -ton the new config, then the config innginx:alpinewith a closed upstream: both callbacks, the webhook validation, the invitation page and its validate call with the page as Referer, the consent page as Referer, the model listing, and a settings page with nothing to mask. Each line came out as the tests expect, and a missing Referer still logs as-.Existing installs pick up the nginx change with
git pullanddocker compose restart nginx. The API and worker changes arrive with their images.