Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ jobs:
cache-provider: basic
- name: Test, lint, and assemble debug and release
run: ./gradlew --no-daemon test lint assembleDebug assembleRelease
- name: Verify release JNI field names
run: ./scripts/verify-release-jni-contract.sh
- name: Verify release shrinker contracts
run: ./scripts/verify-release-shrinker-contracts.sh
- name: Install emulator system image
run: sdkmanager "emulator" "system-images;android-35;google_apis;x86_64"
- name: Create emulator
Expand Down
10 changes: 8 additions & 2 deletions PROJECT_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -1082,8 +1082,14 @@ authentication and default diagnostic checks subsequently passed on the
physical release artifact after force-stop/reopen. Preserving that encrypted
key through the next signed version update was then proven when permanent-key
alpha.4 installed over alpha.3 with local state intact and authenticated with
the retained key without re-import. Alpha distribution is direct invited
sharing while Phase 5 is open. Because the repository is public, any published
the retained key without re-import. Permanent-key alpha.5 then proved the
resumable signing and in-place installation path, but a release-only R8
relocation broke sshlib 0.4.2's bundled Ed25519 provider before authentication.
Alpha.5 is rejected. The alpha.6 source applies the narrow provider keep rule,
passes an isolated minified SSH probe, and adds a mapping/DEX release regression
gate. It still needs independent CI, permanent signing, and physical update
acceptance. Alpha distribution is direct invited sharing while Phase 5 is open.
Because the repository is public, any published
GitHub prerelease would be public. Sufficient invited technical-alpha use is the
remaining Phase 5 boundary.

Expand Down
32 changes: 22 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,22 @@ like responses, while a real terminal remains underneath for interactive work.
## Alpha status

**[Phase 5: Alpha polish](docs/STATUS.md) is in progress.** Its feature slices
and owner-device acceptance through alpha.4 are complete. The remaining product
boundary is enough invited technical-alpha use to evaluate whether ten real
users can complete small remote tasks for two weeks and provide useful feedback.

The current accepted signed build is `0.1.0-alpha.4`; the next source candidate
is `0.1.0-alpha.5`. Signed builds are shared directly with invited testers.
and owner-device acceptance through alpha.4 are complete. Alpha.5 installed in
place but is rejected because a new release-shrinker failure prevents SSH
connection. The alpha.6 source correction now passes the minified SSH proof and
release regression gate; it still needs independent CI, permanent signing, and
physical update acceptance. The remaining product boundary is enough invited
technical-alpha use to evaluate whether ten real users can complete small
remote tasks for two weeks and provide useful feedback.

The current accepted signed build is `0.1.0-alpha.4`; `0.1.0-alpha.5` is an
immutable rejected artifact, and `0.1.0-alpha.6` is the current corrected source
candidate. Signed builds are shared directly with invited testers.
There is no public signed APK or GitHub prerelease while Phase 5 is open.
Testers should obtain the APK and its checksum from the owner through the agreed
private channel, then follow the
[technical-alpha guide](docs/alpha-testing.md).
Alpha.5 must pass the signed update and physical-device regression path before
it replaces alpha.4 for testers.
Do not distribute alpha.5.

Threadline has no supported production release. Do not use the alpha for
privileged or sensitive systems.
Expand Down Expand Up @@ -287,8 +291,16 @@ artifact. Public CI now produces source-identified unsigned candidates while the
permanent signing key remains local. The permanent-key `0.1.0-alpha.4` artifact
installed over alpha.3 in place, retained onboarding, profiles, trusted hosts,
transcripts, settings, and the encrypted saved key, then authenticated with that
key without re-import. Alpha.5 signed-update acceptance is the next operational
check; invited technical-alpha use remains the product-validation boundary.
key without re-import. The permanent-key alpha.5 artifact was selected through
the resumable local signing path, verified against the established certificate,
and installed over the existing physical release in place. Its first connection
attempt exposed a release-only cbssh Ed25519 provider failure caused by R8
relocation. Alpha.5 is rejected; an isolated minified probe reproduces the
failure and proves the narrow keep-rule correction intended for alpha.6.
The alpha.6 source applies that narrow rule and makes the release gate verify
the exact provider class names in both the R8 mapping and assembled DEX. Its
isolated minified password SSH proof passes. Independent CI, permanent signing,
and physical acceptance remain before invited technical-alpha use resumes.

Use these records according to their purpose:

Expand Down
8 changes: 8 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,11 @@
-keepclassmembers class org.connectbot.terminal.ScreenCell {
<fields>;
}

