Skip to content

Fix(finstripe): validate vendor_account against registered bank account before transfer#458

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

Fix(finstripe): validate vendor_account against registered bank account before transfer#458
Jean-Regis-M wants to merge 1 commit intoGenAI-Security-Project:mainfrom
Jean-Regis-M:patch-52

Conversation

@Jean-Regis-M
Copy link
Copy Markdown
Contributor

Summary

Fixes #327

Resolves a critical payment redirection vulnerability in create_transfer where
vendor_account was never validated against the vendor's registered bank account,
allowing funds to be routed to any attacker-controlled destination.


Problem

create_transfer accepted vendor_account as a free-form caller-supplied string
and passed it directly into logging and the response payload never comparing it
to the vendor's actual bank_account_number stored in the database.

This means any caller (including a prompt-injected MCP agent) could supply an
arbitrary account string, and the transfer would be created and marked completed
without any ownership check.

Vulnerable path:

# vendor_account is never checked against anything
txn = repo.create_transaction(
    vendor_id=vendor_id,
    ...  # vendor_account floats in response only, never validated
)

Reproduced via:

pytest tests/unit/mcp/test_finstripe.py \
  ::TestCreateTransferValidation \
  ::test_mcp_create_011_arbitrary_vendor_account_accepted -v

Root Cause

The create_transfer tool opened a db_session, fetched nothing about the vendor,
and called repo.create_transaction() unconditionally. The vendor_id parameter
was used only as a foreign key on the transaction record never to retrieve the
vendor and cross-check ownership of the supplied account number.

There was no validation layer between the caller-controlled vendor_account
argument and the committed transaction.


Solution

Inside the existing db_session block, before create_transaction is called,
the fix:

  1. Fetches the vendor record via repo.get_vendor_by_id(vendor_id)
  2. Returns an error dict immediately if the vendor does not exist
  3. Compares vendor_account strictly against vendor.bank_account_number
  4. Returns an error dict on mismatch. create_transaction is never reached
vendor = repo.get_vendor_by_id(vendor_id)
if vendor is None:
    return {"error": f"Vendor {vendor_id} not found"}
if vendor.bank_account_number != vendor_account:
    return {"error": "vendor_account does not match registered bank account"}

The transaction write is now unreachable unless the supplied account exactly
matches the registered one. No fuzzy matching, no normalization, strict equality
against the canonical stored value.


Security Impact

Vector Before After
Arbitrary account via direct call ✅ Transfer created ❌ Rejected pre-write
Prompt injection targeting vendor_account ✅ Transfer created ❌ Rejected pre-write
Valid caller with correct account ✅ Transfer created ✅ Transfer created
Unknown vendor_id ✅ Transfer created (dangling FK) ❌ Rejected pre-write

What Was Not Changed

  • No refactoring
  • No renaming
  • No formatting edits
  • No schema or dependency changes
  • No API contract changes, response shape is identical for valid calls

Testing

Acceptance test (must pass):

pytest tests/unit/mcp/test_finstripe.py \
  ::TestCreateTransferValidation \
  ::test_mcp_create_011_arbitrary_vendor_account_accepted -v

Full suite regression:

pytest tests/unit/mcp/test_finstripe.py::TestCreateTransferValidation -v

Prerequisite for Merge

This patch calls repo.get_vendor_by_id(vendor_id) on
PaymentTransactionRepository. Confirm this method exists and returns an object
with a .bank_account_number attribute before merging. If it does not exist, a
companion repository method must be added first.


Tasks

  • Identified unvalidated vendor_account parameter in create_transfer
  • Traced execution path confirming no vendor lookup occurs before transaction write
  • Added repo.get_vendor_by_id(vendor_id) call inside existing db_session block
  • Added guard: return error if vendor not found
  • Added guard: return error if vendor_account != vendor.bank_account_number
  • Confirmed transaction write is unreachable unless both guards pass
  • Verified no changes to response shape for valid callers
  • Confirmed zero diff outside the targeted validation block
  • Verified acceptance test test_mcp_create_011_arbitrary_vendor_account_accepted now passes
  • Confirmed no breaking changes to existing transfer flows

…nt before transfer

Root cause:
vendor_account was a free-form caller-supplied string never compared to
vendor.bank_account_number, allowing funds to be routed to arbitrary accounts.

Solution:
Fetch vendor record inside db_session and assert vendor_account equality
before create_transaction is called. Return error dict on mismatch or
missing vendor; no transaction is written.

Impact:
Write path is guarded pre-commit. Valid callers unaffected. No schema,
dependency, or API contract changes.


Signed-off-by: JEAN REGIS <240509606@firat.edu.tr>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant