Restore disable_configstore, EuiccDisabler and thermal CDEV guards, branding and rebuild/sync helpers; document device roles, laptop migration, and Gitea. Co-authored-by: Cursor <cursoragent@cursor.com>
10 KiB
Swissquote differential investigation — 2026-10-09
Outcome (same day, wave 3): Swissquote PASS in Island on LAB after Lineage tools removal — see
009-closure-parity-20261009.md. Body below is the pre-PASS forensics log only.
Status (investigation snapshot): failure classified before wave 3.
Live observations approximately 15:18–15:26 Europe/Berlin. Build/recovery
instructions in 009-handoff-20261008.md still apply. GOLDEN was read-only.
Confirmed new finding
Swissquote 9.11.0 stores this nonfatal exception on LAB, Island user 10:
com.swissquote.android.FreeRaspException
freeRASP: onPrivilegedAccess
com.swissquote.android.ThreatDetectedListenerKt$getThreatDetectedListener$1.onPrivilegedAccess
ThreatDetectedListener.kt:24
app.talsec.rasp.security.api.ThreatListener.onReceive
This was read from the app's existing local Crashlytics event JSON. Only
the exception classification and stack symbols were retained; no raw
Crashlytics report, account data, keys, or session identifiers enter Git.
scripts/009-read-swissquote-rasp.py --device LAB reproduces that limited,
read-only inspection. Retained events may be uploaded/removed by the app;
an empty result does not prove compatibility or that no callback occurred.
Static inspection of the installed APK established that -69 is an
application-defined RASP_FAILING_EXIT_CODE, shared by four handlers:
onPrivilegedAccess, onDebug, onAppIntegrity, and onHooks. Each records
a distinct FreeRaspException, requests the same warning, then calls
System.exit(-69). Consequently the exit code alone was not enough to
attribute the failure to root. The retained exception now identifies the
privileged-access branch for the observed LAB failure. It does not identify
which underlying native check fired or exclude additional failures later.
The APK contains Talsec v19_2_0_fullRelease markers and loads libts.so.
libsigner.so belongs to the separately present Adjust signer integration;
loading it is not evidence that it caused this exit.
The previously suppressed toast was recovered by temporarily granting POST_NOTIFICATIONS to LAB user 10, then revoking it in a finally block. Its text lists generic device-safety requirements (root, debugging, emulation, modification, malware, official store); it does not disclose the failing check. A later observation showed the permission granted with USER_SET, apparently from concurrent user activity; it was not overwritten.
Current comparison, not the earlier handoff inventory
- Both devices have Swissquote 9.11.0 / 91100003, target SDK 37, arm64, with Play Store as installer and initiating package.
- All three installed APKs match byte for byte:
- base:
734293c7b6a7ecf48117e23ffc72ad0a1e3279336025551faf23c1c3c239c8f2 - arm64 split:
6ce67d98a2a4dc4b1ac38a9b3c864e11167b17ba9ed62e8a9d69cf48cb3e090d - density split:
4e0ab62d8eb7c2faa959014552204c8378977459109b3ba2a0b400a87bb9c3a2
- base:
- Both now have Swissquote installed for user 0 and user 10. The earlier statement that GOLDEN has it only for user 10 is obsolete.
- Island matches:
6.4.2-d46911c9, versionCode 51801. - GOLDEN: Android 15 / SDK 35, Magisk 29.0. LAB: Android 16 / SDK 36, SDK_FULL 36.1, Magisk 30.7, deblob4 slot b.
- Both are user/release-keys with
ro.debuggable=0, SELinux Enforcing, Zygisk enabled and DenyList enforced. Both list the main Swissquote process and its_zygoteentry. LAB logs the DenyList hit for user 10. - Both expose
verifiedbootstate=orange,vbmeta.device_state=unlocked,flash.locked=0, andveritymode=enforcingat this measurement. Both protect/proc/cmdlinewith mode 0440. - GOLDEN has only hosts and flag-secure-disabler modules installed. LAB has hosts plus PIF and TEESimulator directories; both latter modules have disable markers. Directory presence is not active-module proof.
- Owner confirmed GOLDEN Swissquote still opens in Island in this state. Its existing user-10 process was alive during read-only inspection.
- Shell developer/ADB settings are enabled on both. No enabled accessibility service was returned for user 10 on either device.
This is strong evidence against treating spoofed green/locked properties, PIF, or TEESimulator as prerequisites for the observed GOLDEN success. It does not prove that every path through either banking app is independent of attestation.
Process-level observations and controlled experiment
Read /proc/<Swissquote PID>/mountinfo and selected maps using root, without
attaching a debugger or modifying the banking process. Both mount tables
lacked obvious Magisk/module mounts; neither selected maps output contained
Magisk/Zygisk/LSPosed libraries. The injected /system_ext/bin/magisk was
absent beneath each inspected process's /proc/<PID>/root view even though
it exists in the root shell's view. This supports actual unmounting, beyond
merely checking DenyList configuration. It does not prove absence of
all detectable root signals, sockets, policy changes, or runtime effects.
Important real ROM difference: GOLDEN has no Yama ptrace_scope node; LAB
has Yama enabled and scope 2. The local apply_009_yama_ptrace.py already
documents that the AXP init rule was inert on GOLDEN's kernel.
At 15:22, changed only LAB's transient ptrace_scope from 2 to 0, cold
started Swissquote in user 10, and observed System.exit(-69) again.
Restored scope 2, with readback. No persistent property was changed.
Thus relaxing ptrace_scope alone does not fix the observed failure; this
is not a blanket clearance of every kernel-hardening difference.
Why GOLDEN passes: what is known and what remains open
GOLDEN's actual process environment satisfies Swissquote's startup checks despite unlocked/orange state, Magisk, and USB debugging. LAB triggers the privileged-access callback despite working basic DenyList unmounting. The APK and Island version are controlled; the operating system, kernel, SELinux policy and Magisk implementation are not controlled yet.
Leading branches for investigation, not established causes:
- A Magisk 29 → 30.7 difference leaves a root-detection surface after unmounting. Upstream changed file injection, mounting and sepolicy-related handling; simply seeing the same DenyList checkboxes cannot exclude this.
- A ROM/kernel/SELinux difference is interpreted as privileged access by Talsec on Android 16. Yama scope alone has been tested unsuccessfully.
- App-visible package/file/policy differences or retained per-install state. Package listings from shell are not automatically app-visible listings.
Do not claim “Magisk 30.7 is broken” from upstream issue 9866: that report had Enforce DenyList OFF, unlike this LAB, and was closed not planned. Do not infer a specific Talsec false positive from unrelated hardware- attestation reports. Do not mask callbacks, patch Swissquote, or transfer GOLDEN banking data as a diagnostic shortcut.
Prepared next discriminator — same build, without Magisk
The current artifact directory is:
/srv/rom/artifacts/009-axp-los23-port/20261009T120025Z-axp-stage1-combo4-cve-hi-releasekeys/
Unpacked both boot images with the host's magiskboot. Verified:
boot.img: SHA256e5822d8e8cbb39fe74ca638cdfcd52cd66fc741d130bdf962f433ae4f16299d2; ramdiskcpio test= 1, Magisk patched.boot-make-out.img: SHA2566caceaca1e911d327e7ad86217cc6bedb8cd32fe690cadd11c9cedeed0440d1b; ramdiskcpio test= 0, stock/unpatched.- The extracted kernel SHA256 is identical in both:
e093a3c7edfb2560f109c0b3df68d2722a1e05f06357316d1278b8c0348e8f89.
This prepares a temporary LAB-only boot experiment, subject to owner
agreement and confirming the phone is still on this same build. No image
has been booted or flashed in this investigation. Preserve data, vendor SPL,
the active slot and the installed APK; do not reinstall/reset the app.
Prefer booting the image temporarily if the bootloader supports that path.
The stock image has not yet been boot-tested; a verified hash is not boot
acceptance. Confirm actual absence of running Magisk after boot before
interpreting the Swissquote result; the manager APK and historical /data
files can remain, so this is not a clean factory/rootless baseline.
- If it starts, focus on active Magisk integration; consider a compatible, isolated comparison against GOLDEN's version, not a blind ship downgrade.
- If it still exits, reclassify the failure before investigating ROM-visible surfaces. Rootless boot failure alone would not prove a Talsec bug.
- Return to the installed boot and repeat the original failure for an A/B/A comparison. A process surviving briefly is not a usable-session PASS.
LAB became unavailable in ADB during investigation. On the next turn the owner confirmed it had been reconnected and authorized the remaining tests. USB enumeration then confirmed LAB was physically present at 3-5.3 as 18d1:4ee1 (MTP only), while GOLDEN was 18d1:4ee2 (MTP + debug). The earlier user-facing assertion that LAB was disconnected was too broad: USB was present, but the ADB interface was unavailable. Requested that the owner enable USB debugging on LAB; do not mistake GOLDEN's ADB presence for LAB.
Sources and audit trail
- Project:
009-handoff-20261008.md, constitution, feature 009 spec, Magisk runbook, current tracked diff and new patch scripts. Relevant recent changes include user/release signing, AXP identity and vendor-SPL guard, user-build signature spoofing, cmdline permissions, AVB flags 0, malloc 2-GiB regions, kernel ports/forward-only patching, Yama, USERFAULTFD off, Settings/privacy ports and two deblob waves. No existing work was reverted. - Talsec callback API
- Magisk 30.7 DenyList source
- Magisk changelog
- Magisk issue 9866, different enforcement configuration
- Adjust upstream libsigner reference
- Local scratch:
/tmp/axp-swissquote-20261009/. Proprietary APKs and decompiled files remain outside the repository and must not be committed. Partial full-APK decompilation was replaced by targeted inspection.