# cbssh 0.4.2 registers these bundled JCA implementations by a class name
# derived from Ed25519Provider's runtime package. R8 must preserve the provider
# package and both name-loaded implementations or host-key verification fails
# before authentication.
-keep class org.connectbot.sshlib.crypto.ed25519.Ed25519Provider { *; }
-keep class org.connectbot.sshlib.crypto.ed25519.Ed25519KeyFactory { *; }
-keep class org.connectbot.sshlib.crypto.ed25519.Ed25519KeyPairGenerator { *; }
6 changes: 6 additions & 0 deletions docs/HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ onboarding state, profile, trusted host, transcript history, settings, and encry
survived, and the retained key authenticated and completed `pwd` without re-import. This closed the
installed-update preservation boundary. Direct invited sharing was selected for the open Phase 5
alpha, with no public prerelease until the remaining technical-alpha use boundary is evaluated.
Alpha.5 then proved the resumable signing and in-place installation path but is
rejected because R8 relocation broke sshlib 0.4.2's bundled Ed25519 provider
before authentication. An isolated minified probe reproduced the failure and
proved the narrow keep-rule correction. The alpha.6 source now applies that rule
and adds a release mapping/DEX gate for the provider class names; permanent-key
and physical acceptance remain.
Additional device and OEM coverage is opportunistic alpha evidence rather than a separate Pixel
gate. See
[STATUS.md](STATUS.md) rather than this chronology for the active boundary.
38 changes: 29 additions & 9 deletions docs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,39 @@ authenticated without re-import and completed `pwd`. This closes the installed
update-preservation boundary. See the
[alpha.4 update-preservation investigation](investigations/2026-08-09-alpha4-update-preservation.md).

The next source candidate is `0.1.0-alpha.5` (`10005`). Its pre-invite
The alpha.5 source candidate is `0.1.0-alpha.5` (`10005`). Its pre-invite
hardening pins GitHub Actions to immutable commits, validates the Gradle wrapper
and distribution checksum, enforces dependency checksums, verifies 16 KiB APK
alignment after signing, upgrades ConnectBot `sshlib` to 0.4.2, and adds the
core API 35 instrumented suite to CI. Alpha.4 remains the latest accepted signed
artifact until alpha.5 passes the signed update and physical-device regression
path.
core API 35 instrumented suite to CI. The new local wrapper selected the exact
successful `main` candidate, signed it with the permanent key, and produced an
alpha.5 artifact whose checksum, identity, certificate, v2/v3 signatures, and
16 KiB alignment verify. Android accepted it as an in-place update over the
existing Galaxy S25 Ultra installation, but the installed release could not
connect. An isolated minified API 35 probe reproduced a pre-authentication
`NullPointerException`: sshlib 0.4.2 constructs its bundled Ed25519 provider,
while R8 had moved that provider into the default package and broken its runtime
package lookup. A narrow provider keep rule made the same minified probe pass
authentication, PTY creation, and shell startup. Alpha.5 is rejected and alpha.4
remains the latest accepted signed artifact. See the
[alpha.5 signing and release-shrinker investigation](investigations/2026-08-10-alpha5-signing-update-progress.md).

The current corrected source is `0.1.0-alpha.6` (`10006`). It preserves the
three cbssh Ed25519 JCA classes whose binary names are part of the provider
contract, and the renamed release verifier now requires those exact names in
both the R8 mapping and assembled DEX alongside the existing termlib JNI field
checks. The gate rejects the pre-fix alpha.5 output and accepts alpha.6. An
isolated minified API 35 probe with the exact production rule completed password
authentication, PTY creation, and shell startup. The full local JVM, lint,
debug, release, connected Android, password fixture, and encrypted-key fixture
paths pass. See the
[alpha.6 Ed25519 shrinker correction](investigations/2026-08-10-alpha6-ed25519-shrinker-correction.md).

## Remaining Phase 5 boundaries

- Alpha.5 signed-update acceptance on the physical device: retained local
state, password and imported-key authentication, and the structured/raw
same-session path.
- Merge alpha.6 and pass independent CI, then sign its exact `main` candidate
and repeat physical update, retained-state, authentication, Diagnostics, and
same-session structured/raw acceptance.
- Technical-alpha use sufficient to evaluate the Phase 5 exit criterion.

## Alpha distribution: direct invited sharing, decided 2026-08-09
Expand All @@ -106,12 +126,12 @@ position.
Two things worth recording so this isn't relitigated from memory:

