Skip to content

feat(runner-release): add bounded release transport and a strict package reader - #43

Merged
namikmesic merged 11 commits into
mainfrom
fm/runner-distribution-bounded-transport-an-92
Sep 30, 2026
Merged

namikmesic merged 11 commits into
mainfrom
fm/runner-distribution-bounded-transport-an-92

Conversation

@namikmesic

@namikmesic namikmesic commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Intent

Add the bounded, authenticated-package transport and the strict release-archive reader that later runner distribution slices will use: a dedicated reader for signed release metadata that bounds every response before parsing, a streamed tarball downloader that constructs official URLs from the known release prefix, validates every redirect against an allowlist, forwards no credentials, and enforces the signed byte count and absolute size and time limits; and a strict reader for the fixed runner package layout that validates the archive entry by entry into private staging without activating anything. No consumer is wired yet.

What Changed

  • Add a strict signed-release record reader that checks encoding and size before the bytes are parsed, and use that bounded body read when the app and the runner fetch /v1/runner/releases.
  • Add a streamed package downloader that builds official GitHub release URLs from the known prefix, allowlists every redirect, sends only its own headers, and enforces the signed byte count plus absolute size and time limits. No production caller uses it yet.
  • Add a strict archive reader for the fixed runner package layout that checks each gzip ustar entry and writes it into a private staging directory, leaving activation to the caller.

Risk Assessment

✅ Low: The new downloader and archive reader fail closed on redirects, size, digest, gzip framing, and tar layout, and the only live caller change is the bounded release-listing read.

Testing

I stood up the real development puck server and drove the release listing through the Electron app client and the runner client, including an oversized body, a still-readable unrelated body, and a gzip listing. I then ran the real downloader and archive reader against that server, a redirecting host, GitHub, a file-size limit, and a sandbox that blocks bin/. Ten of those checks passed. The official redirect onto the asset host stayed untested because GitHub returned 404 with no Location.

  • Live validation: ✅ go - 10 of 11 scenarios driven live against the product
