Harden Superuser architecture boundaries

WH
22d ago
+1220-56

1. Summary

Stacks on #8 (codex/superuser-mode-transitions-and-ui). Merge after #8.

The seven-file scope adds source guardrails, regression fixtures, debug validation, and developer documentation without changing production runtime code:

Makefile
.github/workflows/common-build.yaml
app/src/test/java/com/nutomic/syncthingandroid/runtime/RuntimeSeamTest.java
app/src/test/java/com/nutomic/syncthingandroid/service/SyncthingBackupArchiveTest.java
scripts/check_superuser_architecture.py
scripts/test_superuser_architecture.py
docs/superuser-architecture-guardrails.md

2. Evidence

  • Before: Red-first fixtures reproduced unauthorized preference writes, privileged interface expansion, backend bypasses, conditional import catch fallthrough, and Kotlin key-alias misses.
  • After: 39 architecture fixtures and the production scan pass. Focused archive, import-preference, and runtime JVM regressions pass. make test, make all, IS_COPILOT=true make all, and git diff --cached --check pass. The repository's allowlisted all validation passed on the staged revision (validation-2026-10-11T08-26-03-617Z-302cbc7c). Final local Cubic review found no issues; the formal delegation review passed all technical checks, including change_attribution.
  • Physical qualification: Magisk, KernelSU, APatch, and SELinux device testing NOT RUN.

3. Merge danger

Door: Two-way

Blast radius: Validation

The new checks run in make test and normal debug PR validation. Release and signing behavior are unchanged. This PR depends on #8 and should be merged only after its base branch is integrated.


Summary by cubic

Adds source-level architecture guardrails that prevent regressions of the Superuser Mode boundaries without changing production runtime code. A new Python checker scans production Java, Kotlin, AIDL, and Gradle files for forbidden patterns, backed by 39 positive and negative fixtures.

  • Debug CI and make test now run the checker, fixtures, and JVM unit tests; release and signing behavior is unchanged. The check rejects libsu imports outside the two root transport files, non-core libsu artifacts, generic privileged APIs, RootService/AIDL, process-name signals in execution arguments, mode-preference writes outside the approved owners, and import paths that mutate live state before validation aborts.
  • New JVM regressions verify a root backend launch failure never falls back to the App UID backend, malformed backup extraction leaves live config and identity files byte-for-byte intact, and a superseded device refresh never reaches the folder editor. The folder editor test releases its latches in a finally block so a failure cannot hang the suite.
  • Docs capture the enforced boundaries and the checker's deliberately bounded lexical scope.

This stacks on the superuser-mode-transitions-and-ui branch and should merge after that base is integrated.