Skip to content

Commit f44cd14

Browse files
committed
docs: correct three claims the code stopped supporting
The security threat model called the extension host a sandbox in three places, while the root `SECURITY.md` says the opposite in as many words: it is a fault boundary, not a security one, and an extension reaches app-private storage and the network exactly as the app does. A threat model that asserts a boundary the project says does not exist is the direction that gets a reader to under-protect, so the three rows now name what actually bounds an extension, the Android app sandbox and the SAF grants the user gave. The landing page was the last surface quoting 875 MB and promising a one-time unpack. Both were corrected everywhere else; the page a prospective user reads first kept them. It now says 873 MB, which is what the storage gate asks for, and that extraction repeats after an update. Four comments in `FirstRunSetup.kt` still said setup is keyed on versionName alone. It is keyed on versionName or versionCode, and the difference is expensive: a versionCode bump on its own re-runs the whole extraction, which a reader trusting these comments would conclude it does not. Two rows of the release plan described `r8.yml` as cron or dispatch only. It also runs on pushes to main and on pull requests, filtered to the files that configure the shrinkers, which is why a Kotlin-only change still waits for the cron. One line in MILESTONES.md blamed a noexec mount for npm being a bash function. It is SELinux denying execute_no_trans under filesDir, and the distinction is load-bearing: a noexec mount would also block the .node addons loaded from the same directory. Swept the tree rather than the files a report named. Two candidates were checked and left alone: the file-tree annotations in the implementation plan describe what `ToolchainActivity.kt` is, which is accurate, and the remaining noexec mentions are inside dated records.
1 parent 9a72254 commit f44cd14

6 files changed

Lines changed: 24 additions & 19 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Changed
1515

16+
- The security document no longer calls the extension host a sandbox. It is a fault boundary, and an extension reaches app-private storage exactly as the app does.
17+
- The landing page quotes the storage figure the app computes and says extraction repeats after an update, which every other document already said.
1618
- The design documents now describe the build that ships: two on-demand toolchains, terminals that spawn bash on a real PTY, and how the server is actually patched and built.
1719
- Seven documents named a "Settings > Toolchains" screen the app has never had. They now name the real route, the launcher icon's **Manage toolchains** shortcut.
1820
- Three documents no longer list file-type "Open with" intent filters as shipped. A `content://` URI has no POSIX path, so every save would reach a copy.

‎MILESTONES.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ M6 (Release) → Play Store release
281281
- [x] Symlink: `make` → `libmake.so` via `setupToolSymlinks()`
282282

283283
3. **npm integration** (`FirstRunSetup.createNpmWrappers`)
284-
- [x] npm/npx defined as bash functions in `.bashrc` (not script wrappers — Android noexec restriction)
284+
- [x] npm/npx defined as bash functions in `.bashrc` (not script wrappers; SELinux denies `execute_no_trans` under `filesDir`, while `dlopen` of a `.node` addon there still works)
285285
- [x] Functions invoke Node.js with `npm-cli.js` entry point from `usr/lib/node_modules/npm/`
286286
- [x] `.npmrc` created with `script-shell` pointing to `libbash.so`
287287

‎android/app/src/main/kotlin/com/vscodroid/setup/FirstRunSetup.kt‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ class FirstRunSetup(
216216
// prefix everything on PATH resolves through. Losing one silently
217217
// left an install that reached the editor and could never serve it,
218218
// with markSetupComplete() certifying the result and isFirstRun()
219-
// keyed on versionName, so nothing tried again until the app
220-
// updated. Nothing on device checks these trees are complete --
219+
// keyed on versionName or versionCode, so nothing tried again until
220+
// the app updated. Nothing on device checks these trees are complete --
221221
// verify-server-tree.py checks the build, not the install.
222222
//
223223
// Aborting was held back on the argument that a single lost file
@@ -383,8 +383,8 @@ class FirstRunSetup(
383383
* reach them, so they only ever need creating. This one needs repairing.
384384
*
385385
* Creating it once per version was not enough. isFirstRun() gates on
386-
* versionName, so a folder deleted after setup stayed missing through every
387-
* relaunch and force-stop: the explorer was empty, new files could not be
386+
* versionName or versionCode, so a folder deleted after setup stayed missing
387+
* through every relaunch and force-stop: the explorer was empty, new files could not be
388388
* saved, and terminals started in a directory that was not there. The only
389389
* ways back were clearing app data or installing a new version.
390390
*
@@ -499,9 +499,10 @@ class FirstRunSetup(
499499
*
500500
* The interpreter ships in the APK and every install replaces it. Its
501501
* runtime library and stdlib travel in assets and reach filesDir only
502-
* through first-run extraction, which [isFirstRun] gates on versionName. An
503-
* install that changes the bundled Python without changing versionName --
504-
* `adb install -r` of a rebuilt debug APK is the everyday case -- therefore
502+
* through first-run extraction, which [isFirstRun] gates on versionName or
503+
* versionCode. An install that changes the bundled Python without moving
504+
* either -- `adb install -r` of a rebuilt debug APK is the everyday case --
505+
* therefore
505506
* leaves a new interpreter next to the previous runtime. Python then dies
506507
* with `CANNOT LINK EXECUTABLE ... library "libpython3.X.so" not found`,
507508
* naming a missing file rather than the install that removed it.
@@ -1548,8 +1549,8 @@ claude() {
15481549
// Thrown, not logged. This runs only from runSetupLocked, whose
15491550
// markSetupComplete() is the last statement of the same try block --
15501551
// so swallowing the failure certifies an install that has no
1551-
// .bashrc, and isFirstRun() is keyed on versionName, so nothing
1552-
// writes one until the app updates. The every-launch repairs cannot
1552+
// .bashrc, and isFirstRun() is keyed on versionName or versionCode,
1553+
// so nothing writes one until the app updates. The every-launch repairs cannot
15531554
// cover it either: createNpmWrappers, ensureToolchainEnvSourcing
15541555
// and ensurePromptFix all open with `if (bashrc.exists())`.
15551556
//

‎docs/06-SECURITY.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ flowchart TD
4545
| ----------------------------------------------------- | -------------------------- | ------ | ---------- | ----------------------------------------------------------------------------------- |
4646
| Other app connects to localhost server | **Spoofing** | Medium | Low | localhost-only binding, plus a connection token required on all but three routes |
4747
| Malicious extension impersonates trusted extension | **Spoofing** | Medium | Low | Open VSX publisher verification, user review |
48-
| Malicious extension steals files | **Tampering** | High | Medium | Extension sandbox (Extension Host only), user awareness |
48+
| Malicious extension steals files | **Tampering** | High | Medium | **Not mitigated.** The extension host is a fault boundary, not a security one: an extension reaches app-private storage and the network exactly as the app does. What bounds it is the Android app sandbox and the SAF grants the user gave |
4949
| Man-in-middle on Open VSX downloads | **Tampering** | High | Low | HTTPS only, certificate pinning (future) |
5050
| No audit trail for file changes by extensions | **Repudiation** | Low | Medium | VS Code timeline/git history, extension activity logging (future) |
5151
| User denies executing destructive terminal command | **Repudiation** | Low | Low | Accepted, no audit trail. Each terminal spawns bash directly on a PTY through node-pty, and the only record is bash's own history file in app-private storage, which the user can edit or clear |
@@ -55,7 +55,7 @@ flowchart TD
5555
| Malicious extension consuming all memory/CPU | **Denial of Service** | Medium | Low | Extension Host resource limits, idle-kill for LS |
5656
| WebView XSS via malicious file content | **Elevation of Privilege** | Medium | Low | VS Code CSP, WebView sandboxing |
5757
| Extension/webview script abuses AndroidBridge methods | **Elevation of Privilege** | High | Medium | Per-session capability token on every bridge method, pinned by a reflection test |
58-
| Extension escapes sandbox to access system files | **Elevation of Privilege** | High | Low | Android app sandbox, Extension Host isolation (a worker_thread inside the server process) |
58+
| Extension reads files outside the app | **Elevation of Privilege** | High | Low | Android app sandbox, and SAF grants for anything outside it. The worker_thread the extension host runs in is not part of this: it exists so the host does not spend a phantom process slot |
5959

6060
---
6161

@@ -88,7 +88,7 @@ flowchart TD
8888
| Control | Implementation |
8989
| ------------------------------ | -------------------------------------------------------------------------------------------------------- |
9090
| Extension Host isolation | Runs as a worker_thread inside the server process, applied by `patches/0004-exthost-as-worker-thread.patch`, so it does not spend one of Android's 32 phantom process slots |
91-
| VS Code Extension API sandbox | Extensions can only access vscode.\* APIs |
91+
| Extension reach | **No sandbox.** The extension host is Node, so an extension can `require('fs')` and reach whatever the app can. The `vscode.*` API is a convenience, not a boundary |
9292
| AndroidBridge capability model | All bridge APIs require the valid per-session token; no origin component |
9393
| File system scoping | Extensions see workspace folder by default |
9494
| Open VSX moderation | Open VSX has publisher verification and abuse reporting |

‎docs/10-RELEASE_PLAN.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ flowchart TD
5050
REPO --> B2["Same events, only when one of seven build-configuration paths changes<br/>r8.yml: R8, the resource shrinker and lintVitalRelease"]
5151
REPO --> B3["Tag v*<br/>release.yml: signed AAB and APK, toolchain ZIPs, GitHub Release"]
5252
REPO --> B4["Push to main touching docs/site, the user guide or the privacy policy<br/>pages.yml: publishes the usage site"]
53-
REPO --> B5["Monday cron, or dispatched by hand<br/>r8.yml at 03:00 UTC, patch-drift.yml at 04:00 UTC"]
53+
REPO --> B5["r8.yml: Monday 03:00 UTC, pushes to main, pull requests, or by hand<br/>patch-drift.yml: Monday 04:00 UTC, or by hand"]
5454
REPO --> B6["Dispatched by hand on a VS Code bump, arm64 runner<br/>build-vscode-oss.yml: builds the server once per version"]
5555
```
5656

@@ -101,7 +101,7 @@ jobs:
101101
| Workflow | Schedule | Purpose | Failure Action |
102102
|----------|----------|---------|----------------|
103103
| `patch-drift.yml` | Monday 04:00 UTC, or dispatched with a tag | Applies `patches/` against an upstream VS Code tag with `git apply --check` | Rebase the patch set before the next version bump |
104-
| `r8.yml` | Monday 03:00 UTC, or dispatched | Runs R8, the resource shrinker and `lintVitalRelease`, so a dependency that arrives without its consumer rules is caught before a tag | Fix the keep rules, or the shrinker configuration, before tagging |
104+
| `r8.yml` | Monday 03:00 UTC, pushes to main, pull requests, or dispatched. The three event triggers are filtered to the files that configure the shrinkers, so a change to Kotlin alone still waits for the cron | Runs R8, the resource shrinker and `lintVitalRelease`, so a dependency that arrives without its consumer rules is caught before a tag | Fix the keep rules, or the shrinker configuration, before tagging |
105105

106106
### 2.3 Caching Strategy
107107

‎docs/site/index.html‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ <h1>VS Code, on your phone.</h1>
5252
</a>
5353
</div>
5454
<p class="hero-note">
55-
Android 13 or newer, 64-bit. Around 875 MB free space for the first
56-
launch.
55+
Android 13 or newer, 64-bit. Around 873 MB free space for the first
56+
launch, and again after each update.
5757
</p>
5858
</div>
5959
<div class="device">
@@ -147,8 +147,10 @@ <h3>Install</h3>
147147
<h3>Let it unpack</h3>
148148
<p>
149149
The first launch extracts the editor and the bundled tools behind
150-
a progress bar. It happens once, and it needs the space before it
151-
starts rather than partway through.
150+
a progress bar, and it needs the space before it starts rather than
151+
partway through. It repeats after each app update, though an update
152+
needs far less room because what is already unpacked counts toward
153+
it.
152154
</p>
153155
</div>
154156
</li>

0 commit comments

Comments
 (0)