Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions .github/workflows/mail-backend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
name: Mail backend

on:
push:
branches: ["release/mail-*"]
paths:
- "plugins/omamail/src/**"
- "plugins/omamail/Cargo.*"
- "plugins/omamail/scripts/**"
- "plugins/omamail/tests/**"
- "tools/mail-backend-release.py"
- ".github/workflows/mail-backend.yml"

permissions:
contents: read

concurrency:
group: mail-backend-publication
cancel-in-progress: false

jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
runner: ubuntu-24.04
- arch: aarch64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 40
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87
with:
toolchain: "1.97.1"
- name: Prepare native musl tools
env:
ARCH: ${{ matrix.arch }}
run: |
set -euo pipefail
test "${GITHUB_REF_NAME}" = "release/mail-$(python3 tools/mail-backend-release.py version)"
sudo apt-get update
sudo apt-get install -y musl-tools nodejs
rustup target add "$ARCH-unknown-linux-musl"
- name: Test and build locked source
working-directory: plugins/omamail
env:
CARGO_BUILD_TARGET: ${{ matrix.arch }}-unknown-linux-musl
RUSTFLAGS: -C linker=musl-gcc
run: |
set -euo pipefail
cargo test --locked --features integration-test-credentials
cargo build --release --locked --bin omamail
OMAMAIL_TEST_BIN="$PWD/target/$CARGO_BUILD_TARGET/release/omamail" python3 tests/test_agent_native_bridge.py
- name: Verify exact native binary and create evidence
env:
ARCH: ${{ matrix.arch }}
run: |
python3 tools/mail-backend-release.py prepare \
"plugins/omamail/target/$ARCH-unknown-linux-musl/release/omamail" "$ARCH" artifact
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mail-${{ matrix.arch }}
path: artifact/*
if-no-files-found: error
retention-days: 1

publish:
needs: build
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: mail-*
path: artifacts
- name: Assemble matching native builds
run: python3 tools/mail-backend-release.py assemble artifacts release-assets
- name: Publish a new backend-only prerelease
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
version="$(python3 tools/mail-backend-release.py version)"
tag="mail-backend-$version"
test "$(gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/$GITHUB_REF_NAME" --jq .object.sha)" = "$GITHUB_SHA"
# Successful listing is required; authentication/network errors never mean absent.
gh api --paginate "repos/$GITHUB_REPOSITORY/releases?per_page=100" --jq '.[].tag_name' > existing-releases
gh api --paginate "repos/$GITHUB_REPOSITORY/git/matching-refs/tags/$tag" --jq '.[].ref' > existing-tags
if grep -Fxq "$tag" existing-releases || grep -Fxq "refs/tags/$tag" existing-tags; then
echo 'Version already exists; never overwrite release assets.' >&2
exit 1
fi
cat > release-notes.md <<'NOTES'
Backend-only nbshell maintenance build. This is not a shell update.

- Builds the bundled Mail source with Rustls 0.23.45 (RUSTSEC-2026-0285 fix).
- Native static Linux x86_64 and aarch64 binaries; existing source API 5.
- Source fingerprints, exact compiler/architecture evidence and archive checksums included.
- The nbshell plugin pin is updated separately, only after public-asset verification.

This build is maintained by nbshell, not an upstream Omamail release.
NOTES
gh release create "$tag" release-assets/* --target "$GITHUB_SHA" \
--title "Mail backend $version (nbshell rebuild)" --notes-file release-notes.md \
--prerelease --latest=false
mkdir published
gh release download "$tag" --dir published
diff -r release-assets published

verify-published:
needs: publish
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
runner: ubuntu-24.04
- arch: aarch64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download and exercise the public binary natively
env:
GH_TOKEN: ${{ github.token }}
ARCH: ${{ matrix.arch }}
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y nodejs
mkdir published
gh release download "mail-backend-$(python3 tools/mail-backend-release.py version)" --dir published
python3 tools/mail-backend-release.py verify published "$ARCH"
7 changes: 6 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- name: Verify tag and release metadata
run: |
Expand Down Expand Up @@ -102,11 +104,14 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
prerelease=()
[[ "$(cat VERSION)" == *-* ]] && prerelease+=(--prerelease)
version="$(cat VERSION)"
# Backend-only tags must not become the baseline for shell release notes.
previous="$(git describe --tags --abbrev=0 --match 'v[0-9]*' "${GITHUB_SHA}^")"
gh release create "${GITHUB_REF_NAME}" nbshell-manual.zip \
"nbshell-${version}.tar.gz" "nbshell-${version}.tar.gz.sha256" \
"nbshell-${version}.tar.gz.sigstore.json" \
--title "nbshell $(cat VERSION)" \
--generate-notes "${prerelease[@]}"
--generate-notes --notes-start-tag "$previous" "${prerelease[@]}"
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ configuration and plugin interfaces before `1.0.0`.

### Fixed

- Mail now pins the verified nbshell backend rebuild with Rustls 0.23.45,
closing RUSTSEC-2026-0285 in the delivered executable, not only its source.
Both Linux architectures are tested after publication; archive hashes are
anchored in the shell release and failed installs preserve the prior runtime.
- Project status no longer executes configured Git content filters or inherits
Git environment overrides. Output is capped and timeout/cancellation cleans
up the complete command process group.
Expand Down
41 changes: 32 additions & 9 deletions docs/audits/code-review-2026-09-17.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,22 @@ The Rustls advisory describes accepting handshake messages at an incorrect
encryption level, not an established network-attacker authentication bypass.
Nevertheless, the currently pinned upstream Mail 0.10.4 binary cannot be claimed
fixed by changing our source lockfile. Upstream 0.10.5 still locks Rustls 0.23.44.
Beta publication remains blocked pending a verified corrected backend delivery
path; no pin, tag or release is claimed to have been published here.

Security verdict: **BLOCK for beta publication on the unresolved Mail binary
boundary**; the Git and installer fixes have local regression evidence. No
claim of blanket shell or third-party security certification is made.
This initial blocker is resolved by the separately published
[nbshell backend rebuild 0.10.4-nbshell.1](https://github.com/nerdislb/nbshell/releases/tag/mail-backend-0.10.4-nbshell.1).
Both static Linux architectures passed native Rust/agent/API checks, followed
by public-download API and real-installer verification. The shell now pins
those verified archive hashes and API 5, already implemented by the bundled
source. The actual downloaded x86_64 binary also passes the production
Quickshell process test and 22 synthetic native-agent checks locally. The
legacy-adoption case uses the supported historical runtime/bin layout; the
installed-plugin legacy-job guard is retained, not bypassed.

Security verdict: **PASS for the corrected Mail binary delivery boundary**
after the public-asset checks above; the Git and installer fixes have local
regression evidence. No claim of blanket shell or third-party security
certification is made. The independent-review and hardware/real-account limits
below still apply. The [maintenance plan](../mail-backend-maintenance.md)
records the remaining dependency follow-up and release/rollback rules.

Independent-provider review was attempted within existing subscriptions:
Claude Fable returned quota exhaustion; Claude Sonnet timed out; Gemini
Expand All @@ -88,8 +98,21 @@ over literal source snippets where behavior can be exercised. Keep helper
resource bounds close to their implementation; avoid a large generic framework
or global UI rewrite without a demonstrated need.

The initial complete gate failed on the stale contracts described above. Focused
reruns cover their corrections; final clean-candidate verification and published
artifact validation remain separate release steps. No new two-hour soak,
The initial complete gate failed on the stale contracts described above.
After correction, the complete local gate and GitHub Validate passed on
1750e54; the backend-delivery follow-up repeats the affected tests and complete
CI before merging. Final shell-tag signature and archive checks remain a
separate step from the completed backend publication checks. No new two-hour soak,
physical suspend/display matrix, second-machine login/onboarding, real-account
Mail/Gaming acceptance or complete AT-SPI certification is claimed.


### Follow-up CI timer regression

The final PR gate exposed a flaky recovery-notice fixture: a fixed 4.2-second
wait assumed a 4-second QML toast timer had already fired. On one busy runner,
the old toast was still present when the recovery warning cleared. The fixture
now waits for the actual `draftSavedToast` transition with a bounded deadline,
then asserts that the recovery warning survived that transition. No UI timer
or production behavior changed. Five focused repetitions and all 76 tests in
that component pass; the full remote gate is repeated on the corrected commit.
45 changes: 45 additions & 0 deletions docs/mail-backend-maintenance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Mail backend maintenance

## What nbshell owns

nbshell maintains a **backend-only rebuild** of its bundled Omamail source. This is not an upstream Omamail release and does not include upstream's standalone desktop apps. Preserve upstream authorship and licenses. Do not describe the whole vendored plugin as a one-line fork: nbshell already carries integration and presentation changes, and its bundled source implements API 5 while the previous downloaded backend provided API 4.

The initial rebuild is `0.10.4-nbshell.1`. Its additional dependency fix updates Rustls from 0.23.44 to 0.23.45 for [RUSTSEC-2026-0285](https://rustsec.org/advisories/RUSTSEC-2026-0285.html). No new mail feature is added for this rebuild. The complete source commit, source-file fingerprints, compiler version, architecture and binary hashes travel with each release. A version label alone is not provenance.

The maintainer is the nbshell repository owner. An agent can inspect, prepare and test changes; successful tests are evidence, not permission to silently widen the fork, enable paid services or change accounts. Public replies to upstream maintainers require explicit authorization.

## Small, regular maintenance loop

| When | Work | Outcome |
|---|---|---|
| Weekly | Inspect upstream releases, RustSec/OSV advisories for the locked Cargo graph, and changes to the release toolchain/actions. | A dated assessment: no action, candidate update, or security work. |
| Before each nbshell beta | Refresh advisory checks; verify the exact backend pin and both delivered architectures; run compatibility and installation/rollback gates. | Explicit PASS, BLOCK or NOT VERIFIED for the affected security boundary. |
| On a relevant security advisory | Check affected versions, enabled features and actual call paths promptly; prepare the smallest effective fix. | Patch/release priority based on reachability and impact, not merely scanner severity. |
| On a regression | Preserve the last working release and user data; diagnose with synthetic fixtures. | A new immutable corrective version, never silently replaced assets. |
| Monthly or at an upstream replacement candidate | Review whether our rebuild is still necessary. | Keep a justified patch or retire the extra delivery path. |

This is a maintenance policy, not a claim that monitoring is already scheduled. No autonomous dependency merging or publishing is enabled by this document. If a scheduled check is later enabled, it should report findings and never install or publish by itself.

Budget: use the existing public repository and standard GitHub-hosted Linux runners only. No larger runners, paid fallback routes, extra storage purchases or automatic top-ups. Build artifacts have one-day retention and no Rust build cache is uploaded. Stop at existing limits. See [GitHub Actions billing](https://docs.github.com/en/billing/concepts/product-billing/github-actions).

## Release sequence

1. Start a clean `release/mail-X.Y.Z-nbshell.N` branch. Increment `N` for every new rebuild; never reuse a published version. Keep the Cargo package and lockfile versions identical. The UI manifest and standalone base metadata are independent; only the backend is built here.
2. Review the diff from the previous release. Record why each extra change is needed. Keep the dependency update narrow; do not update the entire Cargo graph merely to clear an unrelated warning.
3. Push the reviewed build change. `.github/workflows/mail-backend.yml` builds locked static musl executables on native x86_64 and ARM64 standard runners. It runs Rust tests, native agent tests and the actual backend API contract, checks ELF architecture/static linkage and private build paths, and records source fingerprints. Both architectures must agree on source/API inputs.
4. CI publishes a uniquely named `mail-backend-X.Y.Z-nbshell.N` prerelease in `nerdislb/nbshell`, not a shell release. It refuses an existing tag/release, compares the downloaded public bytes to the build outputs, and executes the public API contract natively on both architectures. Failure does not update the plugin pin.
5. Verify from a clean checkout/export of the release commit: generated Python bytecode in `src/` is not a release input and a development tree can have a different fingerprint even when Git reports clean. Only after those jobs pass, update `backend-version`, fold the existing API step into `releasedApiVersion`, and write `backend-release.json` with both **downloaded and verified** archive hashes. The consumer uses a fixed nbshell release origin; metadata cannot supply another URL. The source-anchored archive hash must agree with the remote sidecar before any candidate executable runs. There is no upstream/PATH/latest fallback for an nbshell rebuild.
6. Test the real installer in an isolated profile, including corrupt delivery and preservation of the previous executable. Run the production Quickshell bridge against the downloaded binary. Existing legacy AI jobs must finish or be cancelled by their owner before an upgrade; retain `check-upgrade.py`. Source-build legacy adoption is not automatically evidence for binaries built elsewhere.
7. Run the full shell release gate, merge the reviewed branch, install the candidate and verify the live runtime without changing configuration or account data. Publish the nbshell beta using the normal signed shell-archive workflow. Its trusted plugin hash pins are part of that archive.

Backend binaries remain separate assets: the shell source archive is already close to the updater's 50 MiB limit. Never append executables to it without reviewing the real consumer limits.

## Recovery and retirement

- Failed download, checksum, version or archive validation must preserve the installed executable. Never remove mail accounts, drafts, caches or keyring entries as a recovery shortcut.
- Existing tags and assets remain available for older plugin revisions. A bad published candidate gets a new version; do not overwrite or delete it to disguise the failure. Do not move the shell pin to it.
- A rollback to an older backend is allowed only after checking API/data compatibility and security. The vulnerable pre-fix binary is not our recommended security rollback. Prefer a new corrective build when a rollback would reintroduce a known issue.
- Return to upstream only when its **actual published binaries**, not just its source lockfile, contain the required fix and satisfy the current plugin contract. Verify both architectures, the update transition, and the existing QML feature guards. Keep old nbshell artifacts for reproducibility after the return.
- Open findings remain explicit: the current Hickory record-encoding advisory has no established application-level reproducer in the reviewed resolver use; it is a maintenance item, not a clean raw scan. Periodic review must revisit that assessment if dependencies, features or callers change.

See the [review report](audits/code-review-2026-09-17.md) and [shell release process](releasing.md).
10 changes: 9 additions & 1 deletion docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ change before version 1.0.

## Prepare a release

For the separately delivered Mail executable, follow the [backend maintenance and release policy](mail-backend-maintenance.md). A source dependency fix is not a fix to the user's downloaded binary.

1. Update `VERSION` and move the relevant entries from `Unreleased` in
`CHANGELOG.md` to a dated version section.
2. Run the complete local gate:
Expand All @@ -14,7 +16,7 @@ change before version 1.0.
bash tests/release-gate.sh
mkdocs build --strict
git diff --check
git diff --check "$(git describe --tags --abbrev=0)"..HEAD
git diff --check "$(git describe --tags --abbrev=0 --match 'v[0-9]*')"..HEAD
```

Before a release, also run a current Python advisory scan in an isolated
Expand Down Expand Up @@ -59,6 +61,12 @@ the release workflow identity at the exact tag, then verifies the checksum. It
refuses installation when any asset or verification step is missing. Do not tag
a commit until its live desktop test has passed.

Shell release notes start at the previous ancestor tag matching `v[0-9]*`.
Backend-only `mail-backend-*` releases are not shell releases and must never
become that baseline. The release job checks out the complete history for this
selection; a missing previous shell tag fails closed rather than silently
generating an incomplete change list.

## After publishing

- Verify the release archive, checksum, dashboard update check, and installation
Expand Down
Loading