A Nextcloud app signs in one environment and delivers from another¶
- Status: accepted
- Date: 2026-11-03
Context¶
Decision 0091 took every secret away from the build of a release, but that of a Nextcloud app: its build entered the whole environment release for the key that signs the app, and the release workflow called it with secrets: inherit. So the actions that the build runs, such as setup-php and the one that writes the SBOMs, could reach the private key of the release app and the token of the App Store as well. The settings declare the environments signing, with APP_PRIVATE_KEY alone, and appstore, with APPSTORE_TOKEN alone, and the maintainer set both secrets in paperless-sync and paperless-unified-search (#530, steps 1 and 2).
Two things stood in the way of a plain switch:
- A called workflow sees no secret that its caller doesn't pass, not even one of the environment that its own job enters. The caller, the job
assetsofrelease.yml, enters no environment, so the value that it passes is empty. GitHub's documentation says that the secret of the called job's environment takes the place of a passed one; no run of the blueprint had shown that yet. - A job enters one environment, and the one job of
register-app.ymlread the key and the token in one step.
Options¶
- Keep
secrets: inheritand only move the build tosigning: the build would get every secret of the repository and of no environment, a finding of zizmor that only an exception at that line silenced. - Pass
APP_PRIVATE_KEYalone, declared by the build as an optional secret, and let the environmentsigningof the build supply its value; register the app in two jobs, one in each environment. - As 2, but keep the key in
appstoreas well, so thatregister-app.ymlstays one job.
Decision¶
Option 2.
- The build enters
signingand getsAPP_PRIVATE_KEYalone,secrets: APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}, whichbuild-release.ymldeclares withrequired: false. The inline exception of zizmor goes withsecrets: inherit. - A wrong guess fails loudly. Should GitHub hand the build no key, the step Sign the Nextcloud app stops the release with an error that names the environment and the passed secret, before anything is signed or attached, and the signature is checked against the certificate of the app as before, so no release is ever signed by nothing or by another key. The first beta release of a Nextcloud app is the proof; until step 4 of #530,
releasekeeps copies of both secrets, so reverting the switch brings the releases back. - The delivery to the App Store enters
appstore. The job runs only forPUBLISH_TOnextcloud-appstore, as those of PyPI and npm run only for theirs. - Register the app in two jobs. The job sign the app id in
signingchecks the key against the certificate that Nextcloud issued and signs the app id; it hands the app id, the certificate in Base64 and the signature, none of them secret, to the job register the app inappstore, which posts them with the token. That job checks nothing out and reaches onlyapps.nextcloud.com, and it fails when an output arrives empty, as GitHub drops an output that it takes for a secret.
Option 3 would bring the key back into the reach of every delivery to the App Store, which runs with every release.
Consequences¶
The build of a Nextcloud app reaches only the key that signs it, the delivery and the second job of the registration only the token of the App Store, and none of them the private key of the release app. The registration takes two jobs and a hand-over of public data, which tests/test_workflow_steps.py runs with a key and a certificate of its own. Once a beta release has built, signed and verified, step 4 of #530 takes both secrets out of release.