Scenario Result Live Evidence
A development server lists a runner package, and both the app and the runner read that listing ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/listing-response.json
A release listing over 4MiB is refused, and a larger unrelated server response is still read ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
A gzip-encoded release listing is still read, up to the byte bound ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
A development download saves the package from the server origin and checks its size and digest ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
A development download refuses a redirect, another origin, a URL with credentials, and a content encoding ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
An official download requests the constructed GitHub release URL and sends no credential headers ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
An official download follows a redirect only onto the release hosts ⏸️ untested no https://github.com/namikmesic/puck/releases/download/v0.1.0/puck-runner-macos-arm64-0.1.0.tar.gz answered 404 with no Location, and the repo has no published release, so the client never saw a redirec…
A short disk write fails the package download and the archive unpack and leaves nothing behind ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
A valid package is unpacked into private staging and is not run ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
An archive that breaks the layout or names the wrong version is refused and leaves no staging ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
Staging is removed when the archive reader cannot create bin/ ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
Evidence: Development server release listing
{
  "status": 200,
  "headers": {
    "cache-control": "no-store",
    "connection": "keep-alive",
    "content-length": "302",
    "content-type": "application/json; charset=utf-8",
    "date": "Wed, 30 Sep 2026 23:05:10 GMT",
    "keep-alive": "timeout=5",
    "x-content-type-options": "nosniff"
  },
  "body": {
    "latest": "9.9.9",
    "minVersion": null,
    "assets": [
      {
        "os": "linux",
        "arch": "x64",
        "version": "9.9.9",
        "file": "puck-runner-linux-x64-9.9.9.tar.gz",
        "url": "http://127.0.0.1:57333/runner/9.9.9/puck-runner-linux-x64-9.9.9.tar.gz",
        "sha256": "5e49baa0f16a5b5ee19c8b0731d9e6aad85121433392a70b1a434a82787cb2ea",
        "size": 24
      }
    ]
  }
}
Evidence: Live scenario results
{
  "summary": [
    {
      "name": "A development server lists a runner package, and both the app and the runner read that listing",
      "pass": "pass"
    },
    {
      "name": "A release listing over 4MiB is refused, and a larger unrelated server response is still read",
      "pass": "pass"
    },
    {
      "name": "A gzip-encoded release listing is still read, up to the byte bound",
      "pass": "pass"
    },
    {
      "name": "A development download saves the package from the server origin and checks its size and digest",
      "pass": "pass"
    },
    {
      "name": "A development download refuses a redirect, another origin, a URL with credentials, and a content encoding",
      "pass": "pass"
    },
    {
      "name": "An official download requests the constructed GitHub release URL and sends no credential headers",
      "pass": "pass"
    },
    {
      "name": "An official download follows a redirect only onto the release hosts",
      "pass": "untested"
    },
    {
      "name": "A short disk write fails the package download and the archive unpack and leaves nothing behind",
      "pass": "pass"
    },
    {
      "name": "A valid package is unpacked into private staging and is not run",
      "pass": "pass"
    },
    {
      "name": "An archive that breaks the layout or names the wrong version is refused and leaves no staging",
      "pass": "pass"
    },
    {
      "name": "Staging is removed when the archive reader cannot create bin/",
      "pass": "pass"
    }
  ],
  "results": [
    {
      "name": "A development server lists a runner package, and both the app and the runner read that listing",
      "pass": true,
      "detail": {
        "listingHeaders": {
          "cache-control": "no-store",
          "connection": "keep-alive",
          "content-length": "302",
          "content-type": "application/json; charset=utf-8",
          "date": "Wed, 30 Sep 2026 23:05:10 GMT",
          "keep-alive": "timeout=5",
          "x-content-type-options": "nosniff"
        },
        "listing": {
          "latest": "9.9.9",
          "minVersion": null,
          "assets": [
            {
              "os": "linux",
              "arch": "x64",
              "version": "9.9.9",
              "file": "puck-runner-linux-x64-9.9.9.tar.gz",
              "url": "http://127.0.0.1:57333/runner/9.9.9/puck-runner-linux-x64-9.9.9.tar.gz",
              "sha256": "5e49baa0f16a5b5ee19c8b0731d9e6aad85121433392a70b1a434a82787cb2ea",
              "size": 24
            }
          ]
        },
        "appListing": {
          "ok": true,
          "body": {
            "latest": "9.9.9",
            "minVersion": null,
            "assets": [
              {
                "os": "linux",
                "arch": "x64",
                "version": "9.9.9",
                "file": "puck-runner-linux-x64-9.9.9.tar.gz",
                "url": "http://127.0.0.1:57333/runner/9.9.9/puck-runner-linux-x64-9.9.9.tar.gz",
                "sha256": "5e49baa0f16a5b5ee19c8b0731d9e6aad85121433392a70b1a434a82787cb2ea",
                "size": 24
              }
            ]
          }
        },
        "runnerListing": {
          "ok": true,
          "body": {
            "latest": "9.9.9",
            "minVersion": null,
            "assets": [
              {
                "os": "linux",
                "arch": "x64",
                "version": "9.9.9",
                "file": "puck-runner-linux-x64-9.9.9.tar.gz",
                "url": "http://127.0.0.1:57333/runner/9.9.9/puck-runner-linux-x64-9.9.9.tar.gz",
                "sha256": "5e49baa0f16a5b5ee19c8b0731d9e6aad85121433392a70b1a434a82787cb2ea",
                "size": 24
              }
            ]
          }
        }
      }
    },
    {
      "name": "A release listing over 4MiB is refused, and a larger unrelated server response is still read",
      "pass": true,
      "detail": {
        "appHuge": {
          "ok": false,
          "name": "RunnerDownloadError",
          "code": "too-large",
          "message": "The response announces 4194305 bytes; at most 4194304 are read."
        },
        "runnerHuge": {
          "ok": false,
          "name": "RunnerDownloadError",
          "code": "too-large",
          "message": "The response announces 4194305 bytes; at most 4194304 are read."
        },
        "appWide": {
          "ok": true,
          "bytes": 4194391,
          "tail": "END"
        },
        "runnerWide": {
          "ok": false,
          "name": "ApiError",
          "code": "nope",
          "message": "TAIL-END"
        },
        "listingHeadersSeen": [
          {
            "host": "127.0.0.1:57339",
            "connection": "keep-alive",
            "accept": "application/json",
            "accept-language": "*",
            "sec-fetch-mode": "cors",
            "user-agent": "node",
            "accept-encoding": "gzip, deflate"
          },
          {
            "host": "127.0.0.1:57339",
            "connection": "keep-alive",
            "accept": "*/*",
            "accept-language": "*",
            "sec-fetch-mode": "cors",
            "user-agent": "node",
            "accept-encoding": "gzip, deflate"
          }
        ]
      }
    },
    {
      "name": "A gzip-encoded release listing is still read, up to the byte bound",
      "pass": true,
      "detail": {
        "appGz": {
          "ok": true,
          "body": {
            "latest": "1.2.3",
            "minVersion": null,
            "assets": []
          }
        },
        "runnerGz": {
          "ok": true,
          "body": {
            "latest": "1.2.3",
            "minVersion": null,
            "assets": []
          }
        },
        "gzipBytes": 68
      }
    },
    {
      "name": "A development download saves the package from the server origin and checks its size and digest",
      "pass": true,
      "detail": {
        "devDl": {
          "ok": true,
          "got": {
            "url": "http://127.0.0.1:57333/runner/9.9.9/puck-runner-linux-x64-9.9.9.tar.gz",
            "size": 24,
            "sha256": "5e49baa0f16a5b5ee19c8b0731d9e6aad85121433392a70b1a434a82787cb2ea"
          },
          "destExists": true,
          "destBytes": 24,
          "requests": [
            {
              "origin": "http://127.0.0.1:57333",
              "path": "/runner/9.9.9/puck-runner-linux-x64-9.9.9.tar.gz",
              "method": "GET",
              "status": 200,
              "headerNames": [
                "accept",
                "accept-encoding",
                "accept-language",
                "sec-fetch-mode",
                "user-agent"
              ],
              "accept": "application/octet-stream",
              "acceptEncoding": "identity",
              "authorization": null,
              "cookie": null
            }
          ]
        },
        "savedBytes": 24
      }
    },
    {
      "name": "A development download refuses a redirect, another origin, a URL with credentials, and a content encoding",
      "pass": true,
      "detail": {
        "redirDl": {
          "ok": false,
          "name": "RunnerDownloadError",
          "code": "bad-url",
          "message": "Redirect 1 is refused: a development server's download must not redirect.",
          "destExists": false,
          "requests": [
            {
              "origin": "http://127.0.0.1:57350",
              "path": "/pkg.tar.gz",
              "method": "GET",
              "status": 302,
              "headerNames": [
                "accept",
                "accept-encoding",
                "accept-language",
                "sec-fetch-mode",
                "user-agent"
              ],
              "accept": "application/octet-stream",
              "acceptEncoding": "identity",
              "authorization": null,
              "cookie": null
            }
          ]
        },
        "redirHits": 1,
        "redirHeaders": {
          "host": "127.0.0.1:57350",
          "connection": "keep-alive",
          "accept": "application/octet-stream",
          "accept-encoding": "identity",
          "accept-language": "*",
          "sec

... [249 bytes truncated] ...

er http://127.0.0.1:57350.",
          "destExists": false,
          "requests": []
        },
        "credDl": {
          "ok": false,
          "name": "RunnerDownloadError",
          "code": "bad-url",
          "message": "The download URL carries credentials.",
          "destExists": false,
          "requests": []
        },
        "encDl": {
          "ok": false,
          "name": "RunnerDownloadError",
          "code": "content-encoding",
          "message": "The response is gzip-encoded; release bytes are read as sent.",
          "destExists": false,
          "requests": [
            {
              "origin": "http://127.0.0.1:57355",
              "path": "/pkg.tar.gz",
              "method": "GET",
              "status": 200,
              "headerNames": [
                "accept",
                "accept-encoding",
                "accept-language",
                "sec-fetch-mode",
                "user-agent"
              ],
              "accept": "application/octet-stream",
              "acceptEncoding": "identity",
              "authorization": null,
              "cookie": null
            }
          ]
        }
      }
    },
    {
      "name": "An official download requests the constructed GitHub release URL and sends no credential headers",
      "pass": true,
      "detail": {
        "official": {
          "ok": false,
          "name": "RunnerDownloadError",
          "code": "http-status",
          "message": "github.com answered 404 for puck-runner-macos-arm64-0.1.0.tar.gz.",
          "destExists": false,
          "requests": [
            {
              "origin": "https://github.com",
              "path": "/namikmesic/puck/releases/download/v0.1.0/puck-runner-macos-arm64-0.1.0.tar.gz",
              "method": "GET",
              "status": 404,
              "headerNames": [
                "accept",
                "accept-encoding",
                "accept-language",
                "sec-fetch-mode",
                "user-agent"
              ],
              "accept": "application/octet-stream",
              "acceptEncoding": "identity",
              "authorization": null,
              "cookie": null
            }
          ]
        },
        "supplied": {
          "ok": false,
          "name": "RunnerDownloadError",
          "code": "bad-url",
          "message": "An official download takes no URL; it is built from the release.",
          "destExists": false,
          "requests": []
        }
      }
    },
    {
      "name": "An official download follows a redirect only onto the release hosts",
      "pass": false,
      "untested": true,
      "detail": {
        "reason": "The official package URL https://github.com/namikmesic/puck/releases/download/v0.1.0/puck-runner-macos-arm64-0.1.0.tar.gz answers 404 with no Location header, and the repository has no published release. The official policy will not send that request anywhere except github.com, so a local redirector cannot stand in. Publish a runner asset on that release (it 302s to release-assets.githubusercontent.com) to drive the redirect hop.",
        "observedStatus": 404
      }
    },
    {
      "name": "A short disk write fails the package download and the archive unpack and leaves nothing behind",
      "pass": true,
      "detail": {
        "shortDl": {
          "ok": false,
          "name": "RunnerDownloadError",
          "code": "write-failed",
          "message": "Cannot write ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/work/dests/short.bin: EFBIG: file too large, write",
          "destExists": false,
          "requests": [
            {
              "origin": "http://127.0.0.1:57358",
              "path": "/pkg.tar.gz",
              "method": "GET",
              "status": 200,
              "headerNames": [
                "accept",
                "accept-encoding",
                "accept-language",
                "sec-fetch-mode",
                "user-agent"
              ],
              "accept": "application/octet-stream",
              "acceptEncoding": "identity",
              "authorization": null,
              "cookie": null
            }
          ]
        },
        "shortLimit": "LIMIT:4",
        "shortStderr": "",
        "shortUnpack": {
          "ok": false,
          "name": "RunnerArchiveError",
          "code": "write-failed",
          "message": "Cannot write config.sh in staging: EFBIG: file too large, write",
          "parent": [],
          "requests": []
        },
        "shortUnpackLimit": "LIMIT:4",
        "shortUnpackStderr": "",
        "madeBig": {
          "ok": true,
          "bytes": 363,
          "requests": []
        },
        "destLeft": false,
        "unpackLeft": []
      }
    },
    {
      "name": "A valid package is unpacked into private staging and is not run",
      "pass": true,
      "detail": {
        "unpacked": {
          "ok": true,
          "dir": "puck-runner-48utA2",
          "versionText": "9.9.9\n",
          "nodeBody": "#!/bin/sh\necho SHOULD_NOT_RUN\n",
          "tree": [
            {
              "name": "LICENSE",
              "mode": "644",
              "bytes": 8
            },
            {
              "name": "README.md",
              "mode": "644",
              "bytes": 10
            },
            {
              "name": "VERSION",
              "mode": "644",
              "bytes": 6
            },
            {
              "name": "bin/",
              "mode": "755",
              "bytes": 0
            },
            {
              "name": "bin/node",
              "mode": "755",
              "bytes": 30
            },
            {
              "name": "bin/node.LICENSE",
              "mode": "644",
              "bytes": 17
            },
            {
              "name": "bin/puck-runner.cjs",
              "mode": "644",
              "bytes": 20
            },
            {
              "name": "config.sh",
              "mode": "755",
              "bytes": 15
            },
            {
              "name": "run.sh",
              "mode": "755",
              "bytes": 7
            },
            {
              "name": "svc.sh",
              "mode": "755",
              "bytes": 7
            }
          ],
          "parent": [
            "puck-runner-48utA2"
          ],
          "requests": []
        }
      }
    },
    {
      "name": "An archive that breaks the layout or names the wrong version is refused and leaves no staging",
      "pass": true,
      "detail": {
        "extra": {
          "ok": false,
          "name": "RunnerArchiveError",
          "code": "unexpected-entry",
          "message": "Entry \"zzz-extra\" is not part of a runner package.",
          "parent": [],
          "requests": []
        },
        "wrong": {
          "ok": false,
          "name": "RunnerArchiveError",
          "code": "version-mismatch",
          "message": "VERSION says \"9.9.9\", not 1.2.3.",
          "parent": [],
          "requests": []
        }
      }
    },
    {
      "name": "Staging is removed when the archive reader cannot create bin/",
      "pass": true,
      "untested": false,
      "detail": {
        "binResult": {
          "ok": false,
          "name": "RunnerArchiveError",
          "code": "write-failed",
          "message": "Cannot make a staging directory under ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/work/unpack/binfail: EPERM: operation not permitted, mkdir '~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/work/unpack/binfail/puck-runner-UEZ5H3/bin/'",
          "parent": [],
          "requests": []
        },
        "code": 0,
        "stderr": "",
        "stdout": "{\"ok\":false,\"name\":\"RunnerArchiveError\",\"code\":\"write-failed\",\"message\":\"Cannot make a staging directory under ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/work/unpack/binfail: EPERM: operation not permitted, mkdir '~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/work/unpack/binfail/puck-runner-UEZ5H3/bin/'\",\"parent\":[],\"requests\":[]}\n"
      }
    }
  ]
}
Evidence: Official download request
GET https://github.com/releases path /namikmesic/puck/releases/download/v0.1.0/puck-runner-macos-arm64-0.1.0.tar.gz → 404. Headers: accept=application/octet-stream, accept-encoding=identity, authorization=null, cookie=null. Dest file removed. A caller-supplied URL was refused before any request.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 10 of 11 scenarios driven live against the product
Scenario Result Live Evidence
A development server lists a runner package, and both the app and the runner read that listing ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/listing-response.json
A release listing over 4MiB is refused, and a larger unrelated server response is still read ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
A gzip-encoded release listing is still read, up to the byte bound ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
A development download saves the package from the server origin and checks its size and digest ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
A development download refuses a redirect, another origin, a URL with credentials, and a content encoding ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
An official download requests the constructed GitHub release URL and sends no credential headers ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
An official download follows a redirect only onto the release hosts ⏸️ untested no https://github.com/namikmesic/puck/releases/download/v0.1.0/puck-runner-macos-arm64-0.1.0.tar.gz answered 404 with no Location, and the repo has no published release, so the client never saw a redirec…
A short disk write fails the package download and the archive unpack and leaves nothing behind ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
A valid package is unpacked into private staging and is not run ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
An archive that breaks the layout or names the wrong version is refused and leaves no staging ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
Staging is removed when the archive reader cannot create bin/ ✅ pass live ~/.no-mistakes/evidence/01M3T7J68K53QPQYTJ22JQX08P/live-results.json
  • npm run build:server, then node .webpack/server/puck-server.js with PUCK_DEVELOPMENT=true and PUCK_RUNNER_DOWNLOADS set to a 9.9.9/puck-runner-linux-x64-9.9.9.tar.gz
  • GET /v1/runner/releases on that server, then the same listing read by the app's releases() inside Electron and by puck-runner ServerApi.releases()
  • a local server returning 4194305 listing bytes, and a larger non-listing body read by serverRequest and ServerApi.register
  • a gzip-encoded listing read by both releases() clients
  • downloadRunnerPackage under the development policy against the development server, a 302, a foreign origin, a URL with userinfo, and a Content-Encoding: gzip response
  • downloadRunnerPackage under the official policy against https://github.com/namikmesic/puck/releases/download/v0.1.0/puck-runner-macos-arm64-0.1.0.tar.gz, with outgoing headers taken from the undici request
  • ulimit -f 4 around an 8192-byte downloadRunnerPackage and unpackRunnerPackage
  • unpackRunnerPackage on a layout-matching archive, an archive with an extra entry, a version mismatch, and a sandbox-exec profile that denies creating bin/
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Namik Mesic and others added 11 commits October 1, 2026 00:15
…t package reader

