Skip to content

Commit d4d58f2

Browse files
committed
ci: ask Open VSX which key it signs with, every day
The keys are pinned in the app, so a key change at Open VSX is an outage for every installed LevelCode until a release carries the new key. The release gate asks the registry, but only when a release is cut. This asks daily (.github/workflows/openvsx-key.yml runs extension-signature.mjs registry --strict), so that day is known the day it comes and not from a bug report. A failed run is the alarm; docs/EXTENSION-SIGNATURES.md says what to do then. "Could not ask" is tried three times over ten minutes before it fails the run: an outage is not news, but a check that has gone blind is. Its own commit so that it can be reverted alone. It costs a few seconds of a Linux runner a day. GitHub mails a failed scheduled run to whoever last edited the schedule, and stops scheduling in a repository idle for 60 days. The workflow has not run: a schedule only runs from the default branch.
1 parent 5da84f0 commit d4d58f2

4 files changed

Lines changed: 52 additions & 3 deletions

File tree

‎.github/workflows/openvsx-key.yml‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Open VSX signing key
2+
3+
# LevelCode verifies every extension it installs or updates against Open VSX's signing key, and it
4+
# does not ask the registry for that key: the keys it trusts ship inside the app
5+
# (modules/extension-signature/keys.json). That is the point of pinning — and its price. The day
6+
# Open VSX signs with another key, every LevelCode already installed refuses what the new key
7+
# signs, until a release carries it.
8+
#
9+
# This asks the registry once a day, so that day is known the day it comes and not from a user's
10+
# bug report. It runs scripts/extension-signature.mjs registry: which key do the newest extensions
11+
# name, and do some of them really verify with the pinned keys?
12+
#
13+
# A failed run is the alarm. What to do then: docs/EXTENSION-SIGNATURES.md, "When Open VSX changes
14+
# its key". GitHub mails a failed scheduled run to whoever last edited this file's schedule, and
15+
# stops scheduling in a repository with no activity for 60 days.
16+
17+
on:
18+
schedule:
19+
- cron: "17 6 * * *" # daily, off the hour: scheduled runs queue up at :00
20+
workflow_dispatch:
21+
22+
permissions:
23+
contents: read
24+
25+
jobs:
26+
registry:
27+
name: Open VSX signs with a pinned key
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v7
31+
- uses: actions/setup-node@v7
32+
with:
33+
node-version: "24" # as test.yml
34+
- name: Ask the registry
35+
# Exit 1 is evidence — a key that is not pinned, or signatures that no longer verify — and
36+
# fails at once. Exit 2 is "could not ask": an outage is not news, so it is asked again, and
37+
# fails only if the registry cannot be asked three times in ten minutes. That matters too: if
38+
# this check has gone blind, a changed key would pass unseen.
39+
run: |
40+
for attempt in 1 2 3; do
41+
code=0
42+
node scripts/extension-signature.mjs registry --strict || code=$?
43+
if [ "$code" -ne 2 ]; then exit "$code"; fi
44+
echo "Could not ask the registry (attempt $attempt of 3)."
45+
if [ "$attempt" -lt 3 ]; then sleep 300; fi
46+
done
47+
exit 2

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ module for that slot. Full account + runbooks: `docs/EXTENSION-SIGNATURES.md`.
102102
the editor imports it — accepts a real package and refuses a changed one.
103103
- **`smoke <LevelCode.app>`** asks the app itself (its command line installs one tiny extension into
104104
temp folders). It is the check that would have caught the original bug; CI runs it after each build.
105-
- **`registry`** watches for Open VSX changing its key (the release gate runs it). Exit 1 means
105+
- **`registry`** watches for Open VSX changing its key (daily workflow + release gate). Exit 1 means
106106
evidence, and every shipped build is refusing extensions until a release carries the new key.
107107
- **Do not "fix" a refusal by turning verification off** (`extensions.verifySignature`, a patch like
108108
VSCodium's). And a run from source proves nothing here: the editor only enforces on a built app.

‎docs/EXTENSION-SIGNATURES.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ something in the path.
7474
| `release.yml`, after the build | `smoke` on the built app | The job fails; a registry that cannot be reached is a warning |
7575
| `release.yml`, in the test gate | `registry` | The release stops before the hour-long build; unreachable is a warning |
7676
| `make-dmg.sh` step 0, before signing | `install`, then `check` | The app is not signed |
77+
| `openvsx-key.yml`, daily | `registry --strict` | The run fails — that is the alarm |
7778
| Every pull request | The suite, via `test-extensions.sh` | The gate is red |
7879

7980
All four commands are `node scripts/extension-signature.mjs <command>`.
@@ -160,8 +161,8 @@ automatic update of extensions fails without a dialog. Nothing already installed
160161
Users can still click *Install Anyway*, which is exactly the habit this feature exists to end, so
161162
the gap should be short. After `delete`, every install is refused with `NotSigned`.
162163

163-
**How you find out.** The release gate's `registry` step fails, or a user reports `Untrusted`.
164-
To ask by hand:
164+
**How you find out.** In order of likelihood: the daily *Open VSX signing key* workflow fails;
165+
the release gate's `registry` step fails; a user reports `Untrusted`. To ask by hand:
165166

166167
```bash
167168
node scripts/extension-signature.mjs registry

‎modules/extension-signature/test/extensionSignature.test.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,7 @@ function moduleTrusting(keys) {
980980
const smoked = position(release, 'run: node scripts/extension-signature.mjs smoke "VSCode-darwin-${{ matrix.arch }}/LevelCode.app"', 'release.yml');
981981
const zipped = position(release, 'ditto -c -k --sequesterRsrc --keepParent', 'release.yml');
982982
assert.deepStrictEqual([gate, asked, built, smoked, zipped], [gate, asked, built, smoked, zipped].sort((a, b) => a - b));
983+
assert.match(read('.github', 'workflows', 'openvsx-key.yml'), /node scripts\/extension-signature\.mjs registry --strict/);
983984
});
984985

985986
console.log('\nextensionSignature: ' + n + ' tests passed.');

0 commit comments

Comments
 (0)