Skip to content

sec(ladder): LadderExecutionEnabled follows #1765's config-driven-scheduled-execution pattern (currently inert) #178

Description

@cristim

Summary

Found while investigating LeanerCloud/cloud-commitments-cli#1765 (config-driven scheduled auto-exchange bypasses the execute:ri-exchange carve-out). The commitment-laddering feature has the identical pattern -- a config write gated only on update:config controls whether a scheduled task's write side is armed with real cloud-provider clients -- but it is not exploitable today because the planning engine never actually calls the write-side methods. This issue exists so the gap is discoverable by whoever wires that engine, rather than only living as a comment on a different issue's thread.

The pattern

GlobalConfig.LadderingEnabled + LadderExecutionEnabled (internal/config/types.go:67-80) are both writable only via update:config (PUT /api/config -> updateConfig, internal/api/handler_config.go:61). Both are threaded into the scheduled TaskLadderRun task:

handleLadderRun (internal/server/handler_ladder.go:83)
  -> runLadderConfigs -> processOneLadderConfig
  -> buildAndWireCapability(ctx, region, accountID, executionEnabled)   (handler_ladder.go:254)
  -> wireLadderWriteSide(ctx, executionEnabled, ...)                    (internal/server/ladder_write.go:116)

When executionEnabled (== globalCfg.LadderExecutionEnabled) is true, wireLadderWriteSide calls awsladder.WireWriteSide, which wires real EC2 and Savings Plans clients plus an exchangeRunnerAdapter that can trigger RI-exchange auto-execution (the same exchange.RunAutoExchange at the center of LeanerCloud/cloud-commitments-cli#1765) onto the LadderCapability. This is exactly LeanerCloud/cloud-commitments-cli#1765's shape: a config write gated only on update:config controls whether a scheduled task can act with real money-moving authority, with no execute:purchases/Purchaser-group check anywhere on the config-write path.

Why it is not exploitable today

handleLadderRun's own docstring states it plainly: "plan-only: no PurchaseLayer, ReshapeBuffer, email, or approval tokens" (internal/server/handler_ladder.go:82). Confirmed by grep: there are zero non-test callers of .PurchaseLayer( or .ReshapeBuffer( on the LadderCapability interface anywhere in the codebase. wireLadderWriteSide builds and threads the write-side wiring end-to-end, but nothing in the current planning engine invokes it -- LadderExecutionEnabled = true currently wires real AWS clients that are never called.

Why this needs its own issue instead of a note on LeanerCloud/cloud-commitments-cli#1765

Inert-today is exactly the state that stops being true the moment someone wires the ladder engine to actually call PurchaseLayer / ReshapeBuffer (the later phase of issue LeanerCloud/cloud-commitments-go#27). Whoever does that work needs to know this landmine exists before shipping it, and a comment buried in LeanerCloud/cloud-commitments-cli#1765's thread is invisible to someone searching the ladder code or working LeanerCloud/cloud-commitments-go#27. This issue is the thing that surfaces when they look.

Suggested fix direction

When the ladder engine is wired to actually execute (future work on LeanerCloud/cloud-commitments-go#27), apply the same fix LeanerCloud/cloud-commitments-cli#1765 recommends there: require execute:purchases (or Purchaser-group membership) specifically for the write that flips LadderExecutionEnabled (or LadderingEnabled + LadderExecutionEnabled together) to true, via a shared helper analogous to whatever LeanerCloud/cloud-commitments-cli#1765 lands, rather than relying on update:config alone. Do not block on this issue before LeanerCloud/cloud-commitments-cli#1765 -- LeanerCloud/cloud-commitments-cli#1765 is the live, exploitable instance; this one is preventive.

Refs LeanerCloud/cloud-commitments-cli#1765, LeanerCloud/cloud-commitments-cli#1758, LeanerCloud/cloud-commitments-cli#1644, LeanerCloud/cloud-commitments-go#27.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions