# MailFathom third-party license register This file records the license review for the third-party components MailFathom actually pins, bundles, runs, or calls. It is a review register: it names components, states the terms verified for them, and records a compatibility decision. It reproduces no license text and grants no rights of its own, and it is not legal advice. It describes software in use and nothing else. A planned, evaluated, or rejected component has no row here, because a row asserts a completed review of something that exists in the repository today. Four documents describe MailFathom's licensing, and each answers a different question: | Document | Question it answers | Status | |---|---|---| | `LICENSE` | Which rights MailFathom grants for its own code | The unmodified AGPL-3.0 text, SPDX identifier `AGPL-3.0-only`. Never edit it: GitHub detects the license by matching that file, and an edit turns a detected `AGPL-3.0` into `NOASSERTION`. | | `NOTICE` | Who originally created the work, which section 7(b) permits a distribution to require be preserved | Present, and informational only. Third-party notice text belongs beside it in the bundle below, never inside it. | | `THIRD_PARTY_LICENSES.md` — this file | Which third-party components MailFathom uses, under which terms, and whether those terms fit both distribution models | Maintained here. | | A generated third-party notice bundle | The full license texts and attribution notices that must travel with a distributed binary, container, or source artifact | Not produced yet. Issue #191 owns generating it from each artifact's resolved dependency graph; the obligations it must satisfy are the ones recorded in this file. | The register is deliberately not the notice bundle. A notice bundle is generated from the resolved dependency graph of a specific artifact and contains verbatim license texts; this file is the human decision record behind it, and it exists whether or not an artifact has been built. A redistribution therefore carries three separate things: the project grant in `LICENSE`, the original-author attribution in `NOTICE`, and whatever notices the components below require. Never merge third-party notice text into `NOTICE`, because that would attribute third-party work to MailFathom's author. `0.1.0` has been released, so the redistribution obligations recorded here are outstanding rather than anticipated: every component the container image and the Helm chart carry has been distributed, and the notice bundle issue #191 owns is a live obligation against a published artifact rather than a task ahead of the first one. ## License acceptance policy MailFathom must remain compatible with both contemplated distribution models: 1. commercial closed-source distribution; and 2. open-source publication of the MailFathom project. A third-party component may be used only when its license permits both, without forcing MailFathom itself to be distributed under a copyleft license. **MailFathom's own licence moved from Apache-2.0 to AGPL-3.0-only in 2026 and this policy did not move with it.** The two are independent, and the reading that a copyleft project may now consume copyleft dependencies is the one this paragraph exists to refuse. What MailFathom grants for its own code is the owner's to change — `CLA.md` is what keeps it changeable — and what a dependency grants is not, so a single copyleft dependency would end model 1 permanently and no later decision could undo it. Every row below is therefore written against model 1 exactly as it was before the relicensing. [ADR 0025](https://github.com/fathomise/MailFathom/blob/main/docs/decisions/0025-publishing-mailfathom-under-agpl-3-0-only.md) records the move and this consequence of it. Allowed licenses include permissive OSI-approved licenses such as MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, PostgreSQL License, and similarly permissive licenses after review. Licenses that require review before use include LGPL, MPL, EPL, Unicode, custom commercial licenses, source-available licenses, dual licenses, preview license terms, and any license with field-of-use, redistribution, network-service, data-use, patent, trademark, telemetry, or attribution conditions that are not already understood for this project. Prohibited without explicit owner approval are strong copyleft or source-available licenses that could conflict with closed-source commercial distribution or require relicensing MailFathom, including GPL, AGPL, SSPL, BUSL, Commons Clause, PolyForm Noncommercial, and similar restrictions. AGPL is on that list as a dependency licence whatever MailFathom itself is published under. A permissive SDK license never approves the hosted service, model, container image, or trademark behind it. Those terms are reviewed separately and recorded separately. [ADR 0016](https://github.com/fathomise/MailFathom/blob/main/docs/decisions/0016-third-party-licence-obligations-per-artifact.md) states how a component in the review-before-use band is decided, and the three rules it adds are what the rows below are written against. The unit of review is `(component, version, artifact)` rather than the repository, because a redistribution obligation attaches to what is handed to somebody else; the exposure sections below are the coarse form of that, and a component carrying a condition is decided against the artifact rather than against its section. A component that resolves but reaches no artifact is **latent**, which is a decided state rather than a pending review: its row says so, names the event that makes it live, and states what is then owed. And a conditioned component stays **separately replaceable** — unmodified, its own file in the artifact, never merged into MailFathom's own output, trimmed, rewritten, statically or ahead-of-time linked, or sealed in a bundle a recipient cannot open — with the component dropped from an artifact whose build shape cannot allow that, rather than the condition waived for it. ## How the inventory is produced Two questions decide every row here, and they are answered separately. **What earns a row** is a decision this repository took: a component it **pins** in a manifest, **invokes** from a script, a workflow, or a skill, **calls** by name from a published artifact at run time, or **ships** — inside an artifact, or as the runtime an artifact published here is written to run on. Something a contributor installs on their own machine, that this repository neither pins nor invokes nor ships, gets no row: an editor, a language server, a shell, a container desktop, a recommended editor extension. Usefulness earns nothing, and neither does being named in a setup document — prose that helps a contributor configure their machine describes that machine, while a row asserts a completed review of a version this project chose and would notice going stale. A row for software nobody here decided is worse than no row, because it makes the absence of a row unreadable. Where the two readings pull apart — a tool a document recommends but no script runs — the rule decides and the prose stays: it keeps its version and its reasoning, and stops promising a row. **What a row costs** is decided by exposure, which is what the rest of this section is about. A package that ships inside the product carries redistribution and notice obligations; one that only runs on a build agent or a developer machine does not. That distinction is derived from the repository rather than assumed, which is why the sections below are ordered by exposure and why each was produced by a command that can be repeated: ```bash # Direct pins and the transitive closure they resolve to. cat backend/Directory.Packages.props jq -r '.dependencies[] | to_entries[] | select(.value.resolved != null) | "\(.key) \(.value.resolved)"' \ backend/src/*/packages.lock.json backend/tests/*/packages.lock.json | sort -u # License expression, license URL, and upstream repository, from each package's own published metadata. curl -s "https://api.nuget.org/v3-flatcontainer///.nuspec" # What is actually redistributed: the assemblies a framework-dependent publish of the host emits. dotnet publish backend/src/Host/Host.csproj -c Release -o ls /*.dll # Upstream license of a component that is not a NuGet package. gh api repos///license --jq '.license.spdx_id + " " + .html_url' # The client stack's direct pins, and the licenses of the closure they resolve to. cat frontend/package.json frontend/src/*/package.json pnpm --dir frontend licenses list # License expression and upstream repository, from a package's own published metadata. curl -s "https://registry.npmjs.org//" | jq -r '.license, .repository.url' # What a bundle actually carries, which is the production closure and nothing the workspace installs to build or test. pnpm --dir frontend licenses list --prod # The JavaScript census: how many packages under each term, and every one of them that is not MIT. pnpm --dir frontend licenses list --json | jq -r 'to_entries[] | "\(.value | length)\t\(.key)"' | sort -rn pnpm --dir frontend licenses list --json \ | jq -r 'to_entries[] | select(.key != "MIT") | .key as $terms | .value[] | "\($terms)\t\(.name) \(.versions | join(" "))"' | sort # The desktop shell's direct pins, and the license every crate the lock file resolves declares for itself. cat frontend/src-tauri/Cargo.toml cargo metadata --manifest-path frontend/src-tauri/Cargo.toml --format-version 1 --locked \ | jq -r '.packages[] | "\(.license // "UNDECLARED")\t\(.name) \(.version)"' | sort # What the Android head's APK packages, resolved rather than declared: Gradle prints the whole tree with each # conflict-resolved version after an arrow, so the arrow is what the last stage reads back onto the artifact it names. frontend/src-tauri/gen/android/gradlew --project-dir frontend/src-tauri/gen/android --quiet --console=plain \ :app:dependencies --configuration universalDebugRuntimeClasspath \ | sed 's/ (\*)$//; s/ (c)$//; s/ (n)$//' \ | grep -oE '[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+:[0-9][A-Za-z0-9._-]*( -> [0-9][A-Za-z0-9._-]*)?' \ | awk -F' -> ' '{ if (NF == 2) { n = split($1, p, ":"); print p[1] ":" p[2] ":" $2 } else { print $1 } }' | sort -u # The crate census, and which of those crates a Linux desktop build links out of the graph Cargo resolves for every # target it knows about. cargo metadata --manifest-path frontend/src-tauri/Cargo.toml --format-version 1 --locked \ | jq -r '.packages[].license // "UNDECLARED"' | sort | uniq -c | sort -rn cargo tree --manifest-path frontend/src-tauri/Cargo.toml --locked --target x86_64-unknown-linux-gnu \ --edges normal --prefix none --no-dedupe | sed 's/ (\*)$//' | awk 'NF >= 2 { print $1 }' | sort -u ``` Two of the three client enumerations answer for the machine they run on, which is a property to state rather than a defect to correct — the Android one does not, Gradle resolving the same artifacts from the same declarations wherever it runs. `pnpm licenses list` reads what is installed, so it names the platform-specific optional packages this machine selected — one `lightningcss-*`, one `@tauri-apps/cli-*`, one `@rollup/rollup-*` — and not the ones another platform would take instead; the terms are the package's own either way, so a per-platform binary changes which file is installed rather than which licence applies. `cargo metadata` is the opposite and names every target's crates at once, which is why the `cargo tree` line beside it exists: the difference between the two is the difference between what Cargo resolved and what a Linux build links. `AppHost` and `IntegrationTests` carry no lock file, because the Aspire SDK selects part of their graph from the host platform's runtime identifier. Their packages are pinned centrally like every other project's and are recorded below from `backend/Directory.Packages.props` and the project files instead. ## The client's three dependency closures The client brings three graphs this register had never carried a shape for, and each is orders of magnitude larger than anything the service pins: 276 packages behind the pnpm workspace, 502 crates behind the native shell's five direct pins, and 81 Android artifacts behind the five the committed Gradle project declares. Everything below decides how those are recorded, and it is written here once rather than restated in each of the rows below that depend on it. **The granularity is a direct pin per row and a census per closure, and the reason is what a row asserts.** Every other row in this file names a component this repository *chose* — somebody read its terms, decided it was allowed, and wrote down what a distribution then owes. A transitive closure is mostly packages nobody chose: `scheduler` arrives because React depends on it, `foldhash` because something under Tauri does, and `androidx.tracing` because `androidx.appcompat` does. Giving each of those a row of its own would put eight hundred and fifty-nine assertions of a completed review into a file where a row *means* a completed review, and it would bury the fifty decisions that were real — forty npm pins, five crates, and five Android declarations — under the consequences of taking them. Giving the closure no record at all is the opposite failure, because the terms that travel with an artifact are the closure's terms rather than the direct pins'. So each closure is recorded as a census — how many packages resolve under which terms, with every distinct term named and every package carrying a condition named individually — and each direct pin keeps its row. A package under a permissive, unconditional term is counted rather than named, because counting it is the whole of what its terms oblige; a package under anything else is named, reviewed, and decided, because that is what a row does. A census is checkable: the commands above reproduce it, and a number that no longer matches is a graph that moved. **Naming follows the package identifier rather than the project's own capitalisation.** The client rows write `react`, `react-dom`, `vite`, `typescript`, `prettier`, and `vitest` because those are the names a manifest, a lock file, and a registry use, and `scripts/update-dependencies.sh` reads this file by matching them. A row headed *ReactDOM* is a row the survey reports as missing. **What each census currently says**, read from the committed lock files on 2026-10-13, when the Tauri pins moved and the Android project was regenerated from the template they carry: - **The JavaScript closure resolves 276 packages at 290 name-version pairs**, and every one of them declares terms — the census has no unknown bucket. 203 are MIT. The rest are Apache-2.0 (37), ISC (11), BSD-2-Clause (8), BSD-3-Clause (3), `Apache-2.0 OR MIT` (3, the Tauri CLI, its native binding, and `@tauri-apps/api`, all taken under MIT), MPL-2.0 (2), MIT-0 (2), BlueOak-1.0.0 (2), Unlicense (1, `fetch-cookie`), `MIT OR Apache-2.0` (2, `@tauri-apps/plugin-opener` and `@tauri-apps/plugin-notification`, taken under MIT and the same choice spelled the other way round), CC0-1.0 (1), and CC-BY-4.0 (1). The Apache-2.0 bucket grew by sixteen when the client's OpenTelemetry pipeline was pinned, and those sixteen are the first packages under a term other than MIT to reach a bundle — which is why the redistributed row below is the one that decides them. The MIT bucket grew by twelve and the Unlicense bucket appeared when `@microsoft/signalr` was pinned, all thirteen being that package and the closure it resolves for a Node runtime. The MIT bucket grew by one more when `markdown-to-jsx` was pinned, which resolves nothing behind it. It grew by seven when the lint pins moved: `eslint`'s file cache now resolves eight packages it did not — `cacheable`, `@cacheable/memory`, `@cacheable/utils`, `@keyv/bigmap`, `@keyv/serialize`, `hashery`, `hookified`, and `qified` — while `json-buffer` left with the `keyv` they replaced, and all nine are build tooling no bundle carries. The closure shrank by twelve when the unit runner moved a major, and every one of the fourteen names that left was the runner's own or the coverage provider's: `@vitest/expect`, `@vitest/pretty-format`, `@vitest/runner`, `@vitest/snapshot`, `@vitest/utils`, `@standard-schema/spec`, `pathe`, `stackback`, `html-escaper`, and `make-dir` out of the MIT bucket, `siginfo` out of the ISC one, and `istanbul-lib-coverage`, `istanbul-lib-report`, and `istanbul-reports` out of the BSD-3-Clause one — the last three replaced by the two names that arrived, `@vitest/istanbul-lib-coverage` and `@vitest/istanbul-lib-report`, which the census counts as MIT and the test-only row below reads as BSD-3-Clause. It shrank by one more when `jsdom` moved a minor: `symbol-tree` left the MIT bucket, jsdom no longer resolving it, and two name-version pairs went with it without taking a name — `whatwg-url` and the `tr46` behind it each lost the version only jsdom's `data-urls` had held. **No package in the closure is offered under a strong copyleft or source-available licence** — no GPL, LGPL, AGPL, SSPL, BUSL, Commons Clause, or PolyForm term appears anywhere in it — **and the only weak copyleft in it is the MPL-2.0 pair**, which is a build tool. That is the finding, recorded rather than left as silence, because a closure this size returning nothing is exactly the result a reader would otherwise take for a review nobody performed. - **Unlicense** — `fetch-cookie`, reached only through `@microsoft/signalr`'s Node fallback — is named here rather than in a row, for the reason MIT-0 below is: it is a public-domain dedication that obliges nothing at all, so it asks strictly less than the majority term already does, and nothing this repository publishes carries the package regardless. - The four terms that are conditional at all are each decided in the row of the pin that brings them: **MPL-2.0** for `lightningcss` and `lightningcss-linux-x64-gnu`, **CC-BY-4.0** for `caniuse-lite`, and **BlueOak-1.0.0** for `minimatch` in the build-tooling row; **CC0-1.0** for `mdn-data` and **BlueOak-1.0.0** for `lru-cache` in the test-only row. **MIT-0** — `@csstools/color-helpers` and `@csstools/css-syntax-patches-for-csstree`, reached through jsdom's colour parsing — is named here rather than in a row, because it is MIT with the attribution condition removed and obliges strictly less than the majority term already does. - **What a bundle carries is twenty-four packages under two terms**: eight taken under MIT — `react`, `react-dom`, `scheduler`, `@microsoft/signalr`, and `markdown-to-jsx`, which declare MIT, and `@tauri-apps/plugin-opener` and `@tauri-apps/plugin-notification` with the `@tauri-apps/api` behind them, which offer a choice including MIT — and sixteen under Apache-2.0, which is the OpenTelemetry closure the client's own pipeline resolves and has no alternative arm to take. Everything else the workspace installs builds, lints, or tests, and reaches no artifact. - **`pnpm --dir frontend licenses list --prod` is the closure and no longer the bundle, and the difference is exactly seventeen packages.** It returns forty-one, and the seventeen beyond the twenty-four above are `@microsoft/signalr`'s Node runtime — `abort-controller`, `eventsource`, `fetch-cookie`, `node-fetch`, and `ws`, and what those resolve behind them — `tough-cookie` under `fetch-cookie` and `event-target-shim` under `abort-controller` among them. That package reaches for them inside a branch it takes only outside a browser, through a `require` call resolved at run time, so what the built chunk keeps of the seventeen is the module name as a string argument and nothing else. That was read off the built bundle rather than reasoned about — searching `frontend/src/Client.App/dist/assets/index-*.js` for each name finds the `require` line and no module — and it is why the notice beside the bundle names `@microsoft/signalr` alone and says so. Until this pin the two readings agreed, which is why the claim used to be made mechanically; a pin whose package ships a runtime branch of its own is what separated them. - **The Rust closure resolves 502 crates** beside this repository's own `mailfathom-desktop`, which is the one entry `cargo metadata` reports with no licence because it is not published. 311 offer `MIT OR Apache-2.0` in one spelling or another, 111 are MIT alone, and 11 more are `Unlicense OR MIT`, all taken under MIT. The complete tail is in the desktop shell's row below, which is where each of them is decided. - **298 of those 502 are what a Linux desktop build actually links**, over normal dependency edges for `x86_64-unknown-linux-gnu` — the enumeration above prints 299 names, `mailfathom-desktop` itself being one of them and not one of the 502. The rest are another target's — the macOS and Windows families — or a build dependency the binary does not carry. All five MPL-2.0 crates are inside the 298, so the review that row records is a review of something an artifact would really contain rather than of a crate Cargo merely resolved. The opener plugin brought five crates and four of them are inside it: `open`, `is-docker`, and `is-wsl` beside the plugin itself, with `tauri-plugin` outside as a build dependency the binary does not carry. The notification plugin brought ten and seven are inside it: the plugin itself, the `notify-rust` it raises a notification and hears a click on one through, `rand` with `rand_chacha`, `rand_core`, and `ppv-lite86` behind it, and `zerocopy` beside the plugin, with `mac-notification-sys` and `tauri-winrt-notification` outside as the other two platforms' notification services and `zerocopy-derive` outside as an optional feature of `zerocopy` nothing here turns on. - **The Android closure resolves 81 artifacts under one term.** `androidx.webkit`, `androidx.appcompat`, `androidx.activity:activity-ktx`, `androidx.lifecycle:lifecycle-process` and `com.google.android.material` are the five declarations the committed Gradle project makes, and every artifact on the packaged runtime classpath behind them is Apache-2.0 — the census has no second term and no undeclared entry, which is why it is stated in the row that decides them rather than broken out here. It is fixed the way the other two are, by `frontend/src-tauri/gen/android/app/gradle.lockfile`: Gradle reads a bare version as a preference and would otherwise resolve a different closure whenever either repository published a transitive one, so the committed project turns dependency locking on and every build verifies against that file. Rewriting it is `./gradlew :app:dependencies --write-locks`, and it belongs to the change that moved a declaration. **A census is a completed reading and not a derived value, which is what decides who maintains it.** `scripts/update-dependencies.sh` surveys the npm and crate families of direct pins and rewrites them on `--apply`, and it never writes a line of this file — so a moved client pin resolves a new closure whose census nothing recomputes. The run says so when it moves one, and re-running the enumerations above is part of the same change rather than a task after it. The Android declarations are outside that survey altogether, being Gradle's rather than a family it reads, so what moves that closure is a regeneration of the committed project — `frontend/AGENTS.md` § *The Android head* is where that obligation is stated. ## Redistributed with MailFathom A framework-dependent publish of `backend/src/Host` emits 107 assemblies, seven of which are MailFathom's own. The 100 third-party assemblies below are what a binary, container, or source release has to carry notices for. `backend/src/Cli` is published beside it as the `mfctl` administration command and shares that graph apart from three packages of its own, recorded in three rows of their own below. Versions are the ones the host's own graph resolves, which is not always the version pinned in `backend/Directory.Packages.props`: nearest-wins resolution can raise a pin that is only a floor. The last seven rows are the client's, and each is carried differently from every other and from the others: the directory of static files the client stack builds travels inside every published container image, copied into its web root rather than emitted into a publish output, while the desktop application beside it is installed from a `.deb`, an `.rpm`, or an NSIS installer a release attaches. The same static files travel inside that application as well, which is why all but the last two of the seven rows answer for both artifacts. Two of them are not packages at all but files committed into the client's own source tree — a typeface and a set of icon outlines — and they are here rather than under *Externally sourced source material* for the reason that section's rows are not: these are redistributed verbatim inside an artifact, which is what puts a notice obligation on a release rather than on a file in this repository. The final row is the Android head's, and it is the one artifact in this section a release does not carry: [ADR 0027](docs/decisions/0027-an-android-head-built-every-night-and-supported-by-nothing.md) makes the APK one left on the run that built it rather than a published artifact, so nothing about it reaches a release page — it is here rather than under *Build-time and design-time only* because the libraries in it are redistributed to whoever installs that build, and an artifact nobody is promised still travels under the terms its contents carry. | Component and exact version | Use in this repository | License terms verified | Compatibility decision and obligations | Sources reviewed | |---|---|---|---|---| | .NET 10 runtime and ASP.NET Core shared framework. The packages it supplies, which the project locks resolve and the host's own graph then prunes: Microsoft.Extensions.Caching.Abstractions 10.0.13, Microsoft.Extensions.Caching.Memory 10.0.12, Microsoft.Extensions.Configuration 10.0.12, Microsoft.Extensions.Configuration.Abstractions 10.0.12, Microsoft.Extensions.Configuration.Binder 10.0.12, Microsoft.Extensions.Configuration.EnvironmentVariables 10.0.12, Microsoft.Extensions.DependencyInjection 10.0.12, Microsoft.Extensions.DependencyInjection.Abstractions 10.0.13, Microsoft.Extensions.Diagnostics 10.0.12, Microsoft.Extensions.Diagnostics.Abstractions 10.0.12, Microsoft.Extensions.Diagnostics.HealthChecks 10.0.12, Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions 10.0.12, Microsoft.Extensions.FileProviders.Abstractions 10.0.12, Microsoft.Extensions.FileSystemGlobbing 10.0.12, Microsoft.Extensions.Hosting.Abstractions 10.0.12, Microsoft.Extensions.Http 10.0.12, Microsoft.Extensions.Logging 10.0.12, Microsoft.Extensions.Logging.Abstractions 10.0.13, Microsoft.Extensions.Logging.Configuration 10.0.12, Microsoft.Extensions.ObjectPool 10.0.12, Microsoft.Extensions.Options 10.0.12, Microsoft.Extensions.Options.ConfigurationExtensions 10.0.12, Microsoft.Extensions.Options.DataAnnotations 10.0.12, Microsoft.Extensions.Primitives 10.0.13, and System.Threading.RateLimiting 10.0.12 | The published host is framework-dependent, so the runtime is a deployment prerequisite rather than a copied file. A self-contained build or a container image built on the runtime image carries it instead. Seven of the packages listed here are pinned centrally and are still supplied by the framework, so a publish emits an assembly for none of them. Where two projects resolve one of them at two versions, the version named is the higher: `backend/src/Mcp` resolves seven of them at 10.0.10, `backend/src/Infrastructure` resolves four of them at 10.0.12 below the 10.0.13 `Microsoft.Extensions.AI` raises them to in `backend/src/AI`, and `backend/src/AI` resolves `System.Threading.RateLimiting` at 8.0.0 behind `Polly.RateLimiting`. None of those reaches an artifact either. | MIT for the .NET platform repositories and their NuGet package metadata | Allowed. Permissive license supports commercial closed-source use and open-source distribution with notices. A container or self-contained artifact must carry the runtime's own notices. | , , | | Microsoft.EntityFrameworkCore 10.0.12, Microsoft.EntityFrameworkCore.Abstractions 10.0.12, Microsoft.EntityFrameworkCore.Relational 10.0.12, Microsoft.AspNetCore.Authentication.JwtBearer 10.0.12, Microsoft.Bcl.Cryptography 10.0.2, Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore 10.0.12, System.Numerics.Tensors 10.0.13 | Persistence, the JWT bearer authentication middleware, the database health check, and the tensor primitives the AI abstractions use | MIT, declared as a license expression in each package's NuGet metadata | Allowed. Permissive license supports both distribution models with notice preservation. | , , | | Microsoft.Extensions.AI 10.11.0, Microsoft.Extensions.AI.Abstractions 10.11.0, Microsoft.Extensions.AI.Evaluation 10.10.0, Microsoft.Extensions.AmbientMetadata.Application 10.10.0, Microsoft.Extensions.Compliance.Abstractions 10.10.0, Microsoft.Extensions.DependencyInjection.AutoActivation 10.10.0, Microsoft.Extensions.Diagnostics.ExceptionSummarization 10.10.0, Microsoft.Extensions.Http.Diagnostics 10.10.0, Microsoft.Extensions.Http.Resilience 10.10.0, Microsoft.Extensions.Resilience 10.10.0, Microsoft.Extensions.ServiceDiscovery 10.10.0, Microsoft.Extensions.ServiceDiscovery.Abstractions 10.10.0, Microsoft.Extensions.Telemetry 10.10.0, Microsoft.Extensions.Telemetry.Abstractions 10.10.0 | Provider-neutral chat and embedding abstractions, and the service-defaults stack for HTTP resilience, service discovery, and telemetry enrichment. Two packages resolve at two versions, because a central pin is a floor and nearest-wins resolution raises it per project. `Microsoft.Extensions.AI.Abstractions` is 10.11.0 in the host's graph and in `backend/src/AI`, where the provider adapter and the agent framework require it, and 10.8.3 in `backend/src/Mcp`, where `ModelContextProtocol.Core` 2.2.0 asks for no more. `Microsoft.Extensions.AI.Evaluation` is not referenced by any production project: the Agent Framework brings it in at its own floor, 10.10.0, which is the version the host carries, while the evaluation suite's central pin raises it to 10.11.0 there alone. The host's version is the redistributed one; the others are restored during a build of those projects and reach no artifact. | MIT, declared as a license expression in each package's NuGet metadata | Allowed. Permissive license supports both distribution models with notice preservation. Provider-specific AI packages, model terms, and service terms remain separately reviewable and none is adopted. | , , | | Microsoft.Extensions.AI.OpenAI 10.11.0, OpenAI 2.14.0, and transitive System.ClientModel 1.15.0 | The embedding and chat provider adapters in `backend/src/AI/ProviderAdapters`, which together are the whole of MailFathom's contact with a provider. Left at its defaults the OpenAI library describes the process on every request — six `X-Stainless-*` headers and its share of the `User-Agent`, naming its own version, the .NET runtime and its version, the operating system, and the processor architecture. The adapters switch that off through the library's own `OpenAI.DisableTelemetry` switch before the first client is built, so a request to a provider carries none of it; *Hosted services* below states what a request does carry. The `SyntheticMail` development tool's opt-in AI content mode is the one other place in the repository that reaches a provider: it runs on a developer's machine against an endpoint that developer names in a local file, sends only content the tool invented locally under the library's default headers, which describe that developer's machine, and ships in no artifact. One client construction serves both supported providers and both roles, because an Azure OpenAI resource's v1 data plane is OpenAI-compatible, so the SDK is pointed at the resource's own address rather than replaced by a second library. A chat endpoint additionally states which of the SDK's two request clients its calls go through — chat completions or responses — which is a second path within this same library at the same address under the same credential, and is what a reasoning model requiring the responses API is reached by. `Azure.AI.OpenAI` is deliberately not adopted: it would be a second *library*, and it has published no stable release since 2.1.0. | MIT, declared as a license expression in each package's NuGet metadata | Allowed. Permissive license supports both distribution models with notice preservation. The library is what talks to a provider; the provider's own service terms are separate and are recorded under *Hosted services* below. | , , , , , , | | Azure.Identity 1.21.0 and transitive Azure.Core 1.53.0, Microsoft.Identity.Client 4.83.1, Microsoft.Identity.Client.Extensions.Msal 4.83.1, System.Memory.Data 10.0.9, Microsoft.Bcl.AsyncInterfaces 10.0.3, and System.Security.Cryptography.ProtectedData 4.5.0 | The four non-interactive Microsoft Entra credentials an Azure OpenAI endpoint may be reached with where no key exists to provision: managed identity, workload identity, client secret, and client certificate. MailFathom composes that set explicitly and never uses `DefaultAzureCredential`, whose own chain reaches an interactive browser credential and the developer-tool credentials of whoever is signed in on the host. The MSAL token cache brings the DPAPI package into this graph as well as into `mfctl`'s, at the version MSAL pins rather than the one pinned centrally; the call it guards is Windows-only and this service runs on Linux. | MIT, declared as a license expression in each package's NuGet metadata, except `System.Security.Cryptography.ProtectedData` 4.5.0, whose metadata carries a license URL to the MIT text of the repository that built it | Allowed. Permissive license supports both distribution models with notice preservation. The directory these libraries authenticate against is a deployment choice with its own service terms and is not selected here. | , , , | | Microsoft.Agents.AI 1.24.0 and transitive Microsoft.Agents.AI.Abstractions 1.24.0 | Agent and RAG orchestration, including `ChatClientAgent` and `TextSearchProvider` integration. The abstractions package carries a feature-usage token the framework can append to a request's `User-Agent`, and it is written only by the framework's own Foundry and Azure OpenAI client wrappers, which are separate packages and not adopted; no request MailFathom sends carries it. | MIT, declared as a license expression in both packages' NuGet metadata | Allowed. Package terms and any model or service terms reached through an agent remain separately reviewable on every update. | , | | Microsoft.Extensions.VectorData.Abstractions 10.10.0 | Vector-store abstractions the agent framework brings in transitively. MailFathom compiles against neither this package nor a vector-store implementation of its own. | MIT, declared as a license expression in the NuGet package metadata; published from the `dotnet/extensions` repository | Allowed. Permissive license supports both distribution models with notice preservation. | , | | Microsoft.ML.Tokenizers 2.0.0 | Tokenizer primitives the AI abstractions bring in transitively | MIT, declared as a license expression in the NuGet package metadata | Allowed. Permissive license supports both distribution models with notice preservation. Model and vocabulary files are separate artifacts and none is bundled. | , | | Microsoft.IdentityModel.Abstractions 8.19.2, Microsoft.IdentityModel.JsonWebTokens 8.19.2, Microsoft.IdentityModel.Logging 8.19.2, Microsoft.IdentityModel.Protocols 8.19.2, Microsoft.IdentityModel.Protocols.OpenIdConnect 8.19.2, Microsoft.IdentityModel.Tokens 8.19.2, System.IdentityModel.Tokens.Jwt 8.19.2 | Token validation and OpenID Connect metadata handling, brought in transitively by the JWT bearer middleware | MIT, declared as a license expression in each package's NuGet metadata | Allowed. Permissive license supports both distribution models with notice preservation. The identity provider these libraries talk to is a deployment choice with its own service terms and is not selected here. | , | | Microsoft.AspNetCore.OpenApi 10.0.12 and Microsoft.OpenApi 2.12.2 | OpenAPI document generation for the HTTP API, referenced by `backend/src/Host` alone. The generator builds the document from the routes the host already mapped; `Microsoft.OpenApi` supplies the object model the host's own document and operation transformers name, which is why it is pinned directly rather than left to the generator's dependency range. That range is what holds the object model on its 2.x line: the generator declares `[2.12.0, 3.0.0)`, so 2.12.2 is the newest version that restores beside it and a 3.x pin is refused with `NU1608`. Every version in the range is past CVE-2026-49451 (GHSA-v5pm-xwqc-g5wc, high, a stack overflow parsing circular schema references), which `Microsoft.OpenApi` 2.0.0 carried. Both assemblies are redistributed even though the routes they serve are registered only in a `Development` process. | MIT, declared as a license expression in both packages' NuGet metadata | Allowed. Permissive license supports both distribution models with notice preservation. | , , , , | | Scalar.AspNetCore 2.18.0 | The API explorer a developer reads the generated document in, referenced by `backend/src/Host` alone and mapped only in a `Development` process. It declares no package dependency of its own. What it redistributes beyond the managed assembly is its own script bundle and icon, embedded as assembly resources and served by the instance rather than fetched from a content delivery network — so a notice bundle carries Scalar's licence once for the assembly and the assets together. | MIT, declared as a license expression in the NuGet package metadata and confirmed through the GitHub license API for `scalar/scalar` | Allowed. Permissive license supports both distribution models with notice preservation. The hosted service at scalar.com is a separate offering with its own terms and is not adopted: nothing in this repository calls it, and the explorer runs entirely from assets the instance serves. | , , | | ModelContextProtocol.AspNetCore 2.2.0 and transitive ModelContextProtocol 2.2.0 and ModelContextProtocol.Core 2.2.0 | MCP server SDK and Streamable HTTP transport: the tool descriptors and their generated input and output schemas, the call-tool filter the error and outcome mapping is installed as, the endpoint the host maps, and the MCP authentication scheme that publishes the RFC 9728 protected resource metadata document and the challenge pointing at it | Apache-2.0, declared as a license expression in all three packages' NuGet metadata. The upstream `LICENSE` at tag `v2.2.0` records that the project is transitioning from MIT to Apache-2.0: new contributions are Apache-2.0, and contributions whose authors have not consented to relicensing remain MIT. Both are permissive and both are on the allow-list, so the transition needs no decision of its own. | Allowed. Apache-2.0 requires the license and any attribution notices to be preserved on redistribution. No package bundles a `NOTICE` file; the upstream repository carries `THIRD-PARTY-NOTICES.txt`, which must be reproduced if MailFathom ever redistributes the SDK's own sources. Service, model, and client terms of anything reached over the protocol stay separately reviewable. | , , , | | Microsoft.AspNetCore.SignalR.StackExchangeRedis 10.0.12, MessagePack 2.5.303, and transitive StackExchange.Redis 2.7.27, Pipelines.Sockets.Unofficial 2.2.8, MessagePack.Annotations 2.5.303, and Microsoft.NET.StringTools 17.6.3 | The optional signal backplane. Where a `SignalBackplane` section names a RESP endpoint, a signal raised on one replica reaches the client connections another replica holds, which is what makes the client's channel correct above one replica; a deployment declaring no section registers none of this and behaves exactly as one built before it existed. Where a section is written every replica connects, whichever surfaces it serves, because the same endpoint carries the empty announcement that a configuration change committed; the hub's fan-out over it is registered only where the client surface is served. The six assemblies are emitted by every publish regardless, because a publish cannot know which sections an operator will write. `StackExchange.Redis` is the RESP client and the other four are behind it: its socket pipeline, the MessagePack serializer SignalR's Redis protocol writes each invocation with, that serializer's annotations, and the string interner MessagePack brings along. MessagePack is pinned directly rather than taken at the backplane's 2.5.302 floor, because releases below 2.5.303 carry a buffer-limit advisory against nested arrays. | MIT, declared as a license expression in each package's NuGet metadata | Allowed. Permissive license supports both distribution models with notice preservation. The RESP server an operator points the section at is a deployment choice with its own terms and is not selected here; the Valkey image the AppHost can start for a developer and the Helm chart can render for a deployment is recorded under *Local orchestration and container images* below. | , , , , , , , | | MailKit 4.18.1 | IMAP, SMTP, SASL, IDLE, NOTIFY, CONDSTORE, QRESYNC, and TLS modes | MIT, declared as a license expression in the NuGet package metadata | Allowed. Permissive license supports both distribution models with notice preservation. | , | | MimeKit 4.18.1 | MIME parsing for message metadata and body-text extraction: the streaming structural reader, the parsed message tree, TNEF part recognition, decoded-content measurement, and the HTML tokenizer that derives readable text from an HTML body and reads the embedded-resource references an attachment summary depends on, the RFC 8601 `Authentication-Results` parser both the sender-authentication verdict and spam classification read a message's authentication outcomes with, and the signed-message containers signature verification opens: `multipart/signed` and the opaque S/MIME part, whose signed content MimeKit canonicalizes through a cryptography context of its own that MailFathom subclasses only to capture those bytes. Pinned directly rather than taken transitively, because MailFathom compiles against MimeKit's own API and this is also the version MailKit 4.18.1 requires. | MIT, declared as a license expression in the NuGet package metadata | Allowed. Permissive license supports both distribution models with notice preservation. | , , | | BouncyCastle.Cryptography 2.7.0 | The cryptography provider MailKit and MimeKit bring in transitively for S/MIME, PGP, and SASL primitives, and one MailFathom compiles against directly in four places, all inside `backend/src/Infrastructure`. The DKIM key locator names `AsymmetricKeyParameter`, which is what MimeKit's public-key locator contract returns; that key is parsed by MimeKit's own base class. An account's OpenPGP signing key is parsed with `ArmoredInputStream` and `PgpSecretKeyRingBundle` and opened with its passphrase to judge it when the configuration is read, and nothing it parses is kept. Verifying a message's own signature calls the library itself: its CMS parser and signer-information verification for an S/MIME signature, its OCSP request builder and response parser and its CRL parser for revocation, and its OpenPGP packet reader and signature verification for a PGP/MIME signature and the trusted keys configured for one. An organization's signing key is generated by its RFC 8032 `Ed25519` implementation from its `SecureRandom`, into a buffer MailFathom owns, and its two halves leave the library as raw bytes, the private one sealed under the deployment's key ring at once and then erased. Chain building stays with the .NET platform's own `X509Chain`, nothing is signed or encrypted with this library directly, and no type of it leaves the adapter that uses it. Pinned directly for that reason rather than taken transitively, and at the version MailKit and MimeKit 4.18.1 already resolve to. | MIT, declared as a license expression in the NuGet package metadata; the upstream project publishes the same MIT-style terms | Allowed. Permissive license supports both distribution models with notice preservation. | , | | System.Security.Cryptography.Pkcs 10.0.13 | The CMS and PKCS primitives MimeKit brings in transitively at 10.0.0. No type of it is named by MailFathom. Pinned directly at the release that fixes two .NET denial-of-service advisories against 10.0.0 through 10.0.12, and because that version is newer than the one the ASP.NET Core shared framework carries, a framework-dependent publish of the host copies this assembly beside it and the host loads it in place of the framework's own. | MIT, declared as a license expression in the NuGet package metadata | Allowed. Permissive license supports both distribution models with notice preservation. | , | | DnsClient 1.8.0 | The DNS resolver behind local DKIM verification and the DMARC record read beside it, referenced by `backend/src/Infrastructure` alone: one TXT query for `._domainkey.` per signature whose key is not already cached, and the TXT queries of RFC 9989's tree walk under `_dmarc.` for a displayed domain whose record is not already cached. It is adopted because the base class library publishes no TXT lookup at all — `Dns.GetHostEntry` resolves addresses and nothing else — and the alternative is parsing DNS wire format from an unauthenticated network response by hand. The library's types stay inside the two resolver adapters, each the implementation of an application-owned port — the key port trafficking in strings, the DMARC port in domain types — so no DNS type crosses into `Application` or `Domain`. Under the `net8.0` assets .NET 10 resolves it through, it declares no package dependencies of its own, so nothing joins the graph behind it. | Apache-2.0, declared as a license expression in the NuGet package metadata and confirmed through the GitHub license API for `MichaCo/DnsClient.NET` | Allowed. Apache-2.0 requires the license and any attribution notices to be preserved on redistribution; the package bundles no `NOTICE` file. | , , | | HtmlSanitizer 9.2.1039 | The HTML sanitizer that reduces a message body to markup safe to hand to a reader, referenced by `backend/src/Infrastructure` alone. It enforces an allow-list of elements, attributes, CSS properties, CSS at-rules, and URI schemes, and MailFathom configures two policies over it: the model-facing one, which admits no URI scheme and no style at all, and the one behind the self-contained representation a full-HTML surface reads, which keeps the layout while removing everything that runs or reaches another host. Its security history is tracked deliberately — CVE-2026-25543 (GHSA-j92c-7v7g-gj3f, moderate, 2026-02-03), a `