Catch AWS Lambda IAM permission errors and event-shape bugs before you deploy — a local pre-flight linter and AWS-faithful runtime. No Docker, no SAM, no invocation charges.
Project description
lambda-preflight
Catch AWS Lambda IAM permission errors and event-shape bugs before you deploy.
Every "local lambda" tool runs your handler. lambda-preflight does something none of them do: it checks your handler's AWS calls against your execution role's real IAM policies and fails locally — so you find the missing s3:PutObject on your laptop, not in a CloudWatch log after a failed deploy.
No Docker. No SAM CLI. No cloud invocations, so no invocation charges.
pip install "lambda-preflight[aws]"
The headline: local IAM linting
Your Lambda runs as an execution role. When that role is missing a permission, you normally find out the slow way — deploy, invoke, read the AccessDenied in the logs, redeploy. lambda-preflight pulls your role's real policies once, caches them, and checks every AWS call your handler makes before anything leaves your machine.
from lambda_preflight import pull_role, PolicyEvaluator, intercept_boto3
from lambda_preflight import LambdaHarness, events
# Pull the execution role's real policies (managed + inline + boundary), cached after first call.
policies = pull_role("my-function-exec-role")
evaluator = PolicyEvaluator(policies, strict=True)
harness = LambdaHarness("app.handler", timeout=30)
# boto3 calls inside the handler are authorized against the real policy — locally.
with intercept_boto3(evaluator):
result = harness.invoke(events.sqs(body={"orderId": 123}))
# If app.handler calls s3:PutObject and the role can't → AccessDenied raised here,
# with the same reason AWS would give, before you ever deploy.
Three ways to load policies, so it works on a plane or in locked-down accounts:
from lambda_preflight import pull_role, load_role_policies, PolicySet
pull_role("my-exec-role") # 1. live pull — needs creds + IAM read, caches result
load_role_policies("my-exec-role") # 2. offline — reuse cache, no AWS call, no charge
PolicySet.from_policy_documents([{"Statement": [...]}]) # 3. BYO JSON — when you can't pull
It models the parts that actually decide real permissions: explicit-deny-wins, implicit deny, wildcard actions, resource matching, permission-boundary intersection, and common condition operators. What it can't faithfully evaluate (uncommon condition keys, SCPs, resource-based policies) it flags — in strict=True mode it raises CannotEvaluate rather than returning a false green. It tells you the edge of its own knowledge instead of guessing.
The foundation: an AWS-faithful local runtime
The linter needs something real to run against, so the runtime half is built to match AWS's actual invocation contract — not a stubbed approximation.
Schema-accurate event fixtures
The #1 cause of "worked locally, broke in AWS" is a test event whose shape is wrong. These builders emit payloads matching AWS's real schemas, so what you test is what you'll get. REST (v1) and HTTP (v2) API Gateway differ meaningfully — they're separate on purpose:
events.api_gateway_v1(method="GET", path="/health")
events.api_gateway_v2(method="POST", path="/orders", body={"id": 1})
events.sqs(records=[{"a": 1}, {"b": 2}]) # real batch shape, messageAttributes, md5
events.sns(message={"x": 1}, subject="hi")
events.s3(bucket="b", key="k.txt", event_name="ObjectCreated:Put")
events.eventbridge(detail={"k": "v"}, detail_type="my.type", source="svc")
events.dynamodb_stream(keys={"id": {"S": "1"}}, event_name="INSERT")
Faithful invocation semantics
result = harness.invoke(event)
result.ok # success vs. error
result.payload # what the handler returned
result.error # {errorType, errorMessage, stackTrace} — AWS's exact envelope
result.cold_start # models warm-container reuse across invokes
The context reproduces real fields (aws_request_id, invoked_function_arn, log names) and a live get_remaining_time_in_millis() countdown. Timeouts are enforced the way AWS enforces them. Lambda's environment variables are injected so handler code that reads them behaves the same.
pytest, because CI is the point
Fixtures register automatically — fast, cloud-free, zero-charge tests:
def test_orders(lambda_harness, lambda_events):
harness = lambda_harness("app.handler", timeout=5)
result = harness.invoke(lambda_events.sqs(body={"order": 42}))
assert result.ok
How this differs from other local-lambda tools
Tools like python-lambda-local and local-aws-lambda are runners — they execute your handler against an event dict you supply. That's the commodity half, and it's solved. lambda-preflight includes a competent runner too, but its reason to exist is what sits on top:
| Typical local-lambda tools | lambda-preflight | |
|---|---|---|
| Run a handler locally | ✅ | ✅ |
| Schema-accurate event fixtures | ✋ you write the dict | ✅ built-in, v1 ≠ v2 |
| Faithful context / timeout / cold-start / AWS-shape errors | partial | ✅ |
| IAM permission linting against your real role | ❌ | ✅ unique |
Honest scope
This kills the shape / contract / runtime-semantics / identity-permission class of "works locally, breaks in AWS" failures — which is most of the day-to-day pain. It does not, and a local tool cannot, reproduce cloud-only realities: cold-start latency, live concurrency, account quotas, SCPs, and resource-based policies (the last two are recorded in PolicySet.unscoped, never silently ignored). The IAM layer is a pre-flight linter, not a parity guarantee. For final confidence, run against real AWS credentials.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lambda_preflight-0.1.0.tar.gz.
File metadata
- Download URL: lambda_preflight-0.1.0.tar.gz
- Upload date:
- Size: 20.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ee2e25674aa7609db3ce2a925d764c38ceeee4ea4d09830022ca1d7611d90a8d
|
|
| MD5 |
9cc40f5344748ab216f1b2808068eed2
|
|
| BLAKE2b-256 |
d4efb50a942a3c7d71b7103df95cd7498fb38eae4fa00c3d958b38e61fbe5747
|
File details
Details for the file lambda_preflight-0.1.0-py3-none-any.whl.
File metadata
- Download URL: lambda_preflight-0.1.0-py3-none-any.whl
- Upload date:
- Size: 25.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e7101d4df5e7b23195d365607e1e9acf848f3aabc2c154020936235542a55606
|
|
| MD5 |
db076bd09450c0827e07c78ab6167889
|
|
| BLAKE2b-256 |
728d48ea363ac0459dc18f604e7b3099e19cb88e8c802d19c38653287420280d
|