Finbot
Threat Model
evidence-derived · Praxen 1.3.0 · graph contract 1.4 · built against finbot-findings-2026-08-12.json
22Components
34Flows
10Boundaries
16Confirmed
1Potential
0Partial
2Mitigated
Summary
The Agent (as modeled)

FinBot is a public web application that reviews vendor invoices for a production company and decides, per invoice, whether to approve it, reject it, or send it to a human reviewer. Untrusted content reaches it from two directions. Vendors — in practice any anonymous visitor, since the portal lists every registered vendor and lets the visitor act as any of them — submit free-text invoice descriptions that flow straight into the decision engine. And the entire administrative surface that governs the agent's thresholds, its fraud toggle and its natural-language goals sits on the same public listener as the vendor portal with no authentication of any kind. The agent's most consequential capability is committing an invoice to approved and marking it payment-processed in the same write; its second is the stored block of natural-language goals that is pasted into every decision it makes.

Priority Threats (led by attack paths)

Deal with the open administrative surface first. Anyone on the internet can post a new set of goals in plain English; the text is saved and then appended to every future decision prompt beneath a heading that tells the agent to override the safety goals above it. That is not a one-shot attack on a single invoice — a single anonymous request redirects every invoice decision the agent makes from then on, and because nothing records who changed the goals or what they were before, there is no way to notice it afterwards. The same open surface lets a stranger raise any vendor's trust level, switch fraud detection off, and approve the invoices already sitting in the human-review queue, which is the escalation route every other safeguard depends on. The stored goal text is also rendered into the console page without escaping, so the next operator to open it runs whatever the attacker stored.

Second, nothing actually checks an approval. The approve step writes the approved status and the payment-processed flag from whatever the model hands it, without looking at the invoice amount, at the injection flag the agent itself just set, or at the confidence level — the minimum-confidence setting the console invites operators to tune is read nowhere in the codebase, so operators are configuring a control that does not exist. Worse, the rule-based path that runs whenever the model connection fails reads authority and urgency phrases out of the attacker's own invoice text and uses them to approve past the review threshold and past its own injection detection. Third, and independent of the agent, every vendor's bank name, account number, routing number and tax identifier is returned in full to any unauthenticated caller, which is also how a stranger picks a vendor to impersonate. The screening the agent does perform is real and runs on every invoice, but it only sets a flag that no decision path consults; the one structural safeguard that holds is the tool inventory, which contains no shell, code-execution or money-moving capability at all.