Two shared Node modules under src/runner-release, importable by the
app's main process, the Puck server and the runner (never the renderer
or the daemon), with no consumer wired yet.

download.ts is the transport. readBoundedBody reads a response's exact
bytes under a bound checked against Content-Length before the read and
the running count during it, and refuses a content encoding.
verifySignedRunnerReleases decodes a listing's signed records (at most
16, each manifest at most 256 KiB once decoded, the listing at most
4 MiB) and hands every manifest's exact bytes to the verifier; nothing
re-serialised is verified. downloadRunnerPackage streams a package to a
private 0600 file: under the official policy the URL is built from the
repository's release prefix, the canonical version and the signed file
name, every redirect is checked by hand (at most five, https only,
exactly github.com and release-assets.githubusercontent.com, no
userinfo, fragment, port or control character, the github.com path
compared segment by segment), redirect bodies are cancelled, the
request carries no token, the signed byte count is enforced while
streaming under a 256 MiB cap with a 30 s header timeout per hop and a
15 minute whole-body deadline, and a short, long, mistimed, cancelled or
mis-hashed body cancels the response and removes the file. The
development policy is the caller's: http on the configured server's
origin, no redirect.

archive.ts reads a package into a fresh 0700 staging directory,
accepting only the packager's fixed layout (nine regular files and
bin/, exact modes): ustar headers with verified checksums and octal
fields, no links, devices, PAX or GNU entries, no absolute or
traversing paths, no setuid, setgid or sticky bits, each member once,
a two-block trailer and nothing after it, a 512 MiB expansion cap, and
VERSION equal to the expected version. Failures remove staging. System
tar is never run, and the lint boundary now allows the built-ins these
modules need while keeping child_process out of the directory.

