Releases: indiagrams/tunnelless
Release list
TailscaleKit.xcframework — tailscale.com v1.102.3
Prebuilt TailscaleKit.xcframework, built from tailscale.com v1.102.3.
Upstream libtailscale ships no prebuilt binary, so this is the artifact that
makes adoption a drag-and-drop rather than a Go toolchain setup.
Unsigned by design — your app signs it on embed. Set it to Embed & Sign.
| tailscale.com | v1.102.3 |
| slices | ios-arm64, ios-arm64_x86_64-simulator, macos-arm64 |
Validated before publishing: privacy manifests present in both iOS slices
(ITMS-91053), no symlinks in iOS slices, and no vendor team ID left in the
signature (tailscale#15802).
gh release download tailscalekit-v1.102.3+3 -p 'TailscaleKit.xcframework.zip' -D /tmp
unzip -q /tmp/TailscaleKit.xcframework.zip -d vendor/0.1.0
Added
-
tailscale/verify-floor-runtime.sh— proves a declared iOS floor by loading
it.ci/check-platform-floors.shcompares declared floors against the
minosa slice reports, but never loads anything, and every machine that
builds this repo runs an OS above every floor it declares — so the failure a
floor exists to prevent was invisible to every green check.The script builds the app for the simulator and launches it twice: once
carrying the pre-lowering framework from an earliertailscalekit-*release
(the negative control, which dyld must refuse) and once carrying the current
one. If the control loads, the run reports INCONCLUSIVE and discards the
subject's pass rather than reporting it.It refuses to run on a simulator at or above the old floor, because such a
runtime clears the old and new floors alike and cannot tell a lowered floor
from an unlowered one. Needs a runtime in[declared floor, previous floor);
not wired into CI, as GitHub's macOS images carry no old runtimes.First run on iOS 17.5 confirms the lowered floor: the app maps
TailscaleKit
and runs, while the same app carrying thetailscalekit-v1.102.3framework
dies withbuilt for iOS-sim 18.1 which is newer than running OS. The macOS
14.0 floor remains structural only — there is no macOS simulator, so it
needs a machine or VM on macOS 14–15.5.
Changed
-
Deployment floors are now iOS 17.0 / macOS 14.0, down from 18.1 / 15.6.
A new xcframework was published astailscalekit-v1.102.3+2— same tsnet
version, rebuilt with patch0003— and verified from the downloaded asset:
minos 17.0(ios-arm64, and the simulator slice) and14.0(macos-arm64).
Package.swift's URL, checksum and platform floors moved together;
app/project.ymlandapp/Project.swiftfollow.Reaches users with the next app build. The declarations changing does not
alter the0.1.0binaries currently in App Review.
Fixed
-
App Review rejection of macOS
0.1.0build 5 (2026-08-30), both findings. Guideline 2.1(a) — "the Sign in button was unresponsive". Not reproducible here: on a cold, signed build (Debug and Release, macOS 26.5.2)tailscale_startreturnsres=0,control: AuthURL is …does reach the app's log pipe,ASWebAuthenticationSession.start()returnstrueand the sheet presents. What the investigation did find is a latent bug that produces exactly the reported symptom:start()returnsBooland the result was discarded. On failure nothing opens, the completion handler never fires, and the continuation was never resumed — sopresentLogin()never returned, itsisPresentingLoginlatch stayed set, and theguard !isPresentingLoginat the top swallowed every later tap. The sign-in button goes permanently dead with no error shown anywhere.present(url:dismissWhen:)now returns a three-caseOutcome(completed/cancelled/failedToPresent(reason:)) instead of aBoolthat could not represent "the sheet never opened", resumes the continuation on every path, and the caller always clears the latch and shows the reason.WindowPresenternow prefers a key window, then any visible window, before the detachedNSWindow()last resort — a window that was never shown cannot host a sheet, which is one waystart()fails.presentLoginWhenURLAppearsno longer falls through silently after ~20s leaving the UI on "waiting for login…"; it says no sign-in link arrived and points at Demo mode. Guideline 1.5 — the Support URL washttps://github.com/indiagrams/tunnelless/issues, which Apple does not accept as a support site; nowhttps://indiagram.com/support.html(verified HTTP 200). Notefastlane/Fastfile'smetadata_diris platform-agnostic, so this URL is shared: the queued iOS build 5 carried the same rejected URL and would have failed 1.5 for the same reason. -
make shiprecorded almost nothing about the release it had just run. A real ship produced a 13-line log — every line printed bybin/ship.rbitself, none from the multi-minutefastlane releasethose lines described. Redirecting changed nothing: the output never left the Ruby process.Bootstrap::Sh.runwrapsOpen3.capture3, which buffers until exit and discards stderr, andship.rbthen dropped the captured string on success and printed stdout without stderr on failure — so a ship that FAILED was exactly as undiagnosable as one that succeeded, which is backwards. Diagnosing the v0.1.0+6 ship meant reconstructing events from fastlane'sreport.xmland the ASC API.Fixed upstream in apple-shipkit (#276) rather than here, because
bin/is template-owned andbin/ship.rbwas byte-identical to the template — a fork edit would conflict on every sync for a bug with nothing project-specific about it. Cherry-picked in as5bb986fso this repo gets it without the full 271-commit upstream merge, which would also touchfastlane/metadata/and could clobber the live App Review notes while both0.1.0submissions are queued with Apple.Adds
Bootstrap::Sh.stream(tees a child's merged stdout+stderr as it arrives viapopen2e; passeschdir:as a spawn option instead of mutating the process-global cwd for a whole release) andtest/sh_stream_test.rb— 10 stdlib-only assertions wired as thestream-regressionCI job, two of them controls asserting the old path fails what the new one passes. Mutation-verified: restoring thecapture3body fails 5 of the 10.
Removed
-
Module-cache Patch 1 (darwin
os.Executablefallback) is gone. It merged
upstream astailscale#19052and shipped in v1.98.0; the pin has been
v1.102.3since the last bump, so the block did nothing but print "already
applied" on every build — confirmed by readingcase "ios", "darwin":in
tsnet.goat the tag, not by trusting the release date. First item of v0.2's
"carry less" to actually land.It was kept in case the dependency were ever pinned below v1.98. That scenario
is now guarded explicitly:build-tailscalekit.shhard-fails on such a pin
with the reason and agit log -Spointer to recover the patch, instead of
lettingTailscaleNode.init()fail at runtime with "tsnet: cannot find
executable path". Boundary verified —v1.98.0accepted,v1.96.4rejected.
Changed
-
Deployment floors lowered to iOS 17.0 / macOS 14.0 (from 18.1 / 15.6) for
the next xcframework build.tailscale/patches/0003-gate-listener-api.patch
gates TailscaleKit's two listener actors behind@available, which confines
their iOS 18 / macOS 15 requirement to themselves instead of the whole
framework — this app is a pure client and never calls them. Every number was
measured by building at it: unpatched the real floor is 18.0/15.0, and
upstream's shipped 18.1/15.6 is above even that. Sent upstream as
libtailscale#60;
when it lands, delete the patch — do not rebase it.The floors in
app/andPackage.swiftdeliberately stay at 18.1/15.6 until a
new xcframework is published: they must describe the binary consumers actually
resolve, not the one we can now build.ci/check-platform-floors.shenforces
that ordering on its own.
Added
-
ci/check-app-icon.sh— the build now refuses a placeholder icon. Build 1
shipped apple-shipkit's flat blue template icon unchanged and came back as a
Guideline 2.3.8 rejection. Nothing objected on the way out: the asset was a
valid 1024x1024 PNG, all six CI cells were green, and App Store Connect's
upload validation accepted it. The only thing that noticed was a human
reviewer, a full cycle later.Placeholders are detected structurally, with no knowledge of the app's brand:
quantise to 8 colours, take the largest distance between clusters covering=3% of the image. Artwork puts distant clusters on the canvas; a flat fill or
a bare gradient does not. Measured — flat blue0, shipkit's template0,
the real icon282; the threshold is40. Also checks 1024x1024 and rejects
an alpha channel, which Apple refuses outright.Deliberately no Pillow: icons inside a built
.ipaare CgBI PNGs, which
Pillow refuses with "broken data stream" — so a Pillow implementation would
silently skip the artifact that matters most. Pixels come viasipsand a
stdlib BMP parse.ALLOW_PLACEHOLDER_ICON=trueoverrides.
Changed
- README's Upstream section brought current, and it now separates merged
from shipped —tailscale#20985was listed as "Open, approved" when it had
merged on 2026-08-26, and merging is not what retires the carried patch.
Addedlibtailscale#59(universal macOS slice, and why it is not what macOS
support needs) andtailscale#21005(down()callstailscale_up()), both
of which existed only in TAILSCALE.md. Every state re-verified against GitHub
rather than carried over.
Added
-
tailscale-upstream-watch.ymlnow decides patch retirement instead of
delegating it. It readstsnet/tsnet.goat the pinned and latest tags and
reports whether each containstailscale#20985, replacing a weekly issue that
said "verify by grepping the tagged source" and left the grepping to a human.
Four outcomes: actionable now, a bump would retire it, merged-but-unreleased
(nothing to do), and a fetch failure — which reportsUNKNOWNrather than
"not contained", because treating a failed fetch as absence is how a patch
step quietly outlives its purpose. Since today's real state is
merged-but-unreleased, the tracking issue now closes itself rather than
reopening every Monday. -
App Review notes now answer Guideline 5.1.1(v) (account deletion). Apple
raised it against macOS build 4 and no build can answer it: the app creates no
account, so there is nothing to add to the binary. The notes ...
TailscaleKit.xcframework — tailscale.com v1.102.3
Prebuilt TailscaleKit.xcframework, built from tailscale.com v1.102.3.
Upstream libtailscale ships no prebuilt binary, so this is the artifact that
makes adoption a drag-and-drop rather than a Go toolchain setup.
Unsigned by design — your app signs it on embed. Set it to Embed & Sign.
| tailscale.com | v1.102.3 |
| slices | ios-arm64, ios-arm64_x86_64-simulator, macos-arm64 |
Validated before publishing: privacy manifests present in both iOS slices
(ITMS-91053), no symlinks in iOS slices, and no vendor team ID left in the
signature (tailscale#15802).
gh release download tailscalekit-v1.102.3+2 -p 'TailscaleKit.xcframework.zip' -D /tmp
unzip -q /tmp/TailscaleKit.xcframework.zip -d vendor/TailscaleKit.xcframework — tailscale.com v1.102.3
Prebuilt TailscaleKit.xcframework, built from tailscale.com v1.102.3.
Upstream libtailscale ships no prebuilt binary — every consumer builds it themselves with a Go toolchain and Xcode. This release removes that step.
Use it
- Download
TailscaleKit.xcframework.zipand unzip - Drag
TailscaleKit.xcframeworkinto your Xcode project - Set it to Embed & Sign
- Copy
TailscaleNodeManager.swiftandWebAuthLogin.swift
let manager = TailscaleNodeManager(hostName: "my-app")
try await manager.startForBrowserLogin()
let lb = await manager.cachedLoopback // SOCKS5 on loopbackNo NEPacketTunnelProvider, no NetworkExtension entitlement, no VPN permission prompt.
Contents
| Slice | |
|---|---|
ios-arm64 |
device |
ios-arm64_x86_64-simulator |
Simulator |
macos-arm64 |
Apple Silicon Mac |
Passes all six checks in validate-xcframework.sh:
PrivacyInfo.xcprivacyin both iOS slices — Apple rejects with ITMS-91053 without it- no symlinks in iOS slices — App Store rejects those
- no vendor team ID in the signature (tailscale#15802)
- simulator slice present, root
Info.plistpresent
Unsigned by design. Your app signs it at embed time — that's why "Embed & Sign" matters.
Caveats
- Pinned to v1.102.3. For a different version, build from source:
bash tailscale/build-tailscalekit.sh - macOS slice is arm64 only (no Intel), and requires macOS 15.0+
- Reference implementation, not a supported library — no compatibility guarantees between releases
Licence
TailscaleKit and libtailscale are BSD-3-Clause, © Tailscale & AUTHORS. The licence is included in the archive as LICENSE-tailscale-BSD-3-Clause.txt.
SHA-256: 0b73d55c44174951693cf02abcdcb75f854931d95c61a1628bda550a7ab91bba