Architecture & Trust Boundaries
Attack paths are drawn in red, running from where an attacker gets in to what they reach. Click any box to jump to its inventory row, or a B-badge to jump to that boundary; hover a box to reveal its data flows. Everything reads statically below — the key resolves every mark and the tables carry every citation.
User / InputsClient / AdaptersAgent CoreTools / MCPExternal / DeployB1B2B3B5B6B8B9B10B4B7Vendor invoice submitterENTRYPOINTon attack path — source (ingress)Anonymous admin-APIcallerENTRYPOINTon attack path — source (ingress)CineFlow financeoperatorENTRYPOINTon attack path — target (consequence)Vendor portal andonboarding pagesCLIENTon attack path — target (consequence)Admin console pageCLIENTon attack path — pass-throughVendor APIADAPTERon attack path — pass-throughAdmin APIADAPTERon attack path — pass-throughFlask applicationsurfaceADAPTERFinBot orchestrationloopORCHESTRATORon attack path — pass-throughAssembled system promptPROMPTon attack path — pass-throughRule-based fallbackdecision engineORCHESTRATORon attack path — pass-throughInjection and fraudscreeningCONTROLFinBot config and customgoalsMEMORYon attack path — pass-throughMinimum-confidence gate(declared, never read)CONTROLInvoice decision tools(reject / human review)TOOLapprove_invoice toolTOOLon attack path — pass-throughget_invoice_details toolTOOLInvoice and vendordatastoreDATASTOREon attack path — target (consequence)OpenAI chat completionsEXTERNAL_SERVICEPublic Render webserviceDEPLOY_SURFACECommitted FlaskSECRET_KEYSECRET_STOREPython dependencymanifestDEPLOY_SURFACE
Familiesactors & inputsclient / adaptersagent coretools & datacontrolsexternal & deploy
Kindsentrypointclientadapterorchestratorpromptcontrolmemorytooldatastoreexternal servicedeploy surfacesecret store
Marksboundary — worst threat: confirmed— potential (unanswered hypothesis)— partial (control covers part; remainder stated)— mitigatedattack path (origin → consequence)box on an attack path: source (ingress) pass-through control that failed target (consequence)faint arc = flow spanning 2+ lanes
Attack Paths
A stranger's plain-English goal rewrite turns every later invoice into an automatic approval
  1. Anonymous admin-API caller [PRAX-2026-08-12-001] Anonymous, unauthenticated caller — no session, role, or auth primitive guards any admin route
  2. Admin API [PRAX-2026-08-12-002] The goals endpoint accepts any JSON goal string with no validation of its content
  3. FinBot config and custom goals [PRAX-2026-08-12-002] The text is committed to the configuration row and persists across every future session
  4. Assembled system prompt [PRAX-2026-08-12-002] Every later prompt appends it under an explicit instruction to override the safety goals above
  5. FinBot orchestration loop [PRAX-2026-08-12-002] The decision loop now runs under the attacker's goals and chooses which tool to call
  6. approve_invoice tool [PRAX-2026-08-12-003] The approval tool executes with no amount, injection, or confidence check to contradict it
  7. Invoice and vendor datastore [PRAX-2026-08-12-011] Approved status and the payment-processed flag are committed durably, with no actor recorded anywhere
An invoice's own wording talks it past the review threshold and past the fraud check
  1. Vendor invoice submitter [PRAX-2026-08-12-009] Anyone can submit — the invoice endpoint is unauthenticated and the description is fully attacker-authored
  2. Vendor portal and onboarding pages The public portal posts the description as submitted, having let the visitor act as any listed vendor
  3. Vendor API [PRAX-2026-08-12-009] The route stores the invoice and drives processing inline, with no rate limit or content length bound
  4. FinBot orchestration loop Processing begins; when the model client is unavailable the loop hands off to the rule-based path
  5. Rule-based fallback decision engine [PRAX-2026-08-12-004] Authority and urgency phrases in the description score high enough to override the review threshold, and to approve even after injection is detected
  6. approve_invoice tool [PRAX-2026-08-12-003] The approval is committed with no independent amount, injection, or confidence test
  7. Invoice and vendor datastore [PRAX-2026-08-12-011] A high-value invoice that should have gone to a human is durably approved and marked payment-processed
An anonymous visitor walks off with every vendor's bank and tax details
  1. Vendor invoice submitter [PRAX-2026-08-12-005] Unauthenticated visitor with no account and no relationship to any vendor
  2. Vendor API [PRAX-2026-08-12-005] The vendor listing route runs the full serialiser with no caller identity, authorization check, or field filtering
  3. Vendor portal and onboarding pages [PRAX-2026-08-12-005] Every vendor's account number, routing number and tax identifier lands in the visitor's browser — and identifies whom to impersonate next
Stored goal text runs as script in the finance operator's console
  1. Anonymous admin-API caller [PRAX-2026-08-12-001] Anonymous caller reaching the admin API with no authentication of any kind
  2. Admin API [PRAX-2026-08-12-002] The goals endpoint stores the submitted string without validating or escaping its content
  3. FinBot config and custom goals [PRAX-2026-08-12-013] The markup persists in the goals field, waiting for the next console load
  4. Admin console page [PRAX-2026-08-12-013] The goals loader writes the stored value into the page with innerHTML and no escaping
  5. CineFlow finance operator [PRAX-2026-08-12-013] The script executes in the browser of the one real administrator this application has