The harness gains the pure shapes: the release tag, the one mapping
from Node's platform names to the manifest's (the runner's
currentPlatform now delegates to it), the package layout, and the
signed record with strict canonical base64 and its bounds. The app's
and the runner's release listing readers now read through the bounded
transport; other server requests are unchanged.
…ger at the shared layout

readBoundedBody now cancels the response when it refuses a content
encoding or an unreadable Content-Length, as the rest of the transport
does. The packager's header names RUNNER_PACKAGE_ENTRIES as the one
place its entry list must match.
A file handle's write may persist only a prefix without throwing, so a
download or an unpack could report a verified package while the file on
disk was shorter than the bytes hashed. writeAll loops until every byte
of a chunk is written and treats a write of nothing as write-failed, at
both sites, with the existing cleanup.
zlib's gunzip joins concatenated members and ignores trailing NULs, and
the tar reader only ever saw inflated bytes, so a package followed by
an empty member, preceded by one, split across two members, or followed
by a NUL and garbage all passed. The reader now frames the gzip member
itself: a fixed ten-byte header with no optional fields, one raw deflate
stream fed only the bytes before the eight-byte trailer and required to
consume all of them, and a trailer whose CRC-32 and size must match what
was inflated. The streaming inflation cap is unchanged. Tests cover each
of those inputs, trailer and header mutations, and that staging is
removed.
…ackage header

