Skip to content

security(spp_programs): async mark_*_as_done/_failed callbacks can clear operation locks via RPC (force-unlock bypass) [Severity: Low-Medium] #337

Description

@gonzalesedwin1123

Follow-up discovered while fixing the Force Unlock authorization gap (PR #336). Deliberately left out of that PR's scope because the correct fix is different in kind and higher-risk.

Severity: Low–Medium (residual lock-clearing bypass)

Problem

PR #336 gates the manual escape hatch action_force_unlock to base.group_system. But is_locked on spp.cycle / spp.program is also cleared by the async completion callbacks on the manager models:

  • spp_programs/models/managers/cycle_manager_base.pymark_import_as_done/_failed, mark_prepare_entitlement_as_done/_failed, mark_check_eligibility_as_done/_failed
  • spp_programs/models/managers/program_manager.pymark_enroll_eligible_as_done/_failed
  • spp_programs/models/managers/payment_manager.pymark_job_as_done/_failed
  • spp_programs/models/managers/entitlement_manager_base.pymark_job_as_done/_failed

These are public methods and reachable via RPC/call_kw. A user with access to the manager model and the target cycle could call e.g. mark_import_as_done(cycle, "…") directly to clear an active operation lock — the same effect PR #336 blocks on action_force_unlock, via a different door.

Why it was not fixed in PR #336

These callbacks must run in the initiating (non-admin) user's context — the async job executes as whoever started the operation, and the callback is how the pipeline releases its own lock. Applying the base.group_system guard used for action_force_unlock would break normal operation. The correct fix is a different design:

  • a job-identity / one-shot token so only the real on_done/on_error callback can invoke the unlock, or
  • moving the unlock into a trusted callback context not directly RPC-addressable, or
  • splitting "do the completion work" from "release the lock" so the release is not a standalone public method.

Proposed

Decide on one of the above and implement it as its own reviewed change, with tests that a non-admin cannot clear a lock by calling a mark_* method out of band while still allowing the genuine job callback to release it.

Related: #336.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions