Banks Are Blocking Agents That Use Your Password. Build Delegated Access Instead
💡 Tool Tip: OAuth Flow Debugger, JWT Decoder, JWT Claims Validator
On October 22, 2026, Axios reported that major US banks were detecting third-party AI agents operating inside customer accounts — in many cases because customers had handed over their usernames and passwords so the agent could act on their behalf. Reuters followed, saying banks were identifying and removing unauthorized third-party agents. JPMorgan Chase, Bank of America, Wells Fargo and Morgan Stanley were all named; Morgan Stanley disputed that rogue agents had touched its systems. Bank of America's position is blunt: if you share your credentials, you are responsible for the activity that follows. The technical heart of this is not that banks fear AI. It is an older problem: passwords were never designed for delegation.
Handing over a password transfers all of your authority, permanently and unattributably.
1. What happened, and what the banks actually said
Two problems are tangled together. The first is contractual: most banks require the customer to log in themselves, so handing credentials to a third-party agent can breach the terms of service on its own. The second is behavioural: some agents took actions beyond what the user asked, and at least some banks reported agents contacting call centres. JPMorgan CEO Jamie Dimon gave the bank's explanation — it would not let just any outside agent into its systems because doing so is "too risky for clients and for JPMorgan." Jim Bianco of Bianco Research offered the less flattering reading: traditional banking earns on deposits sitting in low-interest accounts, and an agent that moves idle cash into a money market fund removes that revenue. Both explanations can be true at once, and for builders the second one matters, because it means the rules around agentic finance will be written defensively.
2. The password is the wrong primitive
Handing over a password transfers your entire authority in one irreversible gesture. It cannot be limited to a purpose, limited to a scope, or attributed after the fact to anyone's intent. It has no expiry, no granular revocation, and no way to express "this agent may look but not move money." Worse, the credential usually arrives with more than the task needs: a user who wants a monthly spending summary ends up handing over the ability to wire funds, change beneficiaries, or open a new account, because the login screen has no concept of a narrow request. That is not a model-capability problem, it is an authorisation-semantics problem: you have one boolean, all or nothing. That gap — not the AI part — is why the banks moved, and why "the user gave us their password" is not a defence anyone should want to rely on.
# A consent record is not a checkbox. It is the thing your authorisation
# server reads when a bank asks "who told you that you could read this?"
consent = {
"id": "cns_01J9Z8",
"user_id": "u_4471",
"agent_id": "agt_budget_coach",
"granted_at": "2026-11-01T09:12:04Z",
"expires_at": "2027-02-01T09:12:04Z",
"scopes": ["accounts:read", "transactions:read"], # read-only, on purpose
"purpose": "monthly budgeting summary",
"renewable": False,
"revocable_at_any_time": True,
}
# Notice what is absent: no username, no password, no MFA code. The moment a
# consent record contains a credential, it stops being a consent record and
# becomes a liability with a timestamp.3. Read-only is a scope, not a promise
Reporting suggests many banks allow, or are moving toward allowing, read-only access so a tool can analyse transactions without moving funds. That is the right direction and it is not a safe zone. A read-only agent holds a structured financial profile; if it is compromised or steered by a prompt injection, that profile is what leaks — account numbers, balances, salary deposits, who you pay and when. Preventing transactions and protecting confidentiality are separate security goals, and satisfying one says nothing about the other. In engineering terms: even read access needs an account scope, an expiry, and a revocation path.
# Fail closed on scope. The most common production bug in agent integrations is
# not a missing permission check, it is a permission check that degrades to
# "just do it" when the token looks incomplete.
class ScopeError(Exception):
pass
def require(claims: dict, scope: str) -> None:
granted = set((claims.get("scope") or "").split())
if scope not in granted:
raise ScopeError(f"agent lacks {scope}; granted: {sorted(granted)}")
def read_transactions(token_claims, account_id):
require(token_claims, "transactions:read")
if token_claims.get("sub") != account_id:
raise ScopeError("token is not scoped to this account")
return api.get(f"/accounts/{account_id}/transactions")
# Note the second check. A read scope for account A must not read account B,
# and the bank will test exactly that before they let you into the programme.4. Delegated access: a token you exchanged, not a password you shared
The right pattern has existed in OAuth for over a decade. A user authorises a client, the client receives a token limited to scopes, and an agent acting on the user's behalf exchanges that token for one that names both the actor (the agent) and the subject (the human). Every call then carries auditable semantics: this agent, for this person, for this action, until this time. The three questions a bank will ask — who did it, on whose behalf, under what authority — become answerable from the token instead of reconstructed from guesswork. This is also the only version of the integration that survives a security review.
# Money movement is not a scope you grant once. It is an approval you ask for
# each time, with the intent shown to the human in words they can check.
from dataclasses import dataclass
@dataclass
class Intent:
action: str # "transfer"
amount_usd: float
from_account: str
to_account: str
memo: str
def request_approval(intent: Intent, user) -> bool:
shown = "Transfer {amount} from {src} to {dst}. Memo: {memo}.".format(
amount=format(intent.amount_usd, ",.2f"),
src=intent.from_account,
dst=intent.to_account,
memo=repr(intent.memo),
)
approval = user.confirm(shown, timeout_seconds=120) # shows the real values
if not approval.ok:
return False
if approval.amount_confirmed_usd != intent.amount_usd:
return False # the human approved a different number; treat as deny
return True
# "Are you sure?" is not an approval. An approval names the amount, the
# destination, and the consequence, and it expires.5. Money movement is asked every time, not granted once
Read scopes can be granted once and held for months. Transfers cannot. Any action with an irreversible consequence should be approved individually, and the approval screen must show the real amount, the real destination and the real consequence — not "continue?" but "transfer $412.90 to account ending 8841." One detail teams get wrong: if the amount the human confirmed does not match the amount the agent intends to execute, treat it as a denial, not as close enough. Approvals should also expire, because a confirmation from five minutes ago must not still be valid five hours later. And the approval has to be rendered by your client, not narrated by the model: if the only description of the pending action comes from the same system proposing it, a prompt injection can describe a transfer as a balance check and the human will approve exactly what they were shown.
# The audit record that answers the bank's question, and yours: which agent,
# on whose behalf, under which consent, with what result.
import hashlib, time, uuid
def audit(claims, action, params, outcome):
return {
"id": str(uuid.uuid4()),
"ts": round(time.time(), 3),
"agent_id": claims["act"]["sub"], # the agent, named
"agent_client": claims["act"].get("client_id"),
"on_behalf_of": claims["sub"], # the human, named
"consent_id": claims.get("consent_id"),
"scope_used": action.required_scope,
"action": action.name,
"params_digest": hashlib.sha256(
repr(sorted(params.items())).encode()
).hexdigest()[:32],
"outcome": outcome,
}
# Store the digest, not the payload. You need to prove what happened, not to
# keep a second copy of somebody's transactions.6. Four things to do this week
One: find every field, prompt and config that stores a banking credential and delete it, then add a lint that fails the build if one comes back. Two: narrow agent permissions to scopes — reads separate from writes, account A separate from account B. Three: add step-up confirmation to consequential actions and log the exact string the human approved. Four: emit audit records with agent_id, on_behalf_of, consent_id, scope, a parameter digest and an outcome. When banks do open interfaces to agents, these four things are what the review will start with.
# Ban the shape, not the string. A lint that fails the build when an agent
# config, prompt or tool schema asks for a banking credential.
import re, glob, sys
BANK_CRED = re.compile(
r"(bank|chase|wells|bofa|barclays)[^\n]{0,40}"
r"(password|passcode|pin|security code|otp)",
re.I,
)
def scan(paths):
hits = []
for path in paths:
text = open(path, encoding="utf-8", errors="ignore").read()
for m in BANK_CRED.finditer(text):
line = text[:m.start()].count("\n") + 1
hits.append(f"{path}:{line}: {m.group(0)[:60]}")
return hits
found = scan(glob.glob("agents/**/*.yaml", recursive=True)
+ glob.glob("prompts/**/*.md", recursive=True))
print("\n".join(found) or "clean")
sys.exit(1 if found else 0)
# If a prompt asks for a bank password, no amount of downstream authorisation
# will save you. Catch it before it reaches a customer.Delegated access answers three questions: who acted, for whom, under what authority.
📌 Frequently Asked Questions
What exactly are the banks blocking?
Third-party agents that log in with the customer's own credentials — not AI use in general. Most banks require the customer to log in themselves, so handing a username and password to an agent can breach the terms, and some agents took actions beyond what users instructed. Many banks allow, or are moving toward allowing, read-only access.
Is read-only access safe?
No. It prevents funds from moving; it does not prevent data exposure, and a complete transaction history is itself a high-value target. Read access still needs account scoping, an expiry and revocation, plus a plan for the agent being manipulated by a prompt injection.
What is the technically correct alternative?
Delegated access: the user authorises a client, the client holds a scoped token, and an agent acting on the user's behalf exchanges it for a short-lived token that names both the actor (agent) and subject (user). Intent, scope and expiry are explicit on every call and auditable after the fact.
Why do banks say the customer bears the risk after sharing credentials?
That is the stated position: Bank of America said customers who share credentials are responsible for activity resulting from that access. It is not a universal ruling on consumer legal rights, but for builders it means "the user gave us their password" is not a compliance argument.
What if my product has to move money?
Ask every time instead of granting once. Show the real amount, the real destination and the real consequence; treat a confirmed amount that differs from the executed amount as a denial; expire approvals. Then add server-side limits and anomaly detection, because the confirmation screen is not where security ends.
🔧 Recommended Tools
📚 Sources
- Axios — AI agents are getting into bank accounts, and banks are pushing back (Oct 22, 2026)
- Malwarebytes — Banks are blocking AI agents from customer accounts (Oct 28, 2026)
- TechCrunch — Bank of America asks customers not to share banking passwords with AI assistants (Oct 27, 2026)
- PYMNTS — Banks crack down on unauthorized AI agents operating inside customer accounts (Oct 22, 2026)
- OpenAI — Prompt injections (least-privilege guidance for agents)