README
¶
policy-scout
Explore AWS Organizations service control policy (SCP) attachments from a terminal. Policy Scout shows where an account sits in the organization and names from SCP summaries directly attached to that account or to a root/OU in its ancestor path, without requiring several AWS CLI calls or manual console navigation.
Table of Contents
- Features
- Prerequisites
- Usage
- Automation and agent usage
- Output
- Version and JSON compatibility
- Tooling
- License
- Feedback
Features
- Display one account's path from the organization root, or the complete tree with
--account-id all. - Display names from SCP summaries for direct attachments to each returned member account and its ancestors.
- Identify the management account, whose users and roles are not affected by SCPs.
- Produce structured
json(default) or a human-readabletexttree.
Policy Scout lists SCP summary names; it does not retrieve policy documents or evaluate SCP Allow/Deny semantics, IAM policies, resource policies, permission boundaries, session policies, or effective identity permissions.
Prerequisites
Policy Scout uses the AWS SDK default configuration and credential chain. Configure credentials before running it. Pass --profile <name> to select an AWS shared-config profile explicitly; this selection takes precedence over AWS_PROFILE. When --profile is omitted, the SDK's normal profile selection and default credential chain are unchanged.
For an AWS IAM Identity Center (SSO) profile, authenticate separately before running Policy Scout:
aws sso login --profile=my-profile
policy-scout aws --profile my-profile --account-id 339712974046
If the SDK reports a missing or expired SSO session, both policy-scout aws and policy-scout aws auth status include the selected profile in a copyable aws sso login remediation. Detection is best-effort: configuration, permissions, role assignment, network, and cache-file access failures are reported without a login suggestion. Policy Scout never runs AWS CLI login itself, opens a browser or device flow, prompts for credentials, changes environment variables, or stores credentials.
The selected AWS identity must be able to inspect the organization. Depending on the requested scope, Policy Scout calls:
organizations:ListRootsorganizations:DescribeOrganizationorganizations:DescribeAccountorganizations:DescribeOrganizationalUnitorganizations:ListParentsorganizations:ListPoliciesForTargetorganizations:ListChildrenwhen using--account-id all
Usage
Check which AWS identity the default credential chain resolves and whether it can access AWS Organizations:
policy-scout aws auth status
policy-scout aws auth status --output-format text
The status command calls AWS STS GetCallerIdentity and Organizations
DescribeOrganization. It reports the credential source and expiration when
available, but never displays secret credential values. A successful identity
check with denied Organizations access is reported in the output and returns a
nonzero exit status.
Inspect one account (JSON is the default):
policy-scout aws --account-id 339712974046
Select a named AWS shared-config profile explicitly:
policy-scout aws --profile security-audit --account-id 339712974046
Inspect the entire organization and save structured output:
policy-scout aws --account-id all --output-format json > organization.json
Request a terminal-friendly tree:
policy-scout aws --account-id 339712974046 --output-format text
policy-scout aws --account-id all --output-format text
Bound AWS work for a CI job or coding agent:
policy-scout aws --account-id all --output-format json --timeout 30s --max-retries 3
--timeout <duration>sets one overall deadline for AWS configuration and credential loading plus all Organizations API traversal. Durations use Go syntax such as500ms,30s, or2m. The value must be greater than zero.--max-retries <count>limits each AWS API request to that many retries after its initial attempt.0disables retries; accepted values are0through10. Retryability and backoff remain managed by the AWS SDK.- When either flag is omitted, Policy Scout does not override that setting. In particular, omitting
--max-retriespreserves the AWS SDK default or settings from the AWS environment and shared configuration files.
Run policy-scout aws --help for complete, copyable command examples and input requirements.
Discover the installed binary version:
policy-scout version
policy-scout version --output-format json
Automation and agent usage
Policy Scout is non-interactive and is designed to be safe to invoke from scripts and coding agents such as Amp, Claude Code, and Codex:
- Run
policy-scout aws --helpto discover the supported operation and flags. - Ensure AWS credentials are already available through the default credential chain.
- Use
--output-format jsonexplicitly in automation, even though JSON is the default. - Set
--timeoutand, when needed,--max-retriesso a degraded AWS endpoint cannot leave the caller waiting indefinitely. - Check the exit status before parsing stdout. Exit status
0means stdout contains one JSON document; a nonzero status means the operation failed and stderr contains a diagnostic. - Add
--error-format jsonto receive one machine-readable JSON error on stderr. This flag is independent of--output-formatand may appear before or after the subcommand.
The CLI does not use confirmation prompts, interactive input, a pager, browser/device authentication, or colored output. Successful data is written to stdout and errors are written to stderr, so redirection and JSON processors work predictably. If an SSO login remediation is returned, an operator must run it separately in an interactive terminal; agents should report the command rather than execute it:
if policy-scout aws --account-id all --output-format json > organization.json; then
jq '.. | objects | select(.type? == "account")' organization.json
fi
For example, an agent can capture successful data and structured errors separately:
policy-scout --error-format json aws --account-id all \
> organization.json 2> policy-scout-error.json
JSON errors have this stable shape. operation and request_id are omitted when unavailable. Messages and remediation are curated and never include credentials or raw credential-provider errors.
{
"code": "aws_access_denied",
"message": "AWS denied the Organizations request.",
"operation": "ListRoots",
"retryable": false,
"request_id": "example-request-id",
"remediation": "Grant the selected identity the required AWS Organizations read permissions, then retry."
}
Exit statuses and stable error codes are:
| Exit | Error code | Meaning |
|---|---|---|
0 |
— | Success. |
1 |
unexpected |
An unexpected local or AWS response failure. |
2 |
invalid_invocation |
Invalid command, flag, argument, or input value. |
3 |
aws_credentials |
Missing, invalid, or expired AWS credentials. |
4 |
aws_access_denied |
AWS authorization denied the operation. |
5 |
aws_transient |
Retryable network, throttling, or AWS service failure. |
Human-readable stderr remains the default. Retry transient failures with backoff; correct the invocation or credentials/permissions before retrying other classified failures.
Output
JSON output is a tree rooted at the AWS organization root. Nodes use these fields:
schema_version: the organization JSON compatibility version; present on the root node.type:root,organizational_unit, oraccount.id: the AWS entity ID.name: the entity name, when applicable.management_account:truefor the management account.scps: sorted, de-duplicated names from SCP summaries directly attached to a member account or to a root/OU in its ancestor path. This compatibility field is intentionally name-only and does not represent evaluated effective permissions; usescp_attachmentswhen IDs or attachment locations matter.scp_attachments: direct and inherited SCP attachment provenance for a member account. Each item contains:policy_idandpolicy_name: the stable policy identity and its display name.attached_to: thetypeandidof theroot,organizational_unit, oraccountwhere the policy is attached, plus itsnamewhen that name is already returned while building the tree.inherited:falseonly when the policy is attached directly to the reported account; otherwisetrue.
children: nested organization nodes.
scp_attachments contains one item per unique policy-ID/attachment-target pair. This preserves multiple attachment locations for one policy and distinguishes different policy IDs that share a name. Its ordering is deterministic by policy name, policy ID, and then attachment position from root to account. The legacy scps array remains sorted and de-duplicated by name, so duplicate names must be disambiguated through scp_attachments.
Fields that do not apply or contain no values may be omitted. SCP fields are omitted for the management account because SCPs do not affect its users or roles. Policy Scout lists policy summaries attached to each hierarchy target; it does not retrieve or evaluate policy documents. The successful JSON document is not wrapped in a status envelope.
{
"schema_version": "1",
"type": "root",
"id": "r-cww9",
"children": [
{
"type": "organizational_unit",
"id": "ou-cww9-x2atbcle",
"name": "Finance",
"children": [
{
"type": "account",
"id": "339712974046",
"name": "aws-child1",
"scps": ["DenyAccessS3", "DenyRegions", "FullAWSAccess"],
"scp_attachments": [
{
"policy_id": "p-a1b2c3d4",
"policy_name": "DenyAccessS3",
"attached_to": {
"type": "account",
"id": "339712974046",
"name": "aws-child1"
},
"inherited": false
},
{
"policy_id": "p-e5f6g7h8",
"policy_name": "DenyRegions",
"attached_to": {
"type": "organizational_unit",
"id": "ou-cww9-x2atbcle",
"name": "Finance"
},
"inherited": true
},
{
"policy_id": "p-FullAWSAccess",
"policy_name": "FullAWSAccess",
"attached_to": {
"type": "root",
"id": "r-cww9",
"name": "Root"
},
"inherited": true
}
]
}
]
}
]
}
Text output renders the same hierarchy as a tree:
|-- Root: [r-cww9]
|-- OU: Prod [ou-cww9-36h7ub42]
|-- OU: Finance [ou-cww9-x2atbcle]
|-- Account: aws-child1 [339712974046] (SCP summary names from account/ancestor attachments: DenyAccessS3, DenyRegions, FullAWSAccess)
|-- SCP: DenyAccessS3 [p-a1b2c3d4] (Attached to: account aws-child1 [339712974046]; Inherited: false)
|-- SCP: DenyRegions [p-e5f6g7h8] (Attached to: organizational_unit Finance [ou-cww9-x2atbcle]; Inherited: true)
|-- SCP: FullAWSAccess [p-FullAWSAccess] (Attached to: root Root [r-cww9]; Inherited: true)
Version and JSON compatibility
policy-scout version --output-format json provides machine-readable binary and schema discovery without AWS credentials:
{
"version": "1.2.3",
"organization_schema_version": "1"
}
Release binaries report their release version; binaries built directly with go build report dev. The organization output's root-level schema_version matches organization_schema_version above.
Within one schema version, consumers must tolerate additive object fields and should use the type field rather than assume every node has identical fields. Removing or renaming fields, changing their types or meanings, or restructuring the document is a breaking change and requires a new schema_version. The successful organization document remains an unwrapped root node.
Tooling
- Mise pins the development tools used locally and in CI. Run
mise installafter cloning the repository, then use the Make targets for local workflows. - Cobra CLI
- GolangCI-Lint
- Goreleaser
- go-semantic-release
- GitHub Workflows
- Pre-Commit
- EditorConfig
License
Policy Scout is released under the Apache 2.0 license. See LICENSE.
Feedback
Feel free to open an issue to report a bug or submit a feature request. PRs are also welcome!
Documentation
¶
Overview ¶
Copyright © 2024 Aristides Gonzalez aristides@glezpol.com
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.