Repository navigation
Releases: solarssk/mail-autodiscover
Release list
v0.4.1
🚀 mail-autodiscover v0.4.1
At a glance
✨ What's new
- Supply-chain security hardened end to end: CI's own dev-tool installs (ruff, mypy, bandit, pytest, and friends) are now hash-pinned the same way runtime dependencies already were, published container images and each platform's SBOM carry a cryptographically verifiable Sigstore signature, and a Docker layer-caching bug that had silently stopped applying Debian security patches for weeks is fixed.
- OpenSSF Scorecard now runs against this repository and is shown as a README badge — an independent, public check of the controls this repo already claims.
- Full
solarssk/playbookTier 2 compliance re-verified end to end, including workflow linting (actionlint + zizmor), Dependabot cooldowns, and the standardtype:label taxonomy. - Docker Hub's tag list no longer accumulates a new
sha-<short>tag on every merge tomain— onlylatestand version tags are published there now.
🎯 What this means
- No change to the deployed service's behavior, configuration, or API — every change in this release is CI/CD, build-pipeline, and documentation.
- Starting with this release, verify what you're running:
gh attestation verify oci://ghcr.io/solarssk/mail-autodiscover@<digest> --repo solarssk/mail-autodiscover. - If you'd pinned a Docker Hub
sha-<short>tag from a previous build, note those are no longer published there — GHCR still carries them, or pin a version tag/digest instead.
🛠️ Action required
- No action required.
🔧 Technical details
✨ Added
.github/workflows/lint-workflows.yml(replacesactionlint.yml): lints workflows with both actionlint (correctness) and zizmor (safety: template injection, excessive permissions, unpinned actions, missing Dependabot cooldown), which playbook v0.2.0 makes a Tier 2 requirement together. Runs only when.github/workflows/**ordependabot.ymlchange..github/workflows/scorecard.yml: OpenSSF Scorecard, report-only, on push tomain, branch-protection changes, and weekly — an independent check of controls this repo already claims. Never gates a merge..github/workflows/docs-impact.yml: the documentation-impact check as its own workflow (see Changed), withtests/test_check_pr_docs_impact.pycovering the script that had none.- The playbook's standard
type:labels (type: bug,type: feature,type: docs,type: chore). The four existing labels that map one-to-one were renamed in the repository settings, keeping every existing association; topic labels (ci/cd,security,testing,dependencies,outlook,thunderbird,mail-server) stay alongside them. Issue templates, Dependabot PRs, and CONTRIBUTING.md follow the new names. - OpenSSF Scorecard badge in README's top badge row, added once
scorecard.ymlpublished its first real (non-error) result. - CI's own dev-tool installs are hash-pinned, the same way
requirements.txtalready is:requirements-test.txt,requirements-lint.txt,requirements-typecheck.txt,requirements-security.txt, one per extra each CI job installs from, verified againstpyproject.tomlin the same job that already verifiesrequirements.txt.
🔄 Changed
- Pinned
solarssk/playbook'sverify-tierto v0.2.0 (was v0.1.1, overdue — the "pinned to the latest release" check was itself only added in v0.2.0, so the old pin could not warn that it was stale). - The documentation-impact check now runs on
editedtoo. The declaration lives in the PR description, and a plainpull_requesttrigger only fires on opened / synchronize / reopened, so correcting the checkbox left the old failing result in place until another commit was pushed. It is its own workflow rather than aneditedtrigger onci.yml, which would re-run every other job on every description edit. It also now exempts a PR only when its author isdependabot[bot], no longer by branch name (anyone can open a PR from a branch calleddependabot/...). - Every Dependabot entry has
cooldown: default-days: 7(a version published minutes ago is the most likely to be a compromised release; security updates ignore it) and addstype: chorenext todependencies.github-actionsupdates are grouped into one PR, andpydantic-coreis ignored as a standalone bump: it is a transitive pin that Dependabot raised without checking pydantic's own constraint, which breaks--require-hashesinstalls. docker-publish.ymlhas a workflow-levelconcurrency:group keyed on ref and commit, alongside its per-job groups. It only serializes truly identical runs, so it cannot evict a release commit's build the way a per-ref group could.- Docker Hub no longer receives the
sha-<short>tag on every merge tomain— onlylatestis copied there now. Nothing in this workflow ever reads that tag back from Docker Hub (thepromotejob's own polling only ever checks it on GHCR), so it was pure accumulating clutter on the one registry a human is actually likely to browse. GHCR keeps both tags exactly as before.
🐛 Fixed
release.ymlcould not release a version that had no tag yet — i.e. every new release. Its "tag already exists at a different commit?" check readgh api's output, andgh apiwrites the error body to stdout even on failure, so a nonexistent tag's 422 JSON ended up in the "existing SHA" variable and was mistaken for a conflict. This blocked the real 0.4.0 release, which had to be completed by hand. The check now reads the response itself and only accepts the exact "no such ref" shape (HTTP 422, "No commit found for SHA:"); any other outcome — a transient 5xx, a rate limit, an empty body — fails the job instead of guessing, sincegh release createonly honors--targetfor a tag that does not already exist.- Releases created by
release.ymlhad no explicit title, so GitHub displayed the merge commit's message ("Merge pull request #58 from ...") instead of the version. They now use the tag (v0.4.0), like every earlier release. requirements.txtregenerated to resolve apydantic-coreconflict that broke--require-hashesinstalls.
🔒 Security
- Hardened every workflow against template injection and excessive permissions, findings zizmor reported that neither actionlint nor CodeQL had caught (26 in total):
${{ }}expanded straight intorun:scripts now goes throughenv:(the high-severity one was the gitleaks scan-range step), anddocker-publish.ymlno longer grantscontents/packages/security-events: writeto all three of its jobs — deny-all at the top, each job gets only the scopes it uses. zizmor now runs in CI, so this stays fixed. codeql.yml's andrelease.yml'swritepermissions moved from workflow level to their (single) job, closing OpenSSF Scorecard's Token-Permissions finding: a workflow-level grant applies to every job the file will ever have, not just the one it has today.- CI's dev-tool installs (ruff, mypy, bandit, pytest, pip-audit, deptry, httpx) now install from the new hash-pinned lockfiles instead of a bare
pip install ".[extra]", andcodeql.yml's Python analysis now installs from the hash-pinnedrequirements.txtinstead of a barepip install .— closing OpenSSF Scorecard's Pinned-Dependencies gap for CI/build-time tooling (previously only runtime dependencies and third-party Actions were pinned) and making CodeQL analyze the exact versions actually shipped, not whatever currently satisfiespyproject.toml's>=bounds. - The Docker image's
apt-get upgradelayer was silently served fromdocker-publish.yml's build cache on every build after the first one off a given base-image digest (confirmed in the build logs: always reportedCACHED), so it stopped applying Debian security patches — surfaced as GitHub code-scanning alerts for OpenSSL CVEs that a realapt-get upgradewould have fixed. Moved the layer to run afterCOPY/pip install(so busting it doesn't also force those, correctly content-keyed, layers to redo) and added aCACHEBUSTbuild arg set torun_id-run_attempt(not the commit SHA, which would stay identical across aworkflow_dispatchor re-run on an unchanged commit and let the cache serve the same stale layer again), forcing it to actually execute on every build. Verified: rebuilding with a newCACHEBUSTvalue did upgradeopenssl(3.0.20-1~deb12u2→3.0.22-1~deb12u1), confirming the previously cached layer really was stale. docker-publish.yml'spromotejob now signs the promoted container image with a keyless Sigstore attestation (actions/attest, OIDC via Fulcio/Rekor — no static key to manage), published as an OCI referrer on GHCR and uploaded to the GitHub Release asimage.sigstore.json, closing OpenSSF Scorecard's Signed-Releases finding. Verifiable withgh attestation verify oci://ghcr.io/solarssk/mail-autodiscover@<digest>.- Each platform's CycloneDX SBOM gets its own signed SBOM attestation too (same keyless Sigstore mechanism, a distinct predicate type from the image provenance above), published to GHCR and uploaded to the GitHub Release as
sbom-amd64.sigstore.json/sbom-arm64.sigstore.json. scorecard.ymlnow feedsossf/scorecard-actiona token with read access to classic branch-protection settings, so its Branch-Protection check can actually run instead of erroring — the defaultGITHUB_TOKENcan't read them at all (Administrationisn't a grantableGITHUB_TOKENpermission). Prefers a GitHub App installation token (AUTOMATION_APP_ID/AUTOMATION_APP_PRIVATE_KEY— a dedicated app for this repo only, not shared with any other project, scoped toAdministration: Read-only+Contents: Read-only) over a standingSCORECARD_TOKENfine-grained PAT, over the action's ownGITHUB_TOKENdefault — each tier is optional and falls through to the next until one is configured. Each minted installation token expires in about an hour, but the App's own private key does not: it is a long-lived credential like any ot...
v0.4.0
🚀 mail-autodiscover v0.4.0
At a glance
✨ What's new
- Images are now also published to Docker Hub (
docker.io/solarssk/mail-autodiscover), alongside GHCR, always byte-identical. - Releases are now fully automated — merging the version-bump PR is the only manual step.
- The full solarssk Tier 2 engineering standard is now enforced and documented: mechanical playbook compliance checks, a CycloneDX SBOM per release, and a rewritten
SECURITY.mdthat names concrete risks and their mitigations instead of a flat feature list. - CI is significantly faster and catches more: SonarCloud no longer blocks required checks, the test suite now also runs on the exact Python version the container ships, and the built Docker image is smoke-tested before it ever reaches a registry.
- Fixed a rate-limiter correctness bug: under bursty traffic, an already-throttled client could occasionally slip through a request early.
🎯 What this means
- Pull from Docker Hub instead of GHCR if you prefer — see
docker-compose.dockerhub.yml. Both registries always carry the exact same, already-scanned image and digest. - A new version tag is promoted directly from the exact image
mainalready built and scanned for that commit — never rebuilt — so a release is guaranteed to be byte-identical to what was already tested and published. - Rate limiting under heavy, bursty traffic is now strictly correct; normal traffic is unaffected.
🛠️ Action required
- No action required. If you want to switch to Docker Hub, see the updated
docker-compose.dockerhub.yml— otherwise nothing changes.
🔧 Technical details
✨ Added
-
release.ymlnow runs on every push tomainand automates the whole release: if the push changedpyproject.toml's version andCHANGELOG.mdhas a matching## [X.Y.Z] - YYYY-MM-DDheading, it creates thevX.Y.Ztag and GitHub Release, dispatchesdocker-publish.yml(GITHUB_TOKEN-created tags don't self-trigger other workflows'push: tags:listeners), and closes the matchingvX.Y.Zmilestone. Merging the release PR is now the only manual step — matches the automationssf-transmitteralready uses, adapted to this repo's own CHANGELOG format andformat_release_notes.py. -
requirements.txt: hash-pinned lockfile for runtime dependencies, generated withpip-compile. The Docker image now installs from this instead of resolvingpyproject.toml's>=bounds fresh on every build, so the exact same dependency versions and artifacts ship every time. CI fails if it drifts frompyproject.toml. -
CODE_OF_CONDUCT.md. -
.coderabbit.yaml: automatic CodeRabbit review is off; trigger one on demand by commenting@coderabbitai reviewon a PR. Matches the same setting already used inssf-transmitterandwp-critical-css. -
.github/CODEOWNERS. -
"Repository standard" pointer in
AGENTS.mddeclaring this repo as Tier 2 of the solarssk engineering standard (solarssk/playbook). -
README table of contents, badge row, and License section.
-
CycloneDX SBOM generated on version tags and attached to the GitHub Release.
-
"Documentation impact" declaration on pull requests, checked in CI against the actual diff.
-
SECURITY.mdnow has a "Security controls / CI" table and a stated response-time SLA. -
Regression tests for the documented "no mailbox enumeration" invariant: two different mailboxes in an allowed domain must get the same response shape and status code across Outlook, Thunderbird, and Apple Mail.
-
.github/workflows/verify-standard.yml: callssolarssk/playbook's reusableverify-tierworkflow on every push and PR, mechanically checking this repo against its own declared Tier 2 checklist (SHA-pinning,SECURITY.md, issue templates,concurrency:blocks, and more). Same setup already used byssf-transmitter. -
Published images are now also mirrored to Docker Hub (
docker.io/solarssk/mail-autodiscover) alongside GHCR.docker-publish.ymlcopies the already-built, already-scanned GHCR manifest into Docker Hub by digest — never a second build — so both registries always carry byte-identical images with identical Trivy results.latestis now enabled only formainpushes (previously also a clean version tag): main pushes are already serialized against each other, but a tag push runs in its own concurrency group fully parallel to any in-flight main push, so both being eligible to writelatestin two registries each could interleave and leave GHCR and Docker Hub'slatestpointing at different digests. The release process always tags a commit already pushed tomain, somain's own run has already publishedlatestfor that content by the time the tag exists.sha-<short>is restricted tomainfor the same reason: a version tag shares its commit's short SHA with an already-published main run, and theapt-get upgradein the Dockerfile means two independent builds of that same commit aren't guaranteed to produce the same digest, so the same cross-registry interleaving risk applied there too. Addeddocker-compose.dockerhub.ymlas a Docker-Hub equivalent of the existingdocker-compose.ghcr.yml, defaulting tolatestrather than a pinned version for now, since no version tag has been published to Docker Hub yet. Also dropped the{{major}}.{{minor}}floating tag (e.g.1.2) entirely: two different patch releases in the same minor series are two different immutable tags with their own concurrency groups, so nothing serializes them against each other, and both would write the same "1.2" name from genuinely different, both-correct digests — the identical cross-registry interleaving risk as above, but with no single already-serialized run to defer to this time. Serializing all version-tag pushes into one group to fix it would reintroduce the silent-eviction failure mode already rejected at the top of this file (a third tag arriving mid-build would drop a queued release's image and SBOM entirely). The precise per-version tag is immune to this by construction, since no two releases ever share one. -
New "Compatibility tests (Python 3.14)" CI job: the Dockerfile ships Python 3.14, but the test suite only ran on the 3.12 floor used elsewhere in CI until now.
-
ci.yml's Docker build job now smoke-tests the built image (/health,/ready, and the Thunderbird config endpoint) before handing off to Trivy, catching a container that builds and scans clean but never actually starts. -
timeout-minutesset on every CI job, so a hung external dependency (PyPI, SonarCloud, Docker Hub) fails fast instead of blocking up to GitHub's 360-minute default. -
.github/workflows/actionlint.yml, path-filtered to.github/workflows/**: catches GitHub-Actions-workflow-specific correctness problems (malformed expressions, misspelledwith:/needs:names) that CodeQL'sactionslanguage analysis has no queries for.
🔄 Changed
CLAUDE.mdis now a short@AGENTS.mdimport plus Claude-Code-specific notes, instead of duplicatingAGENTS.mdin full.docker-publish.ymlnow scans the actual image about to be pushed with Trivy (gated on fixable HIGH/CRITICAL) before pushing it, instead of only scanning a separate PR-time build that never reaches the registry.ci.yml's secret scan is now scoped to each run's own commit range instead of the gitleaks GitHub Action's full-history default.- All workflows now declare a
concurrency:group (cancel-on-supersede for CI checks, never-cancel for release/publish workflows). - README badges now sit after the description paragraph instead of before it.
app/main.py:create_app()'s Thunderbird/Apple Mail handlers and the Outlook POST body are now plain module-level functions instead of nested closures, and a shared_not_found_response()helper replaces four duplicated 404 responses. Reduces SonarCloud-flagged Cognitive Complexity and unusedasyncfindings with no behavior change (verified by the full test suite, including the mailbox-enumeration invariant).app/config.py:_shared_validation_errors()is split into four focused validation methods, and the duplicated"mail.example.com"/"example.com"literals are now single constants. Same validation behavior and error messages, lower Cognitive Complexity.app/security.py:SecurityMiddleware.dispatch()'s access-log branching is extracted into_log_access_event(), and the log-sanitizer regex's character class no longer duplicates\t\n\v\f\rbetween an explicit range and\s(same matched character set, verified against the full BMP). Lower Cognitive Complexity, same logging output.README.mdrewritten to match the house style used acrosssolarsskrepos (ssf-transmitteras the closest peer): centered badge row (adds Codecov and SonarCloud Quality Gate badges) and platform badge ahead of the description, a collapsed table of contents, a Mermaid "How it works" diagram, and an emoji-tagged Documentation table. Badges now sit before the description again, reversing the earlier "badges after description" change to match the actually-practiced sibling-repo convention rather than the more literal reading of the playbook doc. All content verified against the current repo (endpoints, env vars, compose files, doc links).SECURITY.md's flat "Built-in mitigations" list is now "Main risks and mitigations": four named risks (XXE/XML bombs, oversized-request-body memory exhaustion, log injection, client-IP spoofing), each paired with the specific function and file that mitigates it, plus an explicit "Domain membership is observable — mailbox existence is not" section correcting an earlier draft that implied domain probing was also defended against (it isn't; only mailbox enumeration within an already-allowed domain is). Also corrects the XXE description (defusedxmlblocks entity definitions and external references by default, not DTDs themselves) and drops an inaccurate "slow-drip" mitigation claim from the body-size section, pointing instead at reverse-...
v0.3.3
🚀 mail-autodiscover v0.3.3
At a glance
✨ What's new
- AI coding-agent context files added; documentation corrections.
🎯 What this means
CLAUDE.mdandAGENTS.mdgive AI coding assistants (Claude Code, OpenAI Codex, and others) a concise map of the project: architecture, commands, security invariants, and release process. This avoids agents having to re-derive project conventions from scratch on each session.- CHANGELOG version links now point to the correct diff range for each release.
CONTRIBUTING.mdcorrectly references thedocs/directory instead of the GitHub Wiki, which was superseded in0.3.0.pyproject.tomlproject description now includes Apple Mail, which has been supported since0.2.0.
🛠️ Action required
- No action required.
🔧 Technical details
✨ Added
CLAUDE.md— project context file for Claude Code.AGENTS.md— project context file for OpenAI Codex and other AI agents (mirrorsCLAUDE.md).
🔄 Changed
- Fixed all CHANGELOG version comparison links — each link now points to
compare/vPREV...vVERSIONfor its own release. - Updated
CONTRIBUTING.md: replaced the stale reference to the GitHub Wiki with thedocs/directory. - Updated
pyproject.tomldescription to include Apple Mail profile support.
🐳 Docker tags
ghcr.io/solarssk/mail-autodiscover:0.3.3
docker.io/solarssk/mail-autodiscover:0.3.3
ghcr.io/solarssk/mail-autodiscover:latest
docker.io/solarssk/mail-autodiscover:latest
Portainer / production: pin the semver tag, not latest.
📚 Useful links
v0.3.2
🚀 mail-autodiscover v0.3.2
At a glance
✨ What's new
- Security hardening for the public autodiscover endpoints and their access logs.
- Maintenance updates for the Docker base image and GitHub secret-scanning action.
🎯 What this means
- Plain text access logs are harder to spoof. Request-controlled values such as
X-Request-IDand URL paths can no longer inject extra log lines or fakekey=valuefields likestatus=200. - HTTPS deployments now send stricter browser security headers on normal responses and rate-limited
429responses. - The GHCR Docker image now builds on the Python 3.14 slim Bookworm base image.
- CI secret scanning now uses
gitleaks/gitleaks-actionv3, which moves the action runtime to Node 24.
🛠️ Action required
- No configuration changes are required for existing deployments.
- If you run multiple uvicorn/Gunicorn workers, keep an external rate limiter in front of the service. The built-in limiter is intentionally in-process and each worker has its own counter.
🔧 Technical details
🔒 Security
- Sanitized
X-Request-IDbefore storing it on request state, returning it to clients, or writing it to logs. Only token-safe characters are kept. - Sanitized logged URL paths so control characters, whitespace, and
=cannot create forged fields in text-mode access logs. - Added
Strict-Transport-Security: max-age=63072000whenPUBLIC_BASE_URLuses HTTPS. The header intentionally does not includeincludeSubDomainsby default, because this app cannot know whether sibling subdomains are HTTPS-ready. - Added
Content-Security-PolicyandPermissions-Policyheaders. - Applied security headers to rate-limited
429responses as well as normal responses. - Replaced
xml.sax.saxutils.escapewithhtml.escape(..., quote=False)for XML response text escaping, removing the Bandit B406 warning without changing output behavior.
🔄 Changed
- Updated
Dockerfilefrompython:3.12-slim-bookwormtopython:3.14-slim-bookworm. - Updated CI secret scanning from
gitleaks/gitleaks-actionv2 to v3. - Documented the built-in rate limiter's multi-worker limitation in
SECURITY.md.
📌 Tests
- Added regression coverage for request ID sanitization, URL path log sanitization, exact CSP/Permissions-Policy values, HSTS behavior, and security headers on rate-limited responses.
🐳 Docker tags
ghcr.io/solarssk/mail-autodiscover:0.3.2
docker.io/solarssk/mail-autodiscover:0.3.2
ghcr.io/solarssk/mail-autodiscover:latest
docker.io/solarssk/mail-autodiscover:latest
Portainer / production: pin the semver tag, not latest.
📚 Useful links
v0.3.1
🚀 mail-autodiscover v0.3.1
At a glance
✨ What's new
- Security hardening for reverse-proxy client IP handling and stricter production validation of
TRUSTED_PROXY_IPS.
🛠️ Action required
- No action required.
🔧 Technical details
🐛 Fixed
- Prefer
X-Real-IPoverX-Forwarded-Forwhen the direct peer is a trusted proxy, preventing spoofed leftmost XFF entries from bypassing rate limits or polluting logs. - Parse
X-Forwarded-Forfrom right to left, skipping trusted proxy hops, whenX-Real-IPis missing or invalid.
🔄 Changed
- In production, every non-empty
TRUSTED_PROXY_IPSentry must be a valid IP or CIDR; invalid tokens now fail startup instead of being silently ignored. - Nginx reverse-proxy docs now recommend
X-Forwarded-For $remote_addrinstead of$proxy_add_x_forwarded_forfor single-hop deployments.
🐳 Docker tags
ghcr.io/solarssk/mail-autodiscover:0.3.1
docker.io/solarssk/mail-autodiscover:0.3.1
ghcr.io/solarssk/mail-autodiscover:latest
docker.io/solarssk/mail-autodiscover:latest
Portainer / production: pin the semver tag, not latest.
📚 Useful links
v0.3.0
🚀 mail-autodiscover v0.3.0
At a glance
✨ What's new
- Production-complete release before
1.0: multi-domain YAML config, deployment docs,/ready, and optional JSON access logs.
🎯 What this means
- You can run one container for multiple mail domains via mounted
config/config.yaml, or keep the existing single-profile ENV mode. - Reverse-proxy, DNS, client, and troubleshooting guides now live in
docs/.
🛠️ Action required
- If you upgrade from
0.2.x, review the updated README and mount./config:/config:rowhen using YAML mode. - Set
TRUSTED_PROXY_IPSwhenTRUST_PROXY_HEADERS=truein production (unchanged from0.2.2).
🔧 Technical details
✨ Added
- Optional multi-domain YAML config via
CONFIG_FILEwith per-domain IMAP/SMTP/POP3 settings. GET /readyreadiness endpoint for container and reverse-proxy health checks.- Optional structured JSON access logs via
STRUCTURED_JSON_LOGS=true. - In-repo deployment guides under
docs/plus sample multi-domain config inconfig/config.example.yaml.
🔄 Changed
- Apple Mail
.mobileconfigdownloads now use clearer per-domain filenames and mailbox-specific profile labels. - Docker Compose examples now mount
/configand expose the new config/logging settings.
🐛 Fixed
- Reject YAML config files that define an empty
domainsmap instead of silently falling back to ENV mode.
🐳 Docker tags
ghcr.io/solarssk/mail-autodiscover:0.3.0
docker.io/solarssk/mail-autodiscover:0.3.0
ghcr.io/solarssk/mail-autodiscover:latest
docker.io/solarssk/mail-autodiscover:latest
Portainer / production: pin the semver tag, not latest.
📚 Useful links
v0.2.3
🚀 mail-autodiscover v0.2.3
At a glance
✨ What's new
- This is a small follow-up release that closes a rate-limit configuration gap introduced in
0.2.2.
🎯 What this means
- Production deployments now reject
RATE_LIMIT_MAX_CLIENTS <= 0instead of accepting a value that would silently weaken throttling behavior.
🛠️ Action required
- No action required if you already use a positive
RATE_LIMIT_MAX_CLIENTSvalue. - If you explicitly set
RATE_LIMIT_MAX_CLIENTS=0or a negative value, replace it with a positive number before upgrading.
🔧 Technical details
🐛 Fixed
- Reject non-positive
RATE_LIMIT_MAX_CLIENTSvalues when rate limiting is enabled, preventing ineffective throttling caused by immediate client-eviction behavior.
🐳 Docker tags
ghcr.io/solarssk/mail-autodiscover:0.2.3
docker.io/solarssk/mail-autodiscover:0.2.3
ghcr.io/solarssk/mail-autodiscover:latest
docker.io/solarssk/mail-autodiscover:latest
Portainer / production: pin the semver tag, not latest.
📚 Useful links
v0.2.2
🚀 mail-autodiscover v0.2.2
At a glance
✨ What's new
- Production deployments now fail fast on unsafe placeholder configuration instead of starting with
example.comdefaults. - Reverse-proxy trust is safer by default: forwarded headers require explicit trusted proxy CIDRs.
🎯 What this means
- Misconfigured production stacks surface a clear startup error instead of silently serving placeholder mail settings.
- Rate limiting uses a bounded in-memory store with periodic cleanup, reducing memory growth under abuse.
🛠️ Action required
- If you run behind a reverse proxy with
TRUST_PROXY_HEADERS=true, setTRUSTED_PROXY_IPSto your proxy or Docker bridge CIDRs before upgrading to0.2.2in production. - Review reverse-proxy access logs: Thunderbird and Apple Mail endpoints pass
emailaddressin the query string.
🔧 Technical details
🔒 Security
- Default
TRUST_PROXY_HEADERSchanged tofalse; emptyTRUSTED_PROXY_IPSno longer trusts all peers. X-Forwarded-ForandX-Real-IPvalues are validated as real IP addresses before use.- Production startup validation for HTTPS
PUBLIC_BASE_URL, real domains, and proxy trust settings.
✨ Added
RATE_LIMIT_MAX_CLIENTSandRATE_LIMIT_CLEANUP_INTERVAL_SECONDSfor bounded rate-limit storage.- Stable UUIDv5 identifiers in Apple
.mobileconfigprofiles (re-download updates the same profile). - Healthcheck in
docker-compose.ghcr.yml.
🔄 Changed
TRUST_PROXY_HEADERSdefault is nowfalsein application config,.env.example, and GHCR compose.- Trivy blocks CRITICAL vulnerabilities on pull requests;
mainkeeps advisory SARIF upload.
🐛 Fixed
docker-compose.ghcr.ymlport mapping now respectsCONTAINER_PORT.- Apple
.mobileconfigPayloadIdentifiervalues are account-specific so multiple mailboxes on one domain do not collide. docker-compose.ghcr.ymldefaultIMAGE_TAGmatches the release version (0.2.2).
🐳 Docker tags
ghcr.io/solarssk/mail-autodiscover:0.2.2
docker.io/solarssk/mail-autodiscover:0.2.2
ghcr.io/solarssk/mail-autodiscover:latest
docker.io/solarssk/mail-autodiscover:latest
Portainer / production: pin the semver tag, not latest.
📚 Useful links
v0.2.1
🚀 mail-autodiscover v0.2.1
At a glance
✨ What's new
- The project now lives at
mail-autodiscover, with GHCR images published only underghcr.io/solarssk/mail-autodiscover. - Documentation moved to the GitHub Wiki, and the public Python API is now documented with module docstrings.
🎯 What this means
- New installs have one canonical repository and container image name.
- Admins follow wiki links from the README instead of the removed in-repo
wiki/folder.
🛠️ Action required
- Pull
ghcr.io/solarssk/mail-autodiscoverinstead of the retiredghcr.io/solarssk/autodiscoverpackage path. - Pin
IMAGE_TAG=0.2.1(or newer) in production rather than the oldautodiscoverimage tags.
🔧 Technical details
🔄 Changed
- GitHub repository renamed to
mail-autodiscover; GHCR images publish asghcr.io/solarssk/mail-autodiscover - Documentation now links to the GitHub Wiki instead of the in-repo
wiki/folder - Replace flaky GitHub CodeQL default setup with explicit
.github/workflows/codeql.yml - Add docstrings across the
app/package for public API and security helpers
🐳 Docker tags
ghcr.io/solarssk/mail-autodiscover:0.2.1
docker.io/solarssk/mail-autodiscover:0.2.1
ghcr.io/solarssk/mail-autodiscover:latest
docker.io/solarssk/mail-autodiscover:latest
Portainer / production: pin the semver tag, not latest.
📚 Useful links
v0.2.0
🚀 mail-autodiscover v0.2.0
At a glance
✨ What's new
- This release makes reverse-proxy deployments safer, adds a simple landing page, and introduces Apple Mail profile downloads.
🎯 What this means
- Admins get clearer logging, better control over forwarded headers, and a cleaner user-facing entry point.
- End users on iPhone, iPad, and Mac can install a mail profile instead of entering all settings by hand.
🛠️ Action required
- If you run the service behind a reverse proxy, review and set
TRUSTED_PROXY_IPSfor your own proxy or Docker network ranges. - If you deploy with Portainer or GHCR images, prefer the pinned
0.2.0tag in production.
🔧 Technical details
✨ Added
TRUSTED_PROXY_IPS/FORWARDED_ALLOW_IPSso forwarded client IP headers are trusted only from your configured proxy CIDRsACCESS_LOG_SKIP_PATHSto reduce noise from health checks and static asset requests- Apple Mail
.mobileconfigprofiles (GET /mail/ios.mobileconfig,/.well-known/apple-mail.mobileconfig) - HTML landing page,
robots.txt,favicon.ico,apple-touch-icon.png docker-compose.ghcr.ymlexample for Portainer / GHCR deployments
🔄 Changed
- Unified access logs now show
client_ip, method, endpoint, and status in a single line GET /now returns a small landing page instead of JSON metadata/healthis no longer rate limited
🔒 Security
- Forwarded headers are honored only when the direct peer matches
TRUSTED_PROXY_IPSwhen that setting is present
🐳 Docker tags
ghcr.io/solarssk/mail-autodiscover:0.2.0
docker.io/solarssk/mail-autodiscover:0.2.0
ghcr.io/solarssk/mail-autodiscover:latest
docker.io/solarssk/mail-autodiscover:latest
Portainer / production: pin the semver tag, not latest.