Skip to content

Fix(chat): resolve None vendor_id silently dispatched to orchestrator#455

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

Fix(chat): resolve None vendor_id silently dispatched to orchestrator#455
Jean-Regis-M wants to merge 1 commit intoGenAI-Security-Project:mainfrom
Jean-Regis-M:patch-50

Conversation

@Jean-Regis-M
Copy link
Copy Markdown
Contributor

Closes #415


Problem

_call_start_workflow(vendor_id: int) has no runtime guard against None.
Python type hints are unenforced at runtime, so when the LLM emits null for
vendor_id, the value silently propagates into task_data and is dispatched
to the orchestrator no error, no signal, phantom-vendor failures downstream.


Root Cause

# finbot/agents/chat.py ~L198–201
if not self.background_tasks:
    return json.dumps({"error": "Workflow engine not available"})

# ← no guard here; vendor_id=None falls straight through
task_data: dict[str, Any] = {
    "vendor_id": vendor_id,   # None written into task payload
    ...
}

The background_tasks guard above demonstrates the existing validation
pattern it simply wasn't applied to vendor_id.


Fix

Two lines inserted immediately after the background_tasks guard, before any
state mutation or task dispatch:

if not self.background_tasks:
    return json.dumps({"error": "Workflow engine not available"})

if vendor_id is None:                                          # ← added
    return json.dumps({"error": "vendor_id is required"})     # ← added

from finbot.agents.runner import run_orchestrator_agent  # pylint: disable=import-outside-toplevel
child_workflow_id = f"wf_chat_{secrets.token_urlsafe(12)}"
task_data: dict[str, Any] = {

Why is None and not if not vendor_id?
0 is falsy but is a technically valid ID value. The guard targets only the
None case as specified in the issue.


Behavior Matrix

Input Behavior
vendor_id=None Guard triggers → {"error": "vendor_id is required"}
vendor_id=0 Passes guard (0 is not None); orchestrator handles semantics
vendor_id=-1 Passes guard; DB/orchestrator rejects invalid FK as before
vendor_id="abc" Passes guard (not None); type error surfaces downstream out of scope
Valid int Guard skipped entirely; zero behavior change

Impact

  • No breaking changes, signature unchanged, return type (str) unchanged
  • Backward compatible, all callers passing a valid int are unaffected
  • No side effects return before any state mutation or dispatch
  • Mirrors existing pattern structurally identical to the background_tasks guard

Testing

# Targeted regression
pytest tests/unit/agents/test_chat_assistant.py::TestBoundaryAndTypeValues::test_chat_boundary_011_vendor_id_none_flows_into_task_data -v

# Full suite
pytest tests/unit/agents/test_chat_assistant.py -v

Before fix vendor_id=None returns status='started', assertion fails:

result = await assistant._call_start_workflow("do something", vendor_id=None)
assert json.loads(result).get("error")  # FAILS

After fix deterministic early return:

result = await assistant._call_start_workflow("do something", vendor_id=None)
assert json.loads(result) == {"error": "vendor_id is required"}  # PASSES

All existing happy-path tests are unaffected; the guard is a new early-return
The branch that valid-vendor_id calls never reaches.


CI

Check Status
Compiles Pure Python, no syntax changes, no new imports
Lint if vendor_id is None: matches surrounding style; no pylint violations
Tests Only the boundary test changes assertion direction (fail → pass)
Dependencies None changed
API contract Method signature and return type unchanged

Tasks

  • Identified root cause: missing runtime guard on vendor_id before task_data construction
  • Inserted is None guard at earliest safe interception point (after background_tasks check, before any dispatch)
  • Verified vendor_id=0 edge case is not blocked by the guard
  • Confirmed zero changes to happy-path flow
  • Confirmed method signature and return type are unchanged
  • Unit test added covering vendor_id=None boundary case
  • Full test suite passes with no regressions

Root cause:
vendor_id: int type hint is not enforced at runtime; LLM-supplied null
bypasses all checks and is written directly into task_data.

Solution:
Add an `is None` guard immediately after the background_tasks check,
returning {"error": "vendor_id is required"} before any dispatch occurs.

Impact:
No breaking changes. Callers with valid vendor_id are unaffected.
Deterministic early return. Zero orchestrator side effects on None input.


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

Development

Successfully merging this pull request may close these issues.

Bug_194_EVALUATE: CHAT-BOUNDARY-011 — vendor_id=None silently forwarded to orchestrator with no validation error

1 participant