From db47c7ada76bfe2e19398b45ff07efbc7ca8130d Mon Sep 17 00:00:00 2001 From: Etienne Lescot Date: Sat, 15 Aug 2026 11:47:36 +0200 Subject: [PATCH 1/2] ci(aur): say which key AUR refused, instead of just that it refused one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v1.9.5 got all the way to the push and died on: aur@aur.archlinux.org: Permission denied (publickey) Everything before it worked — tag resolved, .pacman asset found, AUR repo cloned, PKGBUILD audited, version and every checksum recomputed, .SRCINFO regenerated, diff verified, commit created. Only the push failed, and v1.9.1 and v1.9.2 failed the same way. That message covers three different problems and distinguishes none of them: AUR_SSH_PRIVATE_KEY not being a readable key, a key nobody registered on the account, or a key whose account is not a maintainer of this package. After a public key was added upstream we still cannot tell whether the one CI presents is the one that was added. So print it. A public key is public, and `ssh -T` is the decisive probe because AUR answers it by naming the account it authenticated. Runs on the real path only, after the existing "key touches disk last" step, and `continue-on-error` so a diagnostic can never be what fails a release. Not moved into dry_run: that mode deliberately never writes the key to disk, and its summary says so. --- .github/workflows/aur-publish.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/aur-publish.yml b/.github/workflows/aur-publish.yml index 8ee64a792..2a735fdff 100644 --- a/.github/workflows/aur-publish.yml +++ b/.github/workflows/aur-publish.yml @@ -370,6 +370,28 @@ jobs: UserKnownHostsFile ~/.ssh/aur_known_hosts SSHCONF + # "Permission denied (publickey)" is the same message for three different + # problems: a secret that is not a readable key, a key nobody registered + # on AUR, and a key whose account is not a maintainer of this package. + # v1.9.1, v1.9.2 and v1.9.5 all died here and none of them said which. + # A public key is public, so printing it costs nothing and lets the value + # CI presents be compared against what is on the account; `ssh -T` is the + # decisive one, because AUR answers it by naming the user it authenticated. + - name: Identify the key AUR sees + if: steps.aur_secret.outputs.configured == 'true' && !inputs.dry_run + continue-on-error: true + run: | + if ! ssh-keygen -y -f ~/.ssh/aur_key > /tmp/aur_key.pub 2>/tmp/aur_key.err; then + echo "::error::AUR_SSH_PRIVATE_KEY is not a readable private key: $(cat /tmp/aur_key.err)" + exit 0 + fi + echo "Public key this workflow presents:" + cat /tmp/aur_key.pub + ssh-keygen -lf /tmp/aur_key.pub || true + echo "--- what AUR says about it ---" + # Exits non-zero by design (interactive shell disabled); the message is the payload. + ssh -o BatchMode=yes -T aur@aur.archlinux.org 2>&1 || true + - name: Commit and push if: steps.aur_secret.outputs.configured == 'true' && !inputs.dry_run working-directory: aur-repo From 9e0d28adfb4a27fb7acc81f2ce746f9c15274919 Mon Sep 17 00:00:00 2001 From: Etienne Lescot Date: Sat, 15 Aug 2026 12:00:51 +0200 Subject: [PATCH 2/2] ci(aur): stop overclaiming what the probe proves, bound it, pin the key Three review findings, all fair. The comment said `ssh -T` was decisive about maintainer access. It is not: it identifies the authenticated account, and says nothing about whether that account may write ${PACKAGE}. Reworded to what it actually does, which is narrow by elimination -- if AUR answers, the key parses and is registered, so only authorization is left. Switched from `-T` to `help`, which is the documented way to test AUR auth without pushing, and whose reply also enumerates the commands the account may run. That is where to look for a repo-listing command if this has to go further, rather than me asserting one exists: the AUR wiki and RPC are both behind Anubis from here, so I could not verify it. Bounded it. `timeout -k 5 30` plus ConnectTimeout=10, because a diagnostic that hangs is worse than the missing diagnostic it replaced. And `IdentitiesOnly yes` in the ssh config, so both the probe and the push offer this key and nothing else. Without it the probe can report on a different identity than the one the push uses -- which would make the diagnostic actively misleading -- and it is the standard cause of AUR permission-denied where more than one key is reachable. --- .github/workflows/aur-publish.yml | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/.github/workflows/aur-publish.yml b/.github/workflows/aur-publish.yml index 2a735fdff..fce4e0f11 100644 --- a/.github/workflows/aur-publish.yml +++ b/.github/workflows/aur-publish.yml @@ -366,20 +366,33 @@ jobs: HostName aur.archlinux.org User aur IdentityFile ~/.ssh/aur_key + # Offer this key and nothing else. Without it ssh walks whatever + # else it can find first, and AUR can refuse on a key that is not + # the one being diagnosed -- so the probe below would be reporting + # on a different identity than the push. It is also the standard + # cause of "permission denied" against AUR with more than one key. + IdentitiesOnly yes StrictHostKeyChecking yes UserKnownHostsFile ~/.ssh/aur_known_hosts SSHCONF # "Permission denied (publickey)" is the same message for three different # problems: a secret that is not a readable key, a key nobody registered - # on AUR, and a key whose account is not a maintainer of this package. - # v1.9.1, v1.9.2 and v1.9.5 all died here and none of them said which. - # A public key is public, so printing it costs nothing and lets the value - # CI presents be compared against what is on the account; `ssh -T` is the - # decisive one, because AUR answers it by naming the user it authenticated. + # on AUR, and a key registered to an account that does not maintain this + # package. v1.9.1, v1.9.2 and v1.9.5 all died here and none said which. + # + # This narrows it by elimination rather than proving the last one. A + # public key is public, so printing it costs nothing and lets what CI + # presents be compared against what is on the account. `help` is the + # documented way to test AUR auth without pushing: if it answers, the key + # parses AND is registered, so only authorization for ${PACKAGE} is left. + # Its reply also enumerates the commands the account may run, which is + # where to look for a repo-listing one if this needs to go further. - name: Identify the key AUR sees if: steps.aur_secret.outputs.configured == 'true' && !inputs.dry_run continue-on-error: true + env: + PACKAGE: ${{ vars.AUR_PACKAGE_NAME }} run: | if ! ssh-keygen -y -f ~/.ssh/aur_key > /tmp/aur_key.pub 2>/tmp/aur_key.err; then echo "::error::AUR_SSH_PRIVATE_KEY is not a readable private key: $(cat /tmp/aur_key.err)" @@ -388,9 +401,11 @@ jobs: echo "Public key this workflow presents:" cat /tmp/aur_key.pub ssh-keygen -lf /tmp/aur_key.pub || true - echo "--- what AUR says about it ---" - # Exits non-zero by design (interactive shell disabled); the message is the payload. - ssh -o BatchMode=yes -T aur@aur.archlinux.org 2>&1 || true + echo "--- what AUR says about it (auth only; NOT write access to ${PACKAGE}) ---" + # Bounded: a diagnostic must never be the thing that hangs a release. + # Exits non-zero by design; the message is the payload. + timeout -k 5 30 ssh -o BatchMode=yes -o ConnectTimeout=10 \ + aur@aur.archlinux.org help 2>&1 || true - name: Commit and push if: steps.aur_secret.outputs.configured == 'true' && !inputs.dry_run