Releases: babyhuey/spf53
Release list
v0.1.5
Fix
deploy's Lambda execution role policy split logs:CreateLogGroup onto a bare log-group ARN, separate from logs:CreateLogStream/logs:PutLogEvents on a :*-suffixed resource, based on AWS's commonly-documented Lambda execution role convention. Deployed to a real AWS account for the first time: CloudTrail showed every scheduled invocation's CreateLogGroup call failing with AccessDenied, because the actual resource ARN this Lambda runtime requests permission against is a third form neither statement covered (.../log-group:/aws/lambda/<fn>:log-stream:, no wildcard). The function still ran successfully either way — CloudWatch Logs failures don't fail a Lambda invocation — but produced zero retrievable logs for every run.
All three log actions are now granted together on a single :*-suffixed resource, since a wildcard covers every suffix variant. Confirmed against a live deployment: after applying the corrected policy, a manual invoke successfully created the log group and wrote real log output, where prior scheduled invocations had silently failed to create it at all.
This is a class of bug that mocked-AWS unit tests can't catch — moto doesn't enforce ARN-suffix-exact resource matching the way real AWS's Lambda runtime does. If you deployed spf53 with a prior version, you may want to check whether your function's CloudWatch log group actually exists and is receiving events.
v0.1.4
Highlights
This release closes out two extended review efforts since v0.1.3:
- Passthrough SPF entry validation is now grammar-complete and permanently fuzz-tested. Config-supplied passthrough mechanisms (ip4/ip6/a/mx/ptr/exists/include) are validated against RFC 7208's full grammar: shape, CIDR-length range and leading-zero rules, character alphabet, and macro-expand syntax. A permanent property-based test suite (hypothesis + pyspf, an independent SPF parser) now fuzzes this validator continuously as a dev-only test dependency, so future grammar gaps get caught automatically instead of requiring manual review. redirect= is no longer accepted in passthrough (it was silently inert whenever the flattened record fit in a single chunk); use include: instead.
- Deploy reliability fixes. The Lambda deployment package no longer inherits restrictive file permissions from the deploy machine's umask (previously could crash every scheduled invocation with Runtime.ImportModuleError when built on a hardened workstation). The execution role's CloudWatch Logs permissions and the Lambda create/update waiters now match AWS's documented contracts. A
--function-name-derived default SSM parameter path prevents two independently-named deployments from silently clobbering each other's config. - Config hardening: duplicate domains, malformed domain names, and a range of malformed SPF mechanism shapes are now rejected at config-load time instead of publishing invalid or semantically-broken SPF records.
Fixes
- Flattening now refuses to publish fail/softfail/neutral-qualified mechanisms as unconditional passes.
- RFC 7208 lookup-cost accounting fixed for passthrough include:/redirect: chains and diamond/cyclic include graphs.
- NXDOMAIN on an a:/mx: target no longer hard-fails the whole domain.
- Route53 multi-ResourceRecord TXT rrsets are now detected and refused instead of silently mishandled.
- Several hostile/malformed-DNS hardening additions: RFC 7208's MX-exchange cap, a total chain-size cap, and bounds on absurdly long or non-ASCII CIDR-length strings from both user config and remote SPF records.
- Deploy: platform-pinned dependency builds, pip-less-environment error handling, wheel-install smoke test in CI, and several AWS provisioning correctness fixes (see above).
Full history is in the commit log between v0.1.3 and this release.
v0.1.3
Fixes
- The transitive lookup-cost counter added in v0.1.2 (for correctly costing passthrough include:/redirect: entries) had no breadth cap, unlike the main flattening walk. A wide (not just deep) tree of unique hostnames in a passthrough include's chain could cause unbounded serial DNS queries with nothing stopping it, hanging any plan/apply run that referenced it. Now enforces the same chain-size cap the main walk already used.
- Config's bare-"all" passthrough guard only checked whole-entry equality, so a multi-token entry like "ip4:1.2.3.4 all" bypassed it and got spliced whole into the built record, silently terminating SPF evaluation early. Passthrough entries containing any whitespace are now rejected outright.
- The NXDOMAIN-softening fix from v0.1.2 (correct for a genuinely decommissioned a:/mx: hostname) had an unintended interaction with this tool's lack of SPF macro support: an unexpandable macro mechanism like a:%{i}.example always NXDOMAINs, so it silently resolved to no addresses instead of failing loudly, meaning an authorized sending path could vanish from the flattened output with no signal at all. a:/mx: mechanisms containing macro syntax now raise instead of silently dropping.
269 tests passing, no known regressions.
v0.1.2
Fixes
Most severe: flattening stripped SPF qualifiers before processing mechanisms, so fail/softfail/neutral (-/~/?) mechanisms were published as unconditional passes. Flattening now refuses instead of silently inverting a mechanism's meaning.
Also fixed:
- lookup_cost() undercounted passthrough include:/redirect: terms by not walking their transitive cost, letting a config pass the RFC 7208 guard while the real published record could exceed 10 lookups.
- a:/mx: targets hard-failed the whole domain on NXDOMAIN instead of treating it like NoAnswer, so one stale hostname in an upstream provider's record could permanently block updates.
- redirect= was followed even when the record already had an all mechanism.
- Multi-ResourceRecord Route53 TXT rrsets (e.g. left over from another flattener) could break atomic DELETE batches or falsely compare as unchanged; this now refuses loudly instead.
- Deploy's bundled dependencies weren't pinned to the Lambda's actual platform/Python version, and gave an opaque error under pip-less environments (e.g. uv tool install spf53).
- The release workflow now installs the actual published wheel and exercises the deploy path before publishing, closing the gap that let v0.1.0's broken deploy command ship undetected.
- Config now rejects duplicate domain entries and bare "all" passthrough mechanisms at load time.
- v=spf1 record detection is boundary-anchored; ip4/ip6 family mismatches are rejected instead of silently accepted; added RFC 7208's 10-MX-exchange cap and a total chain-size cap.
263 tests passing, no known regressions.
v0.1.1
Bug-fix release. 0.1.0's spf53 deploy command was broken for anyone installing the published wheel rather than an editable dev checkout — fixed and verified against a real clean-venv wheel install.
Also fixes: a safety-guard defect that could let a large IPv6 block mask complete loss of a domain's IPv4 SPF authorization, a reentrant thread-pool deadlock on SPF records with many MX exchanges, an RFC 7208 10-lookup budget that was displayed but never enforced (and briefly over-enforced due to an off-by-one), several per-domain error-isolation gaps, a silent SNS notification failure on region mismatch, and multiple smaller correctness and cleanup fixes.