VNX-841: Improper Enforcement of Behavioral Workflow
Reports workflow state decided by the client: a status, paid, approved or verified field set from a request value, or an if that advances the workflow because a request parameter says so, in Java, JavaScript/TypeScript and Python.
| Field | Value |
|---|---|
| Rule ID | VNX-841 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | Java, JavaScript / TypeScript, Python |
| CWE | CWE-841 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | flow-ordering workflow state-machine cwe-841 |
| Source | internal/sast/rules/vnx-841.rego |
Overview
VNX-841 maps to CWE-841: Improper Enforcement of Behavioral Workflow. A multi-step process (pay, then ship; apply, then approve) is only enforced if the server decides when a step has happened. Two shapes are reported:
- A workflow-state field set from a request value, directly or through one assignment:
order.status = req.body.status,.update(status=request.POST["status"]),loan.setStatus(...)with a@RequestParamvalue,order.is_paid/.approved/.verified/.payment_statusfrom the request.status,stage,step,stateandcompletedcount only on a workflow object (order, payment, checkout, booking, loan, application, ticket, transfer and similar). - An
ifthat advances the workflow because the request says so, such asif request.args.get("payment_status") == "success"orif (req.query.paid === 'true'), followed within six lines by a status change,setStatus, or afulfil/ship/approve/capturecall.
Why This Matters
A client that can set the next state can skip the step in between: it can mark an unpaid order paid by calling the return URL with ?paid=true, or approve its own application.
What Gets Flagged
// FLAGGED: the redirect's query string decides the order is paid
if (req.query.paid === 'true') {
checkout.status = 'complete';
await fulfil(checkout);
}
// FLAGGED: the client picks the order's next state
order.setStatus(request.getParameter("status"));
Not flagged: state set from a constant after a server-side check (stripe.checkout.sessions.retrieve(...), a verified webhook), a requested transition checked against an allowed-transitions table, handlers behind an admin or role check or under an admin path, response status codes, a profile status or an address state.
Remediation
- Decide each transition on the server from verified facts: query the payment provider, or act on a signed webhook.
- Model the workflow as a state machine and reject a requested transition that is not allowed from the current state.
- Restrict manual state changes to authorised roles.
References
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-841
// nosec vnx-841
Or record an ignore rule: vulnetix ignore add --rule vnx-841 --reason "...". See Suppressing findings.