uname, gname, devmajor and devminor (bytes 265 to 344) were skipped, so
a header with bytes after an owner name's terminator or with 0xff in a
device field passed once its checksum was corrected. The owner names are
now read like every other text field, and the device numbers must be
NUL, as the packager writes them. Tests mutate each of the four fields
with a corrected checksum and assert rejection with empty staging.
…during download

The download suite's short-write test claimed to cover unpacking but
only called downloadRunnerPackage. The archive suite now injects a file
handle that persists half a write, or nothing, and asserts
RunnerArchiveError write-failed with the staging parent left empty; the
download test's title says what it covers.
… work has settled

On a stream failure the reader destroyed its three streams and rejected
at once, so the staging directory was removed while an entry's open
could still be in the threadpool; that create could land between the
removal's readdir and rmdir, the removal would throw ENOTEMPTY (an
error that is not a RunnerArchiveError) and the directory survived. The
sink now tracks the chunk it is consuming, its destruction waits for
that work and closes the file it left open, an open that completes
after destruction is closed at once, the rejection waits for the sink's
'close', and the removal retries. A test fails the read stream the
moment the first entry's open begins and asserts that open completed
and closed before the caller heard of the failure, with staging gone.
Flipping a byte near the end of an archive whose runtime payload was
random could end the raw deflate stream early and leave bytes before
the trailer, which the reader correctly rejects as trailing-data, but
the assertion accepted only corrupt or truncated, so the case failed
intermittently. The payload is now fixed pseudo-random bytes from a
seeded xorshift, and the assertion accepts each of the three correct
rejections; staging must still be gone.
@namikmesic
namikmesic force-pushed the fm/runner-distribution-bounded-transport-an-92 branch from 6d5c97a to 82e9ed5 Compare September 30, 2026 23:10
@namikmesic
namikmesic merged commit 1f6e06e into main Sep 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant