Skip to content

Commit c44f468

Browse files
committed
Merge branch 'develop' into feat/web-edition
Two conflicts, both the same kind: develop (#103) and this branch each added to the same lines. - scripts/test-extensions.sh: develop gates modules/*/test/*.test.js, this branch web/test/unit/*.test.js. The gate now discovers all three trees, and its header and its "no suites matched" error name all three. - CLAUDE.md: the scripts/ line lists extension-signature.mjs and build-web.mjs; both new sections are kept, "Extensions are verified" and then "LevelCode in a tab"; the commit line names modules/ and web/, and dist-web/ as never committed. One break git could not see: extensionSignature.test.js pinned the gate's loop line exactly, so with the web glob added it failed and stopped the gate. It now checks what it is there for — modules/*/test/*.test.js is one of the loop's patterns — and still refuses a loop without it. release.yml's comment on the gate names the web suites too. The gate passes on the merged tree on macOS with Node 24: 79 test files (63 extension, 1 module, 15 web), 1,500 cases. Not run on Linux here; the pull request's CI is that run.
2 parents 86864f3 + 2568fc3 commit c44f468

21 files changed

Lines changed: 2436 additions & 16 deletions

‎.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

‎.github/workflows/release.yml‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,16 @@ jobs:
4141
node-version: "24"
4242
- name: Extension unit tests
4343
# The gate itself lives in a script, so the pull-request workflow (test.yml) runs exactly what
44-
# this does. It discovers every extensions/*/test/*.test.js and fails if it finds none.
44+
# this does. It discovers every extensions/*/test/*.test.js, modules/*/test/*.test.js and
45+
# web/test/unit/*.test.js, and fails if it finds none.
4546
run: ./scripts/test-extensions.sh
47+
- name: Open VSX still signs with a key this release trusts
48+
# The app verifies extensions against Open VSX's signing key, and the key is pinned in the app
49+
# (modules/extension-signature/keys.json). If the registry has moved to another key, the app
50+
# about to be built could not install or update one extension: stop here, an hour early.
51+
# Fails only on evidence — a registry that cannot be reached is a warning. What to do when it
52+
# fails: docs/EXTENSION-SIGNATURES.md.
53+
run: node scripts/extension-signature.mjs registry
4654

4755
build:
4856
name: Build ${{ matrix.arch }}
@@ -85,6 +93,12 @@ jobs:
8593
VSCODE_TAG: ${{ github.event.inputs.vscode_tag }}
8694
- name: Build LevelCode.app (${{ matrix.arch }}, proprietary stripped)
8795
run: ./scripts/build-macos.sh ${{ matrix.arch }}
96+
- name: The built app installs an extension from Open VSX
97+
# The one check that asks the app itself. Its own command line installs one four-kilobyte
98+
# extension into throwaway folders, and its log must show the signature verified. Every
99+
# LevelCode release before this step existed failed it: none could verify an extension.
100+
# Fails only on the app's own words; a registry that cannot be reached is a warning.
101+
run: node scripts/extension-signature.mjs smoke "VSCode-darwin-${{ matrix.arch }}/LevelCode.app"
88102
- name: Zip the unsigned app
89103
run: ditto -c -k --sequesterRsrc --keepParent "VSCode-darwin-${{ matrix.arch }}/LevelCode.app" "UNSIGNED-LevelCode-${{ matrix.arch }}.app.zip"
90104
- name: Upload app artifact

‎CLAUDE.md‎

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ lives in the tracked repo and is re-applied onto a clean clone:
1818
| `extensions/levelcode-npp-pack/`, `extensions/levelcode-ai/` | `vscode/extensions/*` | `apply-branding.mjs` (copy) |
1919
| `branding/product.overlay.json`, `branding/icons/` | `vscode/product.json`, `vscode/resources/...` | `apply-branding.mjs` (merge/copy) |
2020
| `patches/levelcode-core.patch` | edits to `vscode/src/**`, `vscode/build/**` | `bootstrap.sh` (`git apply`) |
21+
| `modules/extension-signature/` | the BUILT app's `node_modules/@vscode/vsce-sign` (never `vscode/`) | `extension-signature.mjs install` (from `build-macos.sh`, `make-dmg.sh`) |
2122

2223
So: **edit extensions in `extensions/…`, not `vscode/extensions/…`.** After editing, run
2324
`./scripts/run-dev.sh` (it syncs canonical → checkout first) to test.
@@ -39,8 +40,9 @@ extensions/levelcode-themes/ signature One Dark / One Light themes (JSON, de
3940
extensions/levelcode-hackability/ user init script + Atom/NPP keymap presets + package generator & hot-reload dev loader
4041
extensions/levelcode-sync/ 'levelcode' auth provider that lights up the built-in Settings Sync (LevelCode Sync, S0)
4142
extensions/levelcode-updater/ notify-only update checker (polls the update feed; never auto-applies)
43+
modules/extension-signature/ Open VSX signature verifier the built app loads as @vscode/vsce-sign, + the signing keys it trusts
4244
patches/levelcode-core.patch our core source edits, applied on bootstrap
43-
scripts/ bootstrap.sh, apply-branding.mjs, run-dev.sh, editor-identity.mjs, build-macos.sh, make-dmg.sh, make-icon.sh, build-web.mjs; atom (CLI launcher) + install-level.sh
45+
scripts/ bootstrap.sh, apply-branding.mjs, run-dev.sh, editor-identity.mjs, extension-signature.mjs, build-macos.sh, make-dmg.sh, make-icon.sh, build-web.mjs; atom (CLI launcher) + install-level.sh
4446
tools/ dependency-free reference servers: sync-server (/v1 Settings-Sync), update-server (/api/update feed)
4547
web/ LevelCode in a browser tab: page entry + callback, scratch-workspace extension, layout, themes + chat skin, browser build of levelcode-ai, dev server, tests (docs/WEB.md)
4648
vscode/ GITIGNORED upstream Code-OSS checkout (generated)
@@ -83,6 +85,33 @@ To macOS a run from source and the installed LevelCode used to be ONE app — sa
8385
- A LaunchServices handler must live outside temp folders — a bundle under `/tmp` is registered but
8486
never chosen. Tests therefore run on fixtures and do not register anything (`test/editorIdentity.test.js`).
8587

88+
## Extensions are verified (keep it that way)
89+
90+
Every release up to 1.3.1 refused every signed extension from Open VSX — "Signature verification was
91+
not executed" — and silently never updated one. The editor verifies through `import('@vscode/vsce-sign')`,
92+
a Microsoft-only module no open-source build contains. `modules/extension-signature` is LevelCode's
93+
module for that slot. Full account + runbooks: `docs/EXTENSION-SIGNATURES.md`.
94+
95+
- **What it checks:** Open VSX's Ed25519 signature over the `.vsix` bytes. A repository signature —
96+
"this is what Open VSX published" — not the publisher's, and not a statement about safety.
97+
- **The key is pinned, never fetched:** `modules/extension-signature/keys.json`. Adding or removing a
98+
key is a security decision; the doc says how a key is checked first. The module may `require` only
99+
`node:crypto`/`fs`/`path`/`zlib`, and its suite runs it with every socket refused.
100+
- **No core patch.** `extension-signature.mjs install` copies three files into the BUILT app (like
101+
`strip-proprietary.mjs`). `check` then fails the build unless the built editor still asks for that
102+
name, the name resolves to our module from the bundles that ask, and the module — imported the way
103+
the editor imports it — accepts a real package and refuses a changed one.
104+
- **`smoke <LevelCode.app>`** asks the app itself (its command line installs one tiny extension into
105+
temp folders). It is the check that would have caught the original bug; CI runs it after each build.
106+
- **`registry`** watches for Open VSX changing its key (daily workflow + release gate). Exit 1 means
107+
evidence, and every shipped build is refusing extensions until a release carries the new key.
108+
It asks the registry and the one host the registry redirects downloads to (`CONTENT_ORIGINS`),
109+
following redirects by hand; a redirect anywhere else is not followed, and it says so.
110+
- **Do not "fix" a refusal by turning verification off** (`extensions.verifySignature`, a patch like
111+
VSCodium's). And a run from source proves nothing here: the editor only enforces on a built app.
112+
- Its tests live in `modules/extension-signature/test/` — outside `extensions/`, whose `test/` folders
113+
ship in the app. `scripts/test-extensions.sh` discovers `modules/*/test/*.test.js` too.
114+
86115
## LevelCode in a tab (`web/`) — read `docs/WEB.md` first
87116

88117
The same `extensions/levelcode-ai` source runs in a browser tab (web-worker extension host). Rules that keep
@@ -247,7 +276,7 @@ big-file mode badge. Files: extension.js + fileOps/lineOps/columnOps/encodingEol
247276
is not in the editor until you load it: `./scripts/run-dev.sh --extensionDevelopmentPath=<worktree>/extensions/levelcode-ai`
248277
(from the main checkout; the dev extension replaces the built-in one). Uncommitted work is not "on the branch" —
249278
checking the branch out somewhere else gets none of it. Run it in the editor before telling anyone to try it.
250-
- Commit `extensions/`, `patches/`, `branding/`, `scripts/`, `web/`, `docs/`, `PLAN.md`, `CLAUDE.md`. Never commit `vscode/` or `dist-web/`.
279+
- Commit `extensions/`, `modules/`, `patches/`, `branding/`, `scripts/`, `web/`, `docs/`, `PLAN.md`, `CLAUDE.md`. Never commit `vscode/` or `dist-web/`.
251280
- `extensions/levelcode-ai/diagram/` modules listed in `bundle.FILES` are pasted INTO a script block in `chat.html`.
252281
They must never contain the text of a script tag or an HTML comment opener — not even in a comment — or the block
253282
ends early; `bundle.js` refuses to build if one does. Keep them dependency-free and free of `require('vscode')`/`fs`.

‎NOTICE‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,10 @@ LevelCode is not produced by, endorsed by, or affiliated with Microsoft.
1212
"Visual Studio Code" and related marks are trademarks of Microsoft.
1313
The upstream Code-OSS source is fetched at build time and is not redistributed
1414
in this repository.
15+
16+
modules/extension-signature/test/fixtures/ holds two extension packages as
17+
published on Open VSX (https://open-vsx.org), unmodified, used only as test
18+
data and not shipped in the app:
19+
- git-rebase-syntax 0.0.1, MIT License, Copyright (c) 2020 Jérome Perrin
20+
- systemd-unit-file 1.0.6, MIT License, Copyright (c) 2018 Takashi Oguma
21+
Each package carries its own licence text; see the README in that folder.

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ LevelCode is a **clean overlay on top of Code-OSS**, not a vendored copy of the
3434

3535
- `extensions/` — the first-party extensions (native AI, Notepad++ pack, themes, hackability, sync, updater), plain JS with no build step.
3636
- `branding/` — the LevelCode identity (`product.overlay.json`) + icons, deep-merged onto upstream `product.json`; the extension gallery wired to **Open VSX**.
37+
- `modules/` — what ships in the app outside an extension: the Open VSX signature verifier and the signing keys it trusts.
3738
- `patches/` — the small set of core source patches (each tagged `// [LevelCode]`).
3839
- `scripts/` — bootstrap / run-dev / build / dmg / icon, and `build-web.mjs` (LevelCode in a browser tab).
3940
- `web/` — LevelCode in your browser: the same workbench and AI extension as a static site you can host; sign in with a LevelCode account or use your own key. See [`docs/WEB.md`](docs/WEB.md).
@@ -60,7 +61,7 @@ Actively built, macOS-first. Working today: the Notepad++ power-editing pack; th
6061

6162
## Why a fork
6263

63-
Code-OSS is MIT-licensed and free to fork, modify, and ship. LevelCode honors the constraints every forker must: the **Microsoft Extension Marketplace is Microsoft-products-only**, so LevelCode points its gallery at **Open VSX** (the Eclipse-run open marketplace); and LevelCode ships its own name, icon, and identity. It is **not** produced by, endorsed by, or affiliated with Microsoft, and the upstream Code-OSS source is fetched at build time — never redistributed in this repository.
64+
Code-OSS is MIT-licensed and free to fork, modify, and ship. LevelCode honors the constraints every forker must: the **Microsoft Extension Marketplace is Microsoft-products-only**, so LevelCode points its gallery at **Open VSX** (the Eclipse-run open marketplace) — and checks every extension it installs or updates against Open VSX's signature, with the signing key pinned in the app rather than fetched ([how, and what that does and does not prove](./docs/EXTENSION-SIGNATURES.md)); and LevelCode ships its own name, icon, and identity. It is **not** produced by, endorsed by, or affiliated with Microsoft, and the upstream Code-OSS source is fetched at build time — never redistributed in this repository.
6465

6566
## License
6667

‎SECURITY.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ LevelCode is built to keep your code and your credentials on your machine.
1212
your API key over plaintext `http://` to a non-local host (https, or an explicit localhost, only).
1313
- **Readable.** The AI layer is plain, dependency-free JS — you can see exactly what is sent to a
1414
provider. Transparency is a design goal, not an afterthought.
15+
- **Extensions are checked on the way in.** Every extension installed or updated from Open VSX is
16+
verified against the registry's signature, with the signing key pinned in the app rather than fetched
17+
from the server being checked. That proves the package is the one Open VSX published. It does not
18+
prove the extension is safe, and it is not the publisher's own signature. See
19+
[`docs/EXTENSION-SIGNATURES.md`](./docs/EXTENSION-SIGNATURES.md).
1520

1621
## Reporting a vulnerability
1722

@@ -25,6 +30,7 @@ you posted through the fix.
2530

2631
## Scope
2732

28-
- **In scope:** the LevelCode first-party extensions and the build / branding kit in this repository.
33+
- **In scope:** the LevelCode first-party extensions, the extension signature verifier
34+
(`modules/extension-signature`), and the build / branding kit in this repository.
2935
- **Out of scope:** upstream Code-OSS / VS Code itself (report those to Microsoft), and third-party
3036
extensions you install from Open VSX.

0 commit comments

Comments
 (0)