Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(windows): install 1Helm on Windows with a PowerShell one-liner
Windows no longer ships an application. `irm https://1helm.com/install.ps1
| iex` enables WSL 2, imports a digest-pinned Ubuntu root filesystem, runs
the ordinary Linux installer inside it, and registers the keepalive. The
browser is the interface at http://localhost:8123. Nothing is code-signed
because nothing is an executable, so SmartScreen never appears - which
matters because 1Helm has no Authenticode identity and was never going to
obtain one.

Only enabling the Windows optional features and installing Microsoft's WSL
package cross the UAC boundary. Importing the distribution, running the
Linux installer and registering the keepalive all stay in the signed-in
user's session on purpose: WSL state is per-user, so a distribution
imported by an elevated session started with over-the-shoulder credentials
would belong to that administrator rather than the person at the machine.

The restart Windows requires to activate those features is reported as a
restart with numbered steps, never as a failure, and every step is
idempotent so re-running the same command continues where it stopped.
`-Wait` on an elevated ShellExecute is unreliable, so the parent blocks on
the real process handle before reading the exit code.

Microsoft's WSL package is checked against a pinned digest AND required to
carry a valid Microsoft Corporation Authenticode signature; a digest alone
only proves we fetched what we expected, not who built it.

A port pre-flight refuses to continue when something already holds 8123.
Every WSL distribution shares one network namespace with Windows, so a
foreign listener would both prevent 1Helm binding and answer its health
probe in its place.

The keepalive payload moves to site/public/keepalive/ and is fetched from
the same origin as the installer, so the two can never be mismatched.
Obtaining it is fail-closed: without the keepalive, WSL tears the
distribution down when idle and 1Helm silently stops answering, so a
missing component aborts the install rather than warning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
  • Loading branch information