- **The signing question is separate and already settled.** The permanent update
lineage was established at alpha.1 and verified again at alpha.4 against
lineage was established at alpha.1 and verified again at alpha.5 against
certificate SHA-256 `102893bc…`, with the release key never entering CI. That
holds under either distribution choice, so it argues for neither.
- **The transparency half of the public option is already done.** Checksums, the
certificate fingerprint, signature schemes and 16 KiB alignment are published
in `investigations/2026-08-09-alpha4-update-preservation.md`, in a public
in `investigations/2026-08-10-alpha5-signing-update-progress.md`, in a public
repository. Only the signed tester APK stays private.

The asymmetry closes it: private can become public later, published can't become
Expand Down
20 changes: 20 additions & 0 deletions docs/development/release-signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,26 @@ The update retained the release app's onboarding state, profile, trusted host,
transcript history, settings, and encrypted saved key. That retained key then
authenticated without re-import and completed a structured command.

Permanent-key alpha.5 uses the same certificate. Its verified APK SHA-256 is:

```text
2c238b48c14c3933ab3dcd468352370b0fb1e0d5a20476c10e6fe39b7d38d8bf
```

The artifact identifies as `io.github.r055le.threadline`, version
`0.1.0-alpha.5` (`10005`), verifies with APK signature schemes v2 and v3, and
is 16 KiB page aligned. It was selected from the exact successful `main` build
by `scripts/sign-latest-alpha.sh` and Android accepted it as an in-place update
over the existing physical release. Its first connection regression exposed a
release-only cbssh Ed25519 provider failure after R8 relocation, so alpha.5 is
rejected and must not be distributed or overwritten. Preserve it as immutable
evidence and advance the corrected build to alpha.6.

The current corrected source is `0.1.0-alpha.6` (`10006`). Its release rules
preserve cbssh's name-loaded Ed25519 provider classes, and the release gate
checks their exact binary names in both the R8 mapping and assembled DEX. Its
permanent-key artifact and physical update evidence do not exist yet.

## Signing-key boundary

The long-lived release keystore and its passwords must never enter this
Expand Down
85 changes: 85 additions & 0 deletions docs/investigations/2026-08-10-alpha5-signing-update-progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Alpha.5 signing and release-shrinker rejection

**Date:** 2026-08-10
**Phase:** 5, alpha polish
**Status:** Rejected

## Boundary

Alpha.5 is the first permanent-key candidate produced through the resumable
local signing path. The public candidate matched current `main`, the signed APK
belonged to the established update lineage, and Android accepted it over
alpha.4. Its first connection regression then exposed a release-only shrinker
failure introduced by the sshlib 0.4.2 path. Alpha.5 is rejected.

## Candidate and signing record

GitHub Actions produced the unsigned alpha.5 candidate from source commit
`66a743f6a89c4a8dce7846d6411e0febf77c1caa`. The local wrapper selected the
successful Android push run for that exact commit, downloaded its uniquely
named candidate, and passed it to the existing signing verifier. The permanent
release key and passwords remained local.

The locally signed artifact independently verifies as:

- package: `io.github.r055le.threadline`
- version: `0.1.0-alpha.5` (`10005`)
- APK SHA-256: `2c238b48c14c3933ab3dcd468352370b0fb1e0d5a20476c10e6fe39b7d38d8bf`
- signing certificate SHA-256: `102893bcc2fa4b70fb451661579c717c6c2b917296a99baefa6d9e9d1d13e7fc`
- signer subject: `CN=Threadline`
- signature schemes: APK v2 and v3 verified
- alignment: 16 KiB page-aware zip alignment verified

The certificate matches the permanent update lineage established by alpha.1.

## Physical evidence so far

The owner installed alpha.5 over the existing Threadline release on the Galaxy
S25 Ultra. Android accepted it as an in-place update rather than a second app.
This proves the application ID, version progression, and signing lineage are
compatible on the physical device.

Google Play Protect offered to scan this APK. That prompt had not appeared for
the earlier Threadline alpha installs. This is recorded as a platform behavior
observation only. It is not evidence that Google approved, rejected, or
independently established the safety of the APK.

The owner then found that the installed alpha.5 could not connect. This stopped
the acceptance run before authentication, structured commands, or raw-terminal
behavior could be evaluated.

## Release-only reproduction and root cause

