Ship script gates wave 4 behind AXP_DEBLOB_WAVE4, documents secrets cert path, and adds deblob appliers plus tests; docs capture Swissquote fix and parity. Co-authored-by: Cursor <cursoragent@cursor.com>
64 lines
2.5 KiB
Python
64 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Read only Swissquote's RASP exception classification; never dump reports.
|
|
|
|
Requires an already authorized adb shell su. Does not launch, stop, modify,
|
|
instrument, or grant root to Swissquote. Raw report data stays in memory.
|
|
APK/report/session identifiers and other application data are not emitted.
|
|
"""
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
|
|
|
|
def classifications(value):
|
|
if isinstance(value, dict):
|
|
exception = value.get("exception")
|
|
if isinstance(exception, dict):
|
|
reason = exception.get("reason", "")
|
|
if (exception.get("type") == "com.swissquote.android.FreeRaspException"
|
|
and isinstance(reason, str) and reason.startswith("freeRASP: on")):
|
|
yield reason
|
|
for child in value.values():
|
|
yield from classifications(child)
|
|
elif isinstance(value, list):
|
|
for child in value:
|
|
yield from classifications(child)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--device", required=True, choices=["LAB", "GOLDEN"])
|
|
args = parser.parse_args()
|
|
serial = {"LAB": "11231JEC207477", "GOLDEN": "11071JEC203839"}[args.device]
|
|
base = "/data/user/10/com.swissquote.android/files/.crashlytics.v3/com.swissquote.android"
|
|
|
|
def shell(command):
|
|
return subprocess.check_output(
|
|
["adb", "-s", serial, "shell", "su", "-c", "'" + command + "'"],
|
|
text=True, timeout=15,
|
|
)
|
|
|
|
results = []
|
|
for path in shell("find " + base + " -maxdepth 4 -type f").splitlines():
|
|
# Never interpolate arbitrary output into a shell command.
|
|
if not path.startswith(base + "/") or any(c not in
|
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/._:-" for c in path):
|
|
continue
|
|
if not any(part in path for part in ("/event", "/reports/", "/priority-reports/")):
|
|
continue
|
|
size = shell("stat -c %s " + path).strip()
|
|
if not size.isdigit() or int(size) > 4 * 1024 * 1024:
|
|
continue
|
|
try:
|
|
value = json.loads(shell("cat " + path))
|
|
except json.JSONDecodeError:
|
|
continue
|
|
results.extend(classifications(value))
|
|
print(json.dumps({"device": args.device, "profile": 10,
|
|
"rasp_exceptions": results,
|
|
"note": "Retained events only; absence is not proof of PASS."}, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|