Your Strands agent can call AWS. It can't tell you what it's allowed to do.
strands-stigmer adds the missing contract and permission layer: the exact IAM actions behind an AWS call, a paste-ready least-privilege policy, and a pre-flight check before the call runs.
pip install strands-stigmer
from strands import Agent
from strands_stigmer import stigmer_query, stigmer_policy
from strands_stigmer.hooks import StigmerAuthHook
agent = Agent(
tools=[stigmer_query, stigmer_policy],
hooks=[StigmerAuthHook()],
)
agent("Generate the least-privilege policy for an S3 upload")
No API keys. No account. The hook cancels a call only when AWS's own policy simulator says denied; unknown passes through unless you set fail_closed=True.
What breaks without it
An agent knows the AWS call it wants. It does not know the IAM actions behind it. The failure mode is predictable: AWS returns AccessDenied, the agent asks for a wider role, and the broad policy ships because it makes the error go away.
agent: upload report.pdf to s3://reports/2026/
aws: AccessDenied: kms:GenerateDataKey
agent: retry with s3:* and kms:*
you: grant a role it never needed
The call works. The role is now broader than the workflow, and nobody can say which action was actually required.
stigmer_policy("upload an encrypted report")
→ s3:PutObject
→ kms:GenerateDataKey
hook: denied: kms:GenerateDataKey
→ call cancelled, missing action listed
The exact actions are visible before execution. The broad-role fallback is no longer the easy path.
What you get
Know the call
Required parameters, IAM permissions, pagination contract, call-chain links, and known traps for the AWS method.
Generate the policy
A paste-ready least-privilege policy grouped by service, with a confidence tier and any unresolved operations.
Check before execution
A BeforeToolCallEvent hook asks AWS's policy simulator whether the current identity allows the call.
No blind trust: stigmer_verify feeds a generated policy back to AWS's evaluator and reports whether it grants exactly the intended operations and nothing extra.
The tools
stigmer_query
stigmer_query(query, library="")
Search the contract behind an AWS method. library scopes to one SDK: "boto3" or "aws-sdk-js".
stigmer_policy
stigmer_policy(workflow="", operations="", description="")
Generate a least-privilege IAM policy from a named workflow, explicit operations, or a plain-language description.
stigmer_authorize
stigmer_authorize(operations="", workflow="", principal_arn="")
Resolve the required actions, then ask AWS's SimulatePrincipalPolicy whether the current role allows them. Returns resolution, evaluation, and missing_permissions.
stigmer_verify
stigmer_verify(workflow="", operations="", policy="")
Check a generated policy with AWS's SimulateCustomPolicy. Reports verified, grants_all, and grants_extra.
StigmerAuthHook
StigmerAuthHook(fail_closed=False)
A pre-action hook for use_aws and any tool with a service/operation shape. A denied call is cancelled with the missing permissions listed.
stigmer_use_aws
stigmer_use_aws(service_name, operation_name, parameters={}, ...)
A drop-in use_aws replacement that can scope credentials through sts:AssumeRole plus a session policy. Without those arguments it behaves like use_aws.
Install
pip install strands-stigmer
Then add the tools and the hook to an existing Strands agent. The package is open source, needs no account, and runs locally. Read the source or the PyPI package.
Pre-action hook
What it does
BeforeToolCallEvent
For each use_aws call, the hook resolves the operation to its required IAM actions and asks AWS's own simulator whether the current identity allows them.
denied cancels the call and lists the missing permissions. allowed passes through. unknown passes through by default, or blocks when fail_closed=True.
If the simulator cannot answer, Stigmer does not pretend it did. The default is permissive because an unknown result is not a denial; use fail_closed=True when your workflow requires a hard gate.
Scoped use_aws
stigmer_use_aws
stigmer_use_aws(service_name, operation_name, parameters={}, region="us-west-2", profile_name=None, role_arn=None, session_policy=None)
With role_arn and session_policy, the call runs against an sts:AssumeRole session whose effective permissions are the intersection of the role's policies and the supplied least-privilege policy.
Without them it behaves exactly like use_aws. session_policy without role_arn raises a clear error because the ambient session cannot be narrowed after launch.
Write back
Stigmer grows from agent contributions. If your agent hits a trap that is not in the network, register the fix so the next agent walks around it:
from strands import tool
@tool
def stigmer_register(action: str, symbol: str, error: str, fix: str) -> str:
"""Register a fix with Stigmer. action: 'confirm' | 'append_thread' | 'new_receipt'."""
# Posts to the Stigmer MCP endpoint; see https://stigmer.network/mcp
...