From tag sprawl to spend you can attribute

tagctl audits, fixes and enforces cloud resource tags across AWS. Scan what is wrong, review the plan, then apply it.

Workflow

Scan, plan, apply

Each step writes a file the next one reads. Nothing in your account changes until you have read the plan and confirmed it.

~/infraoutput/
$ tagctl scan
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
                  Tag Compliance Report
                  2026-02-02 10:51:48
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Overall: 65% compliant █████████████░░░░░░░ (98/150 resources)

By Account:
  ACCOUNT         TOTAL  COMPLIANT  COMPLIANCE
  aws/production  100    85         85% ████████░░
  aws/staging     50     13         26% ██░░░░░░░░

By Required Tag:
  TAG          STATUS  PRESENT  MISSING  INVALID  COMPLIANCE
  environment  PASS    140      10       0        93% █████████
  cost-center  FAILED  98       52       5        62% ██████░░░░
  owner        FAILED  75       75       10       43% ████░░░░░░

Run 'tagctl plan' to see suggested fixes.

Detailed results saved to:
  • JSON: output/scan-20260202-105148.json
  • CSV:  output/scan-20260202-105148.csv
  • HTML: output/scan-20260202-105148.html
  • web-prod-api-1 + environment: prodinferred from -prod-
  • etl-stg-worker + environment: staginginferred from -stg-
  • legacy-data-2019 + owner: platform-teamdefault when owner is absent

Rules

Fix what the name already tells you

Infer tags from resource names with patterns you define, and fill the rest with defaults when a condition holds. Every proposed change lands in a plan first.

Normalize

One spelling per value

prod, Production and PROD split one cost report into three buckets. tagctl normalize finds the variants and proposes the spelling your policy allows.

environment "production" allowed by policy, 12 resources
  • "Production"5 resourcescasing
  • "PROD"3 resourcesabbreviation
  • "prod"2 resourcesabbreviation

Enforce

Catch missing tags in the pull request

Cost allocation tags are not retroactive. Spend on an untagged resource can never be billed to a team, however well you tag it afterwards. Check the Terraform plan before apply and fail CI when compliance slips.

  • Terraform plans and state, honouring default_tags
  • SARIF for GitHub code scanning, JUnit for any CI
  • OCSF 1.4 findings for Security Lake or any OCSF-native SIEM

Policy as code

One YAML file, reviewed like any other change

Required tags, allowed values, patterns, inference rules and defaults all live in tagctl.yaml. Keep it in git and run the same policy on your laptop and in CI.

  • tagctl init writes a template
  • tagctl validate checks it before a scan
tagctl.yamlYAML
clouds:
  aws:
    - profile: production
    - profile: staging
      regions: [us-east-1]

policy:
  required:
    - name: environment
      values: [dev, staging, prod]
    - name: cost-center
      pattern: "^[A-Z]{2,4}-\\d{3,6}$"

rules:
  infer:
    - tag: environment
      from_name:
        - pattern: "-prod-"
          value: prod
  defaults:
    - resource: "*"
      when: { tag:owner: absent }
      set: { owner: platform-team@company.com }

Cost

Put a price on missing tags

tagctl cost joins your policy with Cost Explorer and reports the spend nobody can be billed for, worst tag first.

Last 30 days184,320.55 USD
cost-center20% attributed
36,864 USD147,456 USD unattributed
owner90% attributed
165,888 USD18,432 USD unattributed

At this rate, 1,794,053 USD a year of spend nobody can be billed for.

Example output of tagctl cost

Coverage

106 AWS resource types, every enabled region

The same AWS services Prowler audits, read in bulk through the Resource Groups Tagging API. Global discovery is the default; narrow it with --region.

  • AWSAvailable
  • KubernetesComing soon
  • GCPComing soon
  • AzureComing soon
  • EC2
  • S3
  • CloudTrail
  • GuardDuty
  • WAF
  • Bedrock
  • SageMaker
  • CodePipeline
  • WorkSpaces
  • IAM roles

Questions, answered

Something missing? Open an issue on GitHub.

How is this different from Cloud Custodian, AWS Tag Policies or AWS Config?

Custodian is a general policy engine with a lot to learn before it does anything for tags. Tag Policies standardise keys and values in AWS Organizations but do not fix the resources you already have. Config bills per rule evaluation. tagctl does one job: find tag problems, propose fixes, apply them.

Can tagctl change a resource without me seeing it first?

No. apply only executes a plan file and asks for confirmation before it writes. scan and plan never modify anything and run with a read-only IAM policy.

Which AWS credentials does it use?

The standard SDK credential chain: profiles, SSO, environment variables or an instance profile. Pass --role to assume a role, with --external-id and --mfa-serial when you need them.

Which regions does a scan cover?

Every enabled region by default. Limit it with regions in tagctl.yaml or with --region on the command line.

Does it work with Prowler?

The AWS provider covers the same services Prowler audits, and tagctl evaluate checks resources from external JSON against your policy.

What does it cost?

tagctl is free and open source under Apache 2.0. The cost command calls the Cost Explorer API, which AWS bills per request.

Tag your cloud the way your policy says

Install with Go 1.24 or later, or download a prebuilt binary for Linux, macOS or Windows.

go install github.com/unicrons/tagctl/cmd/tagctl@latest