Skip to content

Fix(finstripe): validate invoice ownership against vendor_id before transfer#445

Open
Jean-Regis-M wants to merge 1 commit intoGenAI-Security-Project:mainfrom
Jean-Regis-M:patch-41
Open

Fix(finstripe): validate invoice ownership against vendor_id before transfer#445
Jean-Regis-M wants to merge 1 commit intoGenAI-Security-Project:mainfrom
Jean-Regis-M:patch-41

Conversation

@Jean-Regis-M
Copy link
Copy Markdown
Contributor

@Jean-Regis-M Jean-Regis-M commented Mar 31, 2026

Closes #351


Summary

create_transfer accepted any vendor_id / invoice_id combination without verifying ownership, allowing a caller to pair a high-value invoice from Vendor A with Vendor B's bank account silently misdirecting funds.

This patch adds a two-step guard directly before repo.create_transaction: existence check, then ownership check. No write occurs unless both pass.


Problem

# Before  no ownership check; any vendor_id/invoice_id pair accepted
txn = repo.create_transaction(
    invoice_id=invoice_id,
    vendor_id=vendor_id,   # caller-controlled  never validated against invoice
    ...
)

A caller (or a prompt-injected agent) could supply:

vendor_id  = vendor_b.id   ← attacker-controlled vendor
invoice_id = invoice_a.id  ← invoice owned by vendor_a

Result: transfer created, funds misdirected. No error raised.


Root Cause

create_transfer never fetched the invoice record prior to writing the transaction. Without fetching it, there was no surface on which to compare invoice.vendor_id against the caller-supplied vendor_id. The missing check is a validation gap not a logic error in existing code, but the complete absence of an authorization guard.


Solution

Fetch the invoice before the write path. Fail fast with an error dict on either missing invoice or ownership mismatch. create_transaction is only reached when both checks pass.

invoice = repo.get_invoice_by_id(invoice_id)
if not invoice:
    return {"error": f"Invoice {invoice_id} not found"}
if invoice.vendor_id != vendor_id:
    return {"error": f"Invoice {invoice_id} does not belong to vendor {vendor_id}"}

txn = repo.create_transaction(...)  # unchanged  only reached on valid ownership

Why this approach

  • Early return before any write no partial state, no rollback needed
  • Error dict shape is consistent with existing get_transfer not-found responses
  • Zero changes to create_transaction call signature or downstream logic

Diff Scope

File Change
finbot/mcp/servers/finstripe/server.py +3 lines inside existing with db_session() block

No other files touched. No refactoring. No renames.


Security Impact

Attack Vector Before After
Cross-vendor invoice pairing Allowed (silent) Rejected with error
Prompt-injected agent misdirecting funds Exploitable Blocked at ownership check
Valid matching vendor/invoice pair Works Unaffected

Edge Cases

Case Behaviour
invoice_id does not exist Returns {"error": "Invoice X not found"} no write
invoice.vendor_id is None None != vendor_id → ownership check fails → rejected
Valid owner, matching vendor_id Passes both checks → existing path unchanged
vendor_id is None or invalid Invoice fetch still resolves; mismatch caught at comparison

Testing

# Target test  must now pass
pytest tests/unit/mcp/test_finstripe.py::TestInvoiceValidation::test_mcp_invval_003_invoice_from_different_vendor_accepted -v

# Regression  must continue to pass
pytest tests/unit/mcp/test_finstripe.py::TestFloatTransfer::test_float_001_valid_transfer_creates_transaction -v

Before: Cross-vendor call returns a completed transfer dict no error.
After: Cross-vendor call returns {"error": "Invoice X does not belong to vendor Y"} transaction record never written.


Checklist


Assumption declared: Fix relies on repo.get_invoice_by_id(invoice_id) being available on PaymentTransactionRepository, consistent with the Bug_140 precedent cited in the issue. Verify method name matches repository implementation before merge.

…ransfer

Root cause:
create_transfer passed caller-supplied vendor_id and invoice_id directly to
repo.create_transaction without verifying invoice.vendor_id == vendor_id,
allowing cross-vendor invoice pairing.

Solution:
Fetch invoice before transaction creation; return error dict if invoice is
not found or vendor_id does not match invoice.vendor_id. No write occurs on
mismatch.

Impact:
No breaking changes. Matching vendor/invoice pairs unaffected. Early return
before any DB write on mismatch — deterministic and zero side-effect.

Signed-off-by: JEAN REGIS <240509606@firat.edu.tr>
@Jean-Regis-M Jean-Regis-M changed the title Fix(finstripe): validate invoice ownership against vendor_id before t… Fix(finstripe): validate invoice ownership against vendor_id before transfer Mar 31, 2026
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.

Bug_142_EVALUATE: MCP-INVVAL-003 — Invoice ownership not validated against vendor_id; cross-vendor payment enabled

1 participant