Skip to content

Secure receipt images, upload limits, consistent totals, and AI mismatch reconciliation#16

Merged
cgoncalves94 merged 10 commits into
mainfrom
codex/receipt-fixes
Feb 8, 2026
Merged

Secure receipt images, upload limits, consistent totals, and AI mismatch reconciliation#16
cgoncalves94 merged 10 commits into
mainfrom
codex/receipt-fixes

Conversation

@cgoncalves94

@cgoncalves94 cgoncalves94 commented Feb 4, 2026

Copy link
Copy Markdown
Owner

Executive Summary

This PR started with security and consistency hardening, and now also includes robust mismatch reconciliation for noisy receipt scans.

Delivered Scope

  • Secured receipt image access behind auth-only endpoint.
  • Enforced 10MB upload limits on backend and frontend.
  • Fixed totals consistency logic for item edits.
  • Fixed Gemini media handling consistency.
  • Fixed login safe redirect behavior.
  • Removed sensitive validation body logging.

Added In This Iteration

  • Added scan-time duplicate-line cleanup when OCR over-extracts repeated lines.
  • Added explicit mismatch UX with clear choices:
    • AI reconcile items (remove likely duplicate/noise lines)
    • Set receipt total to items sum (update header total only)
  • Added remove-only reconciliation contract for AI output (item_id, remove=true, short reason).
  • Added deterministic fallback when AI returns no actionable adjustments.
  • Added visibility + undo for scan-time auto-removed lines.
  • Simplified reconcile UI to focus on proposed removals and apply action.
  • Prevented repeated AI reruns for the same receipt state.

Why This Matters

  • Better security for receipt media.
  • Better reliability for totals and editing behavior.
  • Better resilience to OCR duplication issues without hiding user control.

Verification

  • Backend unit tests pass for receipt service reconciliation and dedupe behavior.
  • Frontend type checks pass.

- Remove public /uploads mount; add authenticated /receipts/{id}/image endpoint (header or cookie JWT)\n- Enforce 10MB upload limit (server chunked read + client-side guard)\n- Recompute receipt totals on item create/update/delete\n- Always send Gemini image bytes as PNG; redact validation request body logging\n- Fix login redirect param handling; add tests
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @cgoncalves94, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the application's security posture and data integrity. It addresses critical vulnerabilities by securing receipt image access and preventing sensitive data leaks. Furthermore, it enhances data accuracy by ensuring receipt totals are always correctly calculated based on their items. User experience is also improved through client-side upload validation and proper redirection after login, making the application more robust and trustworthy.

Highlights

  • Enhanced Security for Receipt Images: Receipt images are no longer publicly accessible via a static /uploads mount. A new authenticated endpoint GET /api/v1/receipts/{id}/image has been introduced, requiring either a Bearer token or a receipt_scanner_token cookie for access, and includes safe path resolution to prevent directory traversal.
  • Accurate Receipt Total Calculations: The receipt.total_amount is now consistently recomputed from its constituent items whenever an item is created, updated, or deleted. This prevents total drift and ensures data integrity.
  • Enforced Upload Size Limits: A 10MB upload limit is now enforced on the backend with chunked reads, returning a 400 error if exceeded. The frontend also includes a client-side guard and toast notification for oversized uploads.
  • Consistent Image Encoding for AI Analysis: Images sent to Gemini for analysis are now consistently encoded as PNG with media_type=image/png, resolving issues where media_type might not match the actual byte format.
  • Improved Login Redirect Handling: The frontend now correctly honors the ?redirect=/path parameter after a successful login, ensuring users are sent to their intended internal destination while preventing unsafe redirects.
  • Reduced Sensitive Data Logging: Logging of validation request bodies has been removed from error handlers to prevent sensitive data from being inadvertently logged.
Changelog
  • backend/app/auth/deps.py
    • Added Request import for handling authentication from request objects.
    • Introduced _unauthorized() and _inactive() helper functions for consistent HTTP 401 responses.
    • Implemented _get_user_from_token() to centralize JWT token decoding and user retrieval logic.
    • Refactored get_current_user() to use _get_user_from_token().
    • Added get_current_user_from_request() to support authentication via both Authorization header (Bearer) and receipt_scanner_token cookie.
    • Defined CurrentUserFromRequest type alias for dependency injection.
  • backend/app/core/config.py
    • Added MAX_UPLOAD_SIZE_MB setting with a default of 10MB.
    • Introduced max_upload_size_bytes property to convert the MB setting to bytes.
  • backend/app/core/error_handlers.py
    • Removed logging of exc.body from validation_exception_handler to prevent logging potentially sensitive request data.
  • backend/app/integrations/pydantic_ai/receipt_agent.py
    • Modified analyze_receipt to always save the PIL Image as 'PNG' format, ensuring consistency for Gemini analysis regardless of the original image format.
  • backend/app/main.py
    • Removed StaticFiles import.
    • Removed the public /uploads static mount, enhancing security by preventing direct access to uploaded files.
  • backend/app/receipt/router.py
    • Added mimetypes and FileResponse imports for serving files with correct media types.
    • Updated CurrentUser dependency to CurrentUserFromRequest in relevant endpoints.
    • Introduced a new GET /{receipt_id}/image endpoint to securely serve receipt images, requiring authentication and performing path resolution.
  • backend/app/receipt/services.py
    • Added Path import for file system operations.
    • Implemented _recalculate_total() helper method to sum item total_price for a given receipt.
    • Added resolve_image_path() to securely resolve and validate image file paths within the upload directory, preventing path traversal attacks.
    • Modified create_from_scan() to enforce MAX_UPLOAD_SIZE_MB during file upload using chunked reads, raising BadRequestError if the limit is exceeded.
    • Updated update_item() to recalculate item.total_price and receipt.total_amount after an item update.
    • Updated create_item() to recalculate receipt.total_amount after a new item is added and ensure the item is correctly added to the receipt's items list.
    • Updated delete_item() to remove the item from the receipt's items list and recalculate receipt.total_amount after an item is deleted.
  • backend/tests/integration/receipt/test_receipt_endpoints.py
    • Added imports for Decimal, BytesIO, Path, and settings.
    • Adjusted test_create_receipt_item to remove original_total and simplify the assertion for total_amount.
    • Added test_get_receipt_image_requires_auth to verify that the new image endpoint requires authentication.
    • Added test_get_receipt_image_returns_file to confirm the image endpoint correctly serves files for authorized users.
    • Added test_scan_receipt_rejects_large_upload to test the backend's enforcement of upload size limits.
  • backend/tests/unit/receipt/test_receipt_service.py
    • Added test_update_receipt_item_recomputes_total to ensure receipt totals are correctly updated when items are modified.
    • Added an assertion for existing_receipt.id not being None in test_update_receipt_with_metadata.
    • Modified test_create_item to include an existing item in the receipt and adjusted the expected flush call count.
    • Modified test_delete_item to include a remaining item in the receipt and adjusted the expected flush call count.
  • frontend/src/app/(app)/receipts/[id]/page.tsx
    • Updated the src attribute for img tags displaying receipt images to use the new authenticated endpoint /api/v1/receipts/${receipt.id}/image instead of the public /uploads path.
  • frontend/src/app/(auth)/login/page.tsx
    • Imported useSearchParams from next/navigation.
    • Implemented logic to read the redirect query parameter and safely navigate the user to the specified internal path after successful login, defaulting to '/'.
  • frontend/src/components/scan/dropzone.tsx
    • Imported toast from sonner for user notifications.
    • Defined MAX_FILE_SIZE_BYTES and MAX_FILE_SIZE_MB constants for client-side upload limits.
    • Added client-side validation to handleFileChange to check if an uploaded file exceeds MAX_FILE_SIZE_BYTES, displaying a toast error if it does.
Activity
  • The pull request was created by cgoncalves94 to address several security, data integrity, and user experience issues.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2583a37822

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread frontend/src/app/(app)/receipts/[id]/page.tsx

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This is a great pull request that addresses several important issues, including a critical security vulnerability with public receipt images, data consistency problems with receipt totals, and missing upload limits. The introduction of a secure, authenticated endpoint for images and the path traversal protection in resolve_image_path are well-implemented. The move to recalculate totals from items is a robust fix for data drift. The changes are well-tested, with new integration and unit tests covering the new logic. I have a few minor suggestions in backend/app/receipt/services.py to simplify the ORM usage and improve efficiency by removing some redundant session operations, but overall this is excellent work.

Comment thread backend/app/receipt/services.py Outdated
- Add same-origin proxy route for receipt images and update frontend usage
- Simplify create_item total recalculation and fix unit test
- Ensure API route cookie access is awaited
@cgoncalves94

Copy link
Copy Markdown
Owner Author

Addressed. Images are now fetched through a same-origin proxy route () that attaches the bearer token from the cookie. The receipt detail page uses that proxy, so it works across frontend/API origins.

@cgoncalves94

Copy link
Copy Markdown
Owner Author

Addressed. Images are now fetched through a same-origin proxy route (/api/receipts/{id}/image) that attaches the bearer token from the cookie. The receipt detail page uses that proxy, so it works across frontend/API origins.

@cgoncalves94

Copy link
Copy Markdown
Owner Author

Addressed. now appends the new item to and recomputes totals without the extra refresh/append check; tests updated accordingly.

@cgoncalves94

Copy link
Copy Markdown
Owner Author

Addressed. create_item now appends the new item to receipt.items and recomputes totals without the extra refresh/append check; tests updated accordingly.

@cgoncalves94 cgoncalves94 changed the title Secure receipt images + upload limits + consistent totals Secure receipt images, upload limits, consistent totals, and AI mismatch reconciliation Feb 8, 2026
@cgoncalves94 cgoncalves94 merged commit 60a2413 into main Feb 8, 2026
1 check passed
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.

2 participants