Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
8d7c700
feat(web): clone repositories in the background instead of holding th…
juliusmarminge Sep 14, 2026
549d182
feat(mobile): clone repositories in the background and gate the draft…
juliusmarminge Sep 14, 2026
9d4bb55
fix(mobile): scale inline pills with Dynamic Type (#11792)
juliusmarminge Sep 14, 2026
bcc2024
chore(deps): bump the Clerk stack to current releases (#11764)
juliusmarminge Sep 14, 2026
0f21fcb
feat(mobile): add a T3 Connect page to the Clerk profile (#11765)
juliusmarminge Sep 14, 2026
dc0869b
feat(server): use Clerk's device authorization grant for headless con…
juliusmarminge Sep 14, 2026
8419238
Add new GitHub user f-trycua
juliusmarminge Sep 14, 2026
7931227
fix(server): stop refreshing providers on every config subscription (…
juliusmarminge Sep 14, 2026
5bf43c9
fix(web): align monogram project icons in menus (#11806)
juliusmarminge Sep 14, 2026
9a49d6d
ci(desktop): sign fork PR macOS previews without exposing signing sec…
juliusmarminge Sep 14, 2026
014016a
fix(web): make copy PR link discoverable in keybindings (#11826)
Bil0000 Sep 15, 2026
3be02ae
feat: add custom snooze dates and durations (#11800)
juliusmarminge Sep 15, 2026
ea6af59
feat(mobile): redesign the Android agent activity card (#11645)
SunkenInTime Sep 15, 2026
6dbea7e
chore(mobile): bump app version to 1.2.0
t3-code[bot] Sep 15, 2026
5ea6439
feat(web): inline worktree setup rows and async setup scripts (#11832)
juliusmarminge Sep 15, 2026
b5b29e7
fix(server): stream tight list items one at a time in paragraph mode …
juliusmarminge Sep 15, 2026
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
1 change: 1 addition & 0 deletions .github/VOUCHED.td
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ github:D3OXY
github:dbalders
github:eggfriedrice24
github:extoci
github:f-trycua
github:flamboh
github:FllipEis
github:gbarros-dev
Expand Down
76 changes: 76 additions & 0 deletions .github/scripts/stage-preview-bundle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Stage an untrusted preview ZIP without letting it replace packaging code."""

import shutil
import stat
import sys
import zipfile
from pathlib import Path

ROOTS = ("server/dist", "desktop/dist-electron")
REQUIRED_FILES = {
"server/dist/bin.mjs",
"server/dist/client/index.html",
"desktop/dist-electron/main.cjs",
}
# The current bundle is about 32 MiB compressed. Bound extraction on the
# trusted runner even when the PR replaces the uploader entirely.
MAX_ARCHIVE_BYTES = 512 * 1024 * 1024
MAX_EXPANDED_BYTES = 2 * 1024 * 1024 * 1024
MAX_ENTRIES = 50_000


def stage_bundle(archive: Path, destination: Path):
if archive.stat().st_size > MAX_ARCHIVE_BYTES:
raise ValueError("Preview archive is too large")
with zipfile.ZipFile(archive) as bundle:
entries = bundle.infolist()
if len(entries) > MAX_ENTRIES:
raise ValueError("Preview archive has too many entries")
if sum(entry.file_size for entry in entries) > MAX_EXPANDED_BYTES:
raise ValueError("Expanded preview bundle is too large")
seen = set()
files = set()
for entry in entries:
name = entry.filename.removesuffix("/")
parts = name.split("/")
# Reject ambiguous paths before normalization, including names
# that would alias on the macOS signing runner.
if (
entry.orig_filename != entry.filename
or any(part in ("", ".", "..") for part in parts)
or any(char in name for char in "\\:")
or not name.isascii()
or any(ord(char) < 32 or ord(char) == 127 for char in name)
):
raise ValueError(f"Unsafe preview path: {entry.filename!r}")
allowed = any(name.startswith(root + "/") for root in ROOTS)
if entry.is_dir():
allowed |= any(root == name or root.startswith(name + "/") for root in ROOTS)
if not allowed:
raise ValueError(f"Unexpected preview path: {name!r}")
kind = stat.S_IFMT(entry.external_attr >> 16)
if kind not in (0, stat.S_IFDIR if entry.is_dir() else stat.S_IFREG):
raise ValueError(f"Non-regular preview entry: {name!r}")
if name.casefold() in seen:
raise ValueError(f"Duplicate preview path: {name!r}")
seen.add(name.casefold())
if not entry.is_dir():
files.add(name)
if not REQUIRED_FILES <= files:
raise ValueError("Preview bundle is missing required entry points")
# Validate all names before writing anything. This is a fresh directory
# outside the checkout; neither pre-existing links nor trusted files
# can be followed or overwritten. ZIP permissions are never restored.
destination.mkdir(parents=True, exist_ok=False)
for entry in entries:
target = destination / entry.filename
if entry.is_dir():
target.mkdir(parents=True, exist_ok=True)
else:
target.parent.mkdir(parents=True, exist_ok=True)
with bundle.open(entry) as source, target.open("xb") as output:
shutil.copyfileobj(source, output)


if __name__ == "__main__":
stage_bundle(Path(sys.argv[1]), Path(sys.argv[2]))
97 changes: 97 additions & 0 deletions .github/scripts/stage-preview-bundle.test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import importlib.util
import stat
import tempfile
import unittest
import zipfile
from pathlib import Path
from unittest.mock import patch

spec = importlib.util.spec_from_file_location(
"stage_preview_bundle", Path(__file__).with_name("stage-preview-bundle.py")
)
staging = importlib.util.module_from_spec(spec)
spec.loader.exec_module(staging)


class StagePreviewBundleTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.archive = self.root / "bundle.zip"
self.destination = self.root / "staged"

def bundle(self, extra=(), missing=None):
with zipfile.ZipFile(self.archive, "w") as bundle:
for name in sorted(staging.REQUIRED_FILES - {missing}):
bundle.writestr(name, b"bundle data, never executed")
for name, content in extra:
bundle.writestr(name, content)

def stage(self):
staging.stage_bundle(self.archive, self.destination)

def test_preserves_valid_bundle_layout_and_bytes(self):
self.bundle([("server/", b""), ("server/dist/", b""),
("desktop/dist-electron/chunks/helper.cjs", b"chunk")])
self.stage()
for name in staging.REQUIRED_FILES:
self.assertEqual((self.destination / name).read_bytes(), b"bundle data, never executed")
self.assertEqual((self.destination / "desktop/dist-electron/chunks/helper.cjs").read_bytes(), b"chunk")

def test_rejects_builder_overwrite_and_unsafe_paths_before_writing(self):
for name in [
"desktop/node_modules/electron-builder/cli.js",
"desktop/package.json",
"server/dist/../../desktop/package.json",
"../package.json",
"/server/dist/absolute",
"server/dist/./alias",
"server/dist//alias",
"server/dist/back\\slash",
"server/dist/file:stream",
"server/dist/BIN.MJS",
]:
with self.subTest(name=name):
self.bundle([(name, b"untrusted")])
with self.assertRaises(ValueError):
self.stage()
self.assertFalse(self.destination.exists())

def test_rejects_links_and_special_files(self):
for mode in [stat.S_IFLNK, stat.S_IFIFO, stat.S_IFCHR]:
with self.subTest(mode=mode):
entry = zipfile.ZipInfo("server/dist/link")
entry.create_system = 3
entry.external_attr = (mode | 0o777) << 16
self.bundle([(entry, b"../../../desktop/node_modules")])
with self.assertRaises(ValueError):
self.stage()
self.assertFalse(self.destination.exists())

def test_requires_entry_points(self):
self.bundle(missing="desktop/dist-electron/main.cjs")
with self.assertRaises(ValueError):
self.stage()
self.assertFalse(self.destination.exists())

def test_bounds_archive_size_expanded_size_and_entry_count(self):
for limit in ["MAX_ARCHIVE_BYTES", "MAX_EXPANDED_BYTES", "MAX_ENTRIES"]:
with self.subTest(limit=limit), patch.object(staging, limit, 1):
self.bundle()
with self.assertRaises(ValueError):
self.stage()
self.assertFalse(self.destination.exists())

def test_refuses_existing_destination(self):
self.bundle()
self.destination.mkdir()
sentinel = self.destination / "trusted"
sentinel.write_text("untouched")
with self.assertRaises(FileExistsError):
self.stage()
self.assertEqual(sentinel.read_text(), "untouched")


if __name__ == "__main__":
unittest.main()
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ jobs:
sudo sed -i 's|http://|https://|g' /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config
- name: Test preview artifact validation
run: python3 -B .github/scripts/stage-preview-bundle.test.py

- name: Test nightly release checks
run: node --test .github/scripts/check-nightly-release.test.cjs

Expand Down
Loading
Loading