Skip to content

Releases: babyhuey/spf53

v0.1.5

Choose a tag to compare

@babyhuey babyhuey released this 23 Jul 00:32

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

Choose a tag to compare

@babyhuey babyhuey released this 22 Jul 06:14

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

Choose a tag to compare

@babyhuey babyhuey released this 21 Jul 21:28

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

Choose a tag to compare

@babyhuey babyhuey released this 21 Jul 20:07

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

Choose a tag to compare

@babyhuey babyhuey released this 21 Jul 14:07

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.

v0.1.0

Choose a tag to compare

@babyhuey babyhuey released this 20 Jul 19:02

Initial release: self-hosted SPF flattener for Route53.

  • plan/apply/deploy CLI + Lambda handler
  • SSM-backed config, SNS alerts, refuse-to-apply safety guards
  • Python 3.11-3.14