The production JVM adapter and the API 35 debug Android fixture suite continued
to pass with sshlib 0.4.2. An isolated, manifest-declared probe inside the exact
minified release path reproduced the physical failure on API 35 before
authentication. The adapter reported a generic connection failure caused by a
`NullPointerException` during Ed25519 host-signature verification.

Retracing the optimized stack located the failure in cbssh's bundled
`Ed25519Provider.setup()`. sshlib 0.4.2 now constructs this fallback provider
while converting the Ed25519 host key. The provider derives its implementation
package through `Ed25519Provider::class.java.package.name`; R8 had moved the
class into the default package, so that lookup returned null. Alpha.4 used
sshlib 0.4.1 and did not exercise this new path.

A narrow temporary keep rule for cbssh's Ed25519 provider package was tested in
the same minified probe. The corrected build reached authentication, created a
PTY, started the shell, and reported success. This proves the correction shape,
and the follow-on source correction advances as alpha.6. It is recorded in the
[alpha.6 Ed25519 shrinker investigation](2026-08-10-alpha6-ed25519-shrinker-correction.md).

## Required correction

Do not distribute alpha.5 or overwrite its immutable artifact. Alpha.6 now
preserves the cbssh Ed25519 provider classes needed by their JCA name-based
registration and adds a release gate that fails the broken mapping and DEX.
After merge and permanent signing, repeat:

- a minified password and imported-key fixture proof;
- permanent-key signing and installation over alpha.5;
- retained-state and Diagnostics checks; and
- the structured/raw same-session regression on the physical device.

Alpha.4 remains the latest accepted tester build. No password, passphrase,
private key, private endpoint, or fixture identity is recorded here.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Alpha.6 Ed25519 shrinker correction

**Date:** 2026-08-10
**Phase:** 5, alpha polish
**Status:** Source correction accepted; permanent artifact pending

## Boundary

Alpha.5 was rejected after its installed release failed before authentication.
The [alpha.5 investigation](2026-08-10-alpha5-signing-update-progress.md)
records the physical report, isolated minified reproduction, retraced stack,
and cbssh Ed25519 provider root cause. Alpha.6 must preserve that provider's
name-loaded JCA classes and make the release gate reject the broken R8 output.

## Correction

The source advances to `0.1.0-alpha.6` (`10006`). Its R8 rules preserve only
the three cbssh classes required by this contract:

- `Ed25519Provider`, whose runtime package supplies the registered class-name
prefix;
- `Ed25519KeyFactory`, which JCA loads by that registered name; and
- `Ed25519KeyPairGenerator`, which JCA also loads by name.

The release verifier is now named `verify-release-shrinker-contracts.sh`
because it owns both the existing termlib JNI field-name checks and this JCA
class-identity check. For all three provider classes, it requires the R8
mapping to retain the exact binary name and the assembled release DEX to contain
that exact class.

The new verifier rejected the pre-fix alpha.5 release output because
`Ed25519Provider` was renamed. After the narrow keep rule and version bump, the
alpha.6 release mapping and DEX passed the same gate.

## Release-shaped evidence

An isolated diagnostic clone added a manifest-declared probe directly to a
separately identified, debug-key-signed minified release. The probe used the
production adapter and the local OpenSSH fixture. No diagnostic activity or
alternate application ID enters the product source.

With the exact three-class production rule, the minified probe completed
Ed25519 host-signature verification, password authentication, PTY creation, and
shell startup on API 35. The temporary emulator was stopped without saving a
snapshot, and the fixture password was passed only through process memory.

Repository verification then passed:

- JVM tests, Android lint, debug assembly, and minified release assembly;
- all 69 connected Android tests, with the three credential-gated profiles
skipped by the ordinary runner as designed;
- the production JVM fixture adapter;
- both explicit Android password and encrypted imported-key fixture tests; and
- the new release shrinker-contract verifier.

The first explicit Android fixture run immediately after the heavier connected
suite reached SSH but timed out on its later Ctrl-C completion case. An
immediate isolated rerun passed both credential-gated tests. No product change
was made for that one load-sensitive timeout.

## Remaining acceptance

Alpha.6 still needs to merge and pass the independent GitHub gate. The resulting
exact `main` candidate must then be signed with the permanent key and installed
over alpha.5. On the physical device, repeat retained-state, password and
imported-key authentication, Diagnostics, structured command, and same-session
raw-terminal checks before sharing it with invited testers.

Alpha.4 remains the latest accepted tester build. No password, passphrase,
private key, private endpoint, or fixture identity is recorded here.
Loading