Strands Agents + AWS

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
30,000+ contracts 380 AWS services 0 API keys Open source
Quickstartcopy, paste, run
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.

Without Stigmer
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.

With Stigmer
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

01 / CONTRACT

Know the call

Required parameters, IAM permissions, pagination contract, call-chain links, and known traps for the AWS method.

02 / POLICY

Generate the policy

A paste-ready least-privilege policy grouped by service, with a confidence tier and any unresolved operations.

03 / GATE

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
    ...

Links