gitcommit90 and claude committed Aug 2, 2026
commit 4eaf92da7d33b5f9534adb53e0e7bb5edc2df125
409 changes: 409 additions & 0 deletions site/public/install.ps1

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions site/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,11 @@ const server = createServer(async (req, res) => {
}

if (path === "/install.sh" && serveFile(req, res, join(SITE_PUBLIC, "install.sh"), "no-cache")) return;
// Windows installs with `irm https://1helm.com/install.ps1 | iex`. That script
// fetches the keepalive payload from this same origin, so both must be served
// uncached: a stale installer would pair with a current release.
if (path === "/install.ps1" && serveFile(req, res, join(SITE_PUBLIC, "install.ps1"), "no-cache")) return;
if (path.startsWith("/keepalive/") && serveFile(req, res, safeFile(SITE_PUBLIC, path.slice(1)), "no-cache")) return;
if (path.startsWith("/schemas/") && serveFile(req, res, safeFile(SITE_PUBLIC, path.slice(1)), "public, max-age=3600")) return;
if (path.startsWith("/assets/") && serveFile(req, res, safeFile(SITE_PUBLIC, path.slice(1)), "public, max-age=604800")) return;
if (path.startsWith("/media/") && serveFile(req, res, safeFile(SITE_PUBLIC, path.slice(1)), "public, max-age=604800")) return;
Expand Down
8 changes: 8 additions & 0 deletions test/site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ test("standalone 1helm.com website serves independent product and documentation
assert.equal(benchmarkSchema.$id, "https://1helm.com/schemas/autonomy-benchmark-v1.json");
assert.deepEqual(benchmarkSchema.required, ["schema", "product", "kind", "started_at", "finished_at", "deterministic", "scope", "summary", "checks"]);
assert.equal((await fetch(`${base}/install.sh`)).status, 200);
// Windows installs via `irm https://1helm.com/install.ps1 | iex`, and that
// script downloads its keepalive payload from this same origin. If either
// route 404s the install fails partway, so both are contract surface.
assert.equal((await fetch(`${base}/install.ps1`)).status, 200);
for (const part of ["keepalive-install.ps1", "keepalive-run.ps1", "keepalive-remove.ps1", "keepalive-hold.sh"]) {
assert.equal((await fetch(`${base}/keepalive/${part}`)).status, 200, `/keepalive/${part} must be served`);
}
assert.equal((await fetch(`${base}/keepalive/../server.mjs`)).status, 404, "the keepalive route must not escape its directory");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm how safeFile normalizes and contains paths in the site server.
set -u
fd -t f 'server.mjs' site || true
rg -n -C 12 'function safeFile|const safeFile' site/ || true
rg -n -C 3 'safeFile\(' site/ || true

Repository: gitcommit90/1Helm

Length of output: 3495


🏁 Script executed:

#!/bin/bash
set -u
printf '%s\n' '--- test/site.mjs ---'
sed -n '80,125p' test/site.mjs
printf '%s\n' '--- site/server.mjs request parsing and routing ---'
sed -n '390,465p' site/server.mjs
printf '%s\n' '--- relevant public files ---'
git ls-files site | rg '(^|/)(server\.mjs|keepalive|schemas|assets|media|icons|brand)/|site/public' | head -80
printf '%s\n' '--- URL behavior probe ---'
node - <<'JS'
for (const value of [
  'http://example.test/keepalive/../server.mjs',
  'http://example.test/keepalive/%2e%2e/server.mjs',
  'http://example.test/keepalive/..%2fserver.mjs',
  'http://example.test/keepalive/%2e%2e%2fserver.mjs',
]) {
  const url = new URL(value);
  console.log(JSON.stringify({input: value, href: url.href, pathname: url.pathname}));
}
JS

Repository: gitcommit90/1Helm

Length of output: 9394


🏁 Script executed:

#!/bin/bash
set -u
printf '%s\n' '--- request URL parsing ---'
rg -n -C 10 'new URL|req\.url|pathname|decodeURI|decodeURIComponent' site/server.mjs
printf '%s\n' '--- safeFile imports and path semantics ---'
sed -n '1,45p' site/server.mjs
printf '%s\n' '--- path normalization probe ---'
node - <<'JS'
import { normalize, resolve } from "node:path";
const root = "/repo/site/public";
for (const value of [
  "keepalive/..%2fserver.mjs",
  "keepalive/%2e%2e%2fserver.mjs",
  "keepalive/../server.mjs",
]) {
  const relative = normalize(value).replace(/^[/\\]+/, "");
  const candidate = resolve(root, relative);
  console.log(JSON.stringify({value, relative, candidate, contained: candidate === root || candidate.startsWith(`${root}/`)}));
}
JS
printf '%s\n' '--- files that can provide a served escape target ---'
find site/public -maxdepth 2 -type f -printf '%P\n' | sort | head -100

Repository: gitcommit90/1Helm

Length of output: 4980


🏁 Script executed:

#!/bin/bash
set -u
sed -n '45,95p' site/server.mjs
sed -n '250,330p' site/server.mjs
printf '%s\n' '--- exact route and server construction ---'
rg -n -C 8 'createServer|handleRequest|function .*request|new URL' site/server.mjs

Repository: gitcommit90/1Helm

Length of output: 7979


Test safeFile directly. fetch and the server’s new URL() both normalize /keepalive/../server.mjs to /server.mjs. Encoding only the slash avoids normalization, but safeFile then treats %2f as a literal character, so the 404 still does not test traversal rejection. Add a direct safeFile test for ../server.mjs, and retain an HTTP test for encoded-path handling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/site.mjs` at line 111, Replace the current normalized-path assertion in
the test with a direct safeFile check using ../server.mjs that verifies
traversal is rejected, while retaining a separate HTTP request using an encoded
slash to exercise server handling of encoded paths. Anchor the changes to the
existing safeFile and keepalive route tests, preserving the expected 404
responses.

const linuxRelease = await (await fetch(`${base}/api/releases/linux/latest`)).json();
assert.deepEqual(linuxRelease, {
version: "0.0.31",
Expand Down