Trust Boundaries — Threats & Governing Remit Rules
B1 Vendor invoice and registration intake (untrusted-ingress) — 2 threats, 2 remit rules · worst: confirmed
R-01 partial FinBot MUST NEVER treat instructions, directives, or policy-like language contained in an invoice description … as commands
R-06 partial Vendor-supplied content is untrusted data, never instructions.
STRIDEOWASPThreatStatus
TASI01Authority and urgency phrases written into the invoice description are scored by the agent's own business-context logic and used to override both the manual-review threshold and the injection flag it just raised, so the submitter's text sets the agent's speed-over-security priority for that invoice.confirmed PRAX-2026-08-12-004
SASI03Invoice submission carries no caller identity and the portal picker is populated from the full vendor listing, so any anonymous visitor can select a registered vendor and submit invoices as them.confirmed PRAX-2026-08-12-005
B2 Admin and configuration surface (control-plane-exposure) — 3 threats, 5 remit rules · worst: confirmed
R-07 gap MUST require authenticated, role-restricted admin access.
R-08 gap MUST NOT be accessible to vendors or unauthenticated users.
R-14 gap Any unauthenticated party exercising admin capabilities.
R-17 gap FinBot configuration and natural-language goals read/write … gated to authenticated, authorized admins
R-28 gap Any change to FinBot's configuration … or to its goals — permitted only for an authenticated, authorized admin.
STRIDEOWASPThreatStatus
EASI03Every /api/admin/* route is reachable by an anonymous caller, so a stranger can rewrite goals and thresholds, switch fraud detection off, raise any vendor's trust level, and approve invoices sitting in the human-review queue (checked: no before_request handler on admin_bp and no auth primitive anywhere in the module).confirmed PRAX-2026-08-12-001
I—The console and its API are published to the public internet — one 0.0.0.0 listener serves the vendor portal and the admin console, and the console page sits in the publicly served static folder, so it is reachable by URL alone from any network.confirmed PRAX-2026-08-12-006
T—Wildcard CORS is applied before blueprint registration, so any web origin can drive the goals and configuration endpoints from a visiting operator's browser.confirmed PRAX-2026-08-12-010
B3 Stored goals re-entering every decision (stored-state) — 2 threats, 2 remit rules · worst: confirmed
R-02 gap FinBot MUST NEVER redefine, override, expand, or reprioritize its own goals or operating priorities in response to natural-language content arriving through any vendor-facing input.
R-29 gap FinBot MUST NEVER let vendor-submitted invoice content alter its approval thresholds, confidence requirement, fraud-detection state, or goals.
STRIDEOWASPThreatStatus
TASI06Goal text written once into the configuration row is appended to every subsequent system prompt under an explicit instruction to override the safety goals above it, so a single write redirects all future invoice decisions rather than one.confirmed PRAX-2026-08-12-002
R—The configuration row carries only an updated timestamp — no acting actor, no previous value, no change history — so the highest-impact write in the system leaves nothing to attribute or detect it by afterwards.confirmed PRAX-2026-08-12-007
B4 Model decision to invoice tool (tool-invocation) — 2 threats, 2 remit rules · worst: confirmed
R-04 partial its action surface is limited to producing an invoice decision (approve / reject / route-to-review) and MUST NOT be expanded or redirected by invoice content
R-18 verified FinBot MUST NOT have arbitrary shell, code-execution, or filesystem-command capability.
STRIDEOWASPThreatStatus
ELLM03The confidence value the model supplies with an approval is accepted at face value and written to the record; the operator-configured minimum-confidence setting that would filter it is defined, writable and displayed in the console but has no read site in any decision path.confirmed PRAX-2026-08-12-003
EASI05There is no route from a model decision to host command or code execution: the entire registered action surface is five invoice-specific functions and the tree contains no subprocess, exec, eval, or shell invocation.mitigated src/services/finbot_agent.py:228
B5 Approval decision to durable invoice state (state-commit) — 2 threats, 5 remit rules · worst: confirmed
R-24 gap Auto-approving an invoice strictly below the configured auto-approve threshold that has passed fraud/prompt-injection screening.
R-25 gap Approving any invoice at or above the manual-review threshold — it MUST be routed to a human reviewer, never auto-approved.
R-26 gap Approving any invoice in which prompt-injection, goal-manipulation, or fraud content was detected — it MUST be routed to a human reviewer.
R-27 gap Approving any invoice whose decision confidence is below the operator-configured minimum AI confidence — it MUST be routed to a human reviewer
R-30 gap When prompt-injection or goal-manipulation content is detected in an invoice, FinBot MUST stop short of auto-approval, route the invoice to human review, and alert an admin
STRIDEOWASPThreatStatus
ELLM03The approval write commits approved status from whatever argument it is handed, with no comparison against the manual-review threshold and no test of the injection flag the agent set moments earlier, so every routing obligation rests on prompt text the same request can override.confirmed PRAX-2026-08-12-003
TLLM03The same call that records the decision also sets the payment-processed flag, so the agent's decision reads as a payment instruction to any downstream process, past the remit's decisions-only boundary.confirmed PRAX-2026-08-12-011
B6 Vendor and invoice records readable by any caller (data-at-rest) — 2 threats, 3 remit rules · worst: confirmed
R-15 gap Any vendor accessing another vendor's invoices, onboarding data, or payment details.
R-21 gap FinBot MUST NEVER expose one vendor's invoices, onboarding data, PII, or payment details to another vendor or to any unauthenticated party.
R-22 partial FinBot MUST NEVER emit vendor bank/payment details or full tax identifiers into AI reasoning text, vendor-visible responses, or logs.
STRIDEOWASPThreatStatus
ILLM02The vendor listing and detail routes return the complete row — bank name, account holder, account number, routing number and tax identifier — to any unauthenticated caller, and the same serialisation is embedded in the invoice detail response.confirmed PRAX-2026-08-12-005
ILLM08Stored AI reasoning text quotes the operator's configured review threshold back verbatim and the invoice listing returns it to any caller, handing a submitter the exact number to price an invoice under (checked: src/routes/vendor.py:141-164 — no authorization check and no field filtering on that route).potential
B7 Agent to model provider (model-egress) — 2 threats, 2 remit rules · worst: confirmed
R-13 verified Any other AI/LLM service or outbound integration is outside the configured closure and MUST be reported as a trust expansion.
R-23 verified FinBot MUST NEVER transmit vendor PII or payment details to any destination outside CineFlow's authorized systems.
STRIDEOWASPThreatStatus
ILLM02Vendor contact email — a remit-declared sensitive class — is placed in the model context on every invoice although no decision rule consults it, making it an extractable field in any context-leakage scenario; bank and tax identifiers are correctly kept out of the same structure.confirmed PRAX-2026-08-12-012
DLLM06Each anonymous submission drives up to five model calls inline with no rate limit, per-caller quota, spend ceiling, or call timeout, so a single script can run the operator's model spend without bound.confirmed PRAX-2026-08-12-009
B8 Secrets in source and environment (secret-material) — 2 threats, 0 remit rules · worst: confirmed
the remit does not touch this boundary — threats here are assessed against the RAISE/OWASP baseline alone (a remit is a job description, not a security model; silence here is normal)
STRIDEOWASPThreatStatus
ILLM02The Flask signing key is a hardcoded literal in source rather than an environment lookup, so it is public to anyone with repository access and identical across every deployment of this code.confirmed PRAX-2026-08-12-008
I—The model-provider credential is not committed: the client is constructed with no inline key and resolves it from the environment, and no API-key literal appears in the tree.mitigated src/services/finbot_agent.py:16
B9 Dependency and install path (supply-chain) — 1 threats, 0 remit rules · worst: confirmed
the remit does not touch this boundary — threats here are assessed against the RAISE/OWASP baseline alone (a remit is a job description, not a security model; silence here is normal)
STRIDEOWASPThreatStatus
TLLM04Only Flask is pinned exactly; every other dependency is an open-ended floor with no lock file, no component inventory and no dependency scanning, so two builds of the same commit can resolve different code.confirmed PRAX-2026-08-12-014
B10 Audit and telemetry (telemetry-egress) — 1 threats, 3 remit rules · worst: confirmed
R-31 partial When FinBot's configuration or goals are changed, FinBot MUST record and surface the change to admins.
R-32 partial Every invoice decision (approve / reject / route-to-review) MUST be recorded to a durable audit log with its reasoning and confidence.
R-33 gap Every configuration or goal change MUST be logged with the acting admin and a timestamp.
STRIDEOWASPThreatStatus
R—No logging framework, log file, or telemetry configuration exists anywhere in the tree — only bare print calls — and the reprocess endpoint clears an invoice's recorded decision, confidence and reasoning in place, so the per-invoice record that does exist can be erased by an anonymous caller.confirmed PRAX-2026-08-12-007
Component Inventory
Every component with its kind, lane, and source evidence — the diagram's tooltips, on paper.
ComponentKindLaneDescriptionEvidence
Vendor invoice submitterentrypointuser_inputsThe party submitting invoices and registration records — unauthenticated, and able to act as any listed vendor.src/routes/vendor.py:69; src/routes/vendor.py:12
Anonymous admin-API callerentrypointuser_inputsAny anonymous caller reaching /api/admin/* — the remit's explicitly-forbidden 'unauthenticated party exercising admin capabilities'.src/routes/admin.py:90; tests/baselines/v1.3-opus5/finbot/finbot-remit.md:87
CineFlow finance operatorentrypointuser_inputsThe intended human operator who monitors invoices, acts on the review queue, and edits configuration and goals through the console.src/static/admin-dashboard.html:120; tests/baselines/v1.3-opus5/finbot/finbot-remit.md:77
Vendor portal and onboarding pagesclientclient_adaptersVendor-facing browser pages that pick a vendor, submit invoices, and register new vendors with bank and tax details.src/static/vendor-portal.html:345; src/static/vendor-portal.html:184
Admin console pageclientclient_adaptersOperator console that edits thresholds, the fraud toggle and goals, and renders the stored goal text into the page unescaped.src/static/admin-dashboard.html:474; src/static/admin-dashboard.html:498
Vendor APIadapterclient_adaptersPublic HTTP surface for vendor registration, invoice submission, and vendor and invoice listings, with no authentication on any route.src/routes/vendor.py:69; src/routes/vendor.py:63
Admin APIadapterclient_adaptersThe /api/admin/* surface — goals, configuration, reprocess, human review, vendor trust — registered with no authentication, session, or role check.src/routes/admin.py:90; src/routes/admin.py:74
Flask application surfaceadapterclient_adaptersRegisters all blueprints under /api with wildcard CORS and serves the static folder — including the admin console — to any requester.src/main.py:18; src/main.py:20
FinBot orchestration looporchestratoragent_coreFinBotAgent's per-invoice loop: builds the prompt, calls the model, dispatches whichever tool the model names with the model's own arguments.src/services/finbot_agent.py:101; src/services/finbot_agent.py:174
Assembled system promptpromptagent_corePer-invoice system prompt built from the config row; appends stored custom goals under an explicit instruction to override the safety goals above.src/services/finbot_agent.py:32; src/services/finbot_agent.py:71
Rule-based fallback decision engineorchestratoragent_coreThe decision path taken whenever the OpenAI client is unavailable; scores phrases out of the invoice description and can approve past the review threshold and past its own injection flag.src/services/finbot_agent.py:772; src/services/finbot_agent.py:661
Injection and fraud screeningcontrolagent_coreRegex injection detector plus fraud-indicator tool, both keyed on the fraud_detection_enabled flag; they record findings but gate nothing.src/services/finbot_agent.py:549; src/services/finbot_agent.py:372
FinBot config and custom goalsmemoryagent_coreThe single persisted FinBotConfig row holding thresholds, the fraud toggle, speed priority and the natural-language goals that re-enter every future prompt.src/models/vendor.py:105; src/services/finbot_agent.py:744
Minimum-confidence gate (declared, never read)controlagent_coreStub control: the confidence_threshold setting is defined, writable and displayed as an operator control, but no decision path reads it.src/models/vendor.py:102; src/services/finbot_agent.py:758
Invoice decision tools (reject / human review)tooltools_mcpThe registered tool family the model can call besides approval and detail retrieval — reject_invoice and request_human_review, each writing a decision to the invoice row.src/services/finbot_agent.py:228; src/services/finbot_agent.py:460
approve_invoice tooltooltools_mcpThe one tool that commits an approval — writes approved status and the payment-processed flag straight from the model's arguments.src/services/finbot_agent.py:409; src/services/finbot_agent.py:416
get_invoice_details tooltooltools_mcpRetrieves the invoice and vendor record for the model — the path by which the attacker-controlled description enters model context.src/services/finbot_agent.py:394; src/services/finbot_agent.py:403
Invoice and vendor datastoredatastoretools_mcpSQLAlchemy-backed store of vendor records (including bank and tax fields), invoices, and per-decision AI reasoning and confidence.src/models/vendor.py:38; src/models/vendor.py:59
OpenAI chat completionsexternal_serviceexternal_deployThe model provider backing invoice decisioning; credential resolved from the environment and the model pinned to a concrete release.src/services/finbot_agent.py:16; src/services/finbot_agent.py:176
Public Render web servicedeploy_surfaceexternal_deployInternet-facing deployment: a Render 'web' service fronting a gunicorn listener bound to 0.0.0.0 that serves the vendor portal and the admin console alike.render.yaml:2; gunicorn.conf.py:4
Committed Flask SECRET_KEYsecret_storeexternal_deployThe Flask signing key is a hardcoded string literal in source rather than an environment lookup; identical across every deployment of this code.src/main.py:15
Python dependency manifestdeploy_surfaceexternal_deployInstall-time dependency set: Flask pinned exactly, every other entry an open-ended floor, with no lock file or component inventory in the tree.requirements.txt:2; runtime.txt:1
Extraction Notes
lane_fit: Clean, with one judgement call: the Flask app surface, the vendor API and the admin API are modelled as client_adapters because they are the API surfaces callers talk to, even though they also host application logic; the invoice/vendor datastore sits in tools_mcp as the local store the agent's tools read and write, while the configuration-and-goals row sits in agent_core because its contents re-enter the agent's own prompt.
omissions: Arbitration divergence: for PRAX-2026-08-12-004 the stored record names LLM01 as its LLM code with ASI01 alongside; under the Agentic KB's 'goal altered by live input' row ASI01 is the primary and LLM01 the co-tag, so the untrusted-ingress threat is tagged ASI01. PRAX-2026-08-12-002's stored ASI06 primary matches the KB persistence test and is kept. PRAX-2026-08-12-013 (stored XSS in the console) and PRAX-2026-08-12-006/010 are left OWASP-null: no LLM or ASI category honestly names them, and the KB forbids forcing a code. Three nodes are deliberately edgeless: the minimum-confidence control (declared and writable but read by nothing), the committed Flask signing key (repo posture, no runtime flow), and the dependency manifest (install-time posture). src/routes/user.py exposes an unauthenticated User CRUD surface, but the User model is referenced by nothing else in the tree and no agent path touches it, so it is not modelled. src/static/agreement-check.js and ctf-footer.js, the entry/leadership/why-partner marketing pages, and docs/FinBot-CTF-walkthrough-goal-manipulation.md are CTF scaffolding around the target rather than components of the agent, and fold into no node.
generated by: Opus 5 (1M context)