Docs

Risk tiers and approvals

Nothing runs without a recorded approval from a named person, and high-risk changes cannot run at all.

Built, not hosted The approval flow runs in the self-hosted application and is tested against mocked AWS. In the hosted service remediation is switched off, and no approved action has run against a live AWS account yet. Hosted findings show the tier and the commands as text.

Risk tiers

TierWaste typesWhat happens
Low riskunassociated_eip, log_group_no_retention, gp2_volumeRuns after approval
Medium riskunattached_volume, orphaned_snapshot, stale_amiRuns after approval. A volume is snapshotted first; an AMI's snapshots are kept; deleting a snapshot has no backup and needs an acknowledgement
High riskidle_nat_gateway, idle_load_balancer, idle_rds, stopped_instance_ebsRecommendation only. The command is shown for reference and cannot be executed
Advisorylambda_x86Guidance only. Moving to arm64 needs a rebuilt deployment package, so it goes through your build or a pull request

The tiers are fixed in code, not in the templates. Any waste type without a tier is treated as high risk.

Routing

OwnerRoute
TerraformA pull request, when a safe edit exists. Otherwise a recommendation
CloudFormationRecommendation. Change it in the stack
Click-opsA CLI plan to approve, when the tier is low or medium
UnknownRecommendation only

An account with no infrastructure as code at all can opt in to CLI fixes for unknown ownership with ALLOW_CLI_WHEN_OWNERSHIP_UNKNOWN=true.

The approval lifecycle

  1. Request. A pending action is created holding the rendered plan: the steps, the equivalent AWS CLI commands and a hash of both.
  2. Approve, reject or snooze. An approval needs a named approver. If the plan is irreversible it also needs acknowledge_irreversible. The approved commands and hash are stored on the record.
  3. Execute. Runs the plan under CostAgentRemediator.
  4. Record. On success the savings ledger gets an entry. Every event is appended to the action record with a time, an actor and detail.

Statuses: pending, approved, rejected, snoozed, executing, succeeded, failed.

Checks at execution

Before any step runs, all of these must hold. If one fails the action is refused and nothing is changed.

  • The approval is younger than APPROVAL_TTL_HOURS (default 72).
  • The plan, rendered again from the stored finding, has the same hash and commands as the approved one.
  • The plan is still executable, and a medium-risk plan with no backup step was acknowledged.
  • The resource is not managed by Terraform or CloudFormation.
  • The account is not the management account, and it has opted in to remediation.
  • The pre-check against live AWS passes: the resource still exists, is still idle, and is still not protected by a keep tag or the allow-list.

Steps that support it are sent with DryRun first. A backup step waits for its snapshot to complete before the delete.

Pre-checks

ActionRefused if
Release Elastic IPThe address is gone or is now associated
Delete volumeThe volume is gone or is no longer unattached
gp2 to gp3The volume is no longer gp2
Delete snapshotThe snapshot backs an AMI, or its source volume exists again
Deregister AMIAn instance was launched from it, or a launch template references it
Set log retentionThe log group is gone or already has a retention period

Each also refuses when the resource has since been keep-tagged.

Approving through the API

POST /api/remediations                      {"finding_id": "f-..."}
POST /api/remediations/<action_id>/approve  {"acknowledge_irreversible": false}
POST /api/remediations/<action_id>/reject   {"reason": "..."}
POST /api/remediations/<action_id>/snooze
POST /api/remediations/<action_id>/execute
GET  /api/remediations

These routes always need an identified caller, even when the deployment allows anonymous reads. They are not part of the versioned OpenAPI document.

Sources: remediation/engine.py, remediation/approvals.py, remediation/prechecks.py, iac/routing.py, app.py, security/auth.py.