Skip to content

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.

Updated 02 Nov 2026Checked against cli@de9b52a
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:

  1. 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 @RequestParam value, order.is_paid / .approved / .verified / .payment_status from the request. status, stage, step, state and completed count only on a workflow object (order, payment, checkout, booking, loan, application, ticket, transfer and similar).
  2. An if that advances the workflow because the request says so, such as if request.args.get("payment_status") == "success" or if (req.query.paid === 'true'), followed within six lines by a status change, setStatus, or a fulfil/ship/approve/capture call.

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

  1. Decide each transition on the server from verified facts: query the payment provider, or act on a signed webhook.
  2. Model the workflow as a state machine and reject a requested transition that is not allowed from the current state.
  3. 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.

Edit this pageSources: cli/internal/sast/rules/vnx-841.rego, cli/internal/sast/docs/vnx-841.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.