← WatchMemory
Changelog
0.3.0 · 2026-11
Prove your app forgets: per-secret results, honest verdicts, Android (beta).
Security advisory WM-2026-10-001
CLI: watchmemory-cli-0.3.0.tar.gz · SHA-256
sha256: 5b324918d7a1b0947fe65c071a24331b207b3f51fa609cedfc417fd6cadadd13
Residue check
- Results per secret, not per string: the same token in memory and on disk is one group that lists every place it survived, the store and key holding it, and why it blocks (or doesn't). Headline: “3 secrets survived logout in 7 places”.
- Three verdicts: PASS (exit 0), FAIL (exit 1) and INCOMPLETE (exit 2) when a capture read less than the minimum coverage of writable memory (default 90 %), a source failed, a file or folder could not be read, a limit was reached or the before capture found no secrets. A partial scan is never a PASS.
- Key-aware storage extraction from plists, SQLite (including the -wal file), JSON (AsyncStorage, redux-persist), XML preferences, MMKV and DataStore. Findings name the store and key, e.g. SDK:redux-persist › persist:root › auth › refreshToken (redux-persist in AsyncStorage).
- Known-value tracking on disk: residue capture --track-from before.json looks again, by value, for the secrets seen before logout in the storage and files captured after it, even opaque tokens no pattern matches (as written; base64-wrapped, compressed or otherwise re-encoded copies are not followed). Captures store keyed hashes, never values.
- Refresh, access, ID, session and auth tokens are rated High; canaries are Critical.
- Memory survivors block only where the result is reliable: native runtimes with MallocScribble on. Memory-only hits in React Native (Hermes) and Flutter (Dart) heaps are report-only, with the reason shown.
- Every capture and report states the runtime (native, React Native, Flutter, KMP), the build (debug) and the evidence used to detect it.
- New capture and compare formats (wm-residue-capture/2, wm-residue-compare/2). Comparing a 0.2.x capture with a 0.3.0 capture is refused: capture both again.
- Captures record the device (Simulator UDID or adb serial): a before/after pair from two devices or two apps is refused. An app that encrypts everything it stores can accept an empty before capture with residue compare --allow-empty-before.
- Large databases: key-aware decoding has a budget per file counted in decoded values (--max-structured-values, default 20 million), so the result is the same on every machine. A file over it is still scanned byte by byte; without --track-from the check is INCOMPLETE (an opaque token past the budget could be missed), with it a note.
- Secrets in file and folder names are masked in every report, like values.
OWASP MAS v2
- Every survivor maps to MASWE-0024 (sensitive data accessible after session termination, MASVS-AUTH-3).
- Secrets on disk map to MASVS-STORAGE-1 with MASWE-0001/0002, MASTG-TEST-0302 (iOS) and MASTG-TEST-0207 (Android); memory findings to MASVS-STORAGE-2.
- References to the deprecated MASTG-TEST-0060 and MASTG-TEST-0052 were removed.
- Corrected: a T.C. Kimlik No is personal data under KVKK, not special-category data.
Android (beta)
- --android-package com.acme.app on disk-scan, ci, assert and residue capture (plus --adb-serial and --android-user).
- Reads a debuggable app's private data over adb run-as: no root, CLI on Linux or macOS. Disk only: no memory or Java-heap results yet.
- Store labels for SharedPreferences, DataStore, Room/SQLite, WebView, OkHttp cache, MMKV and app files.
CLI, CI and licensing
- Versioned CLI tarball (watchmemory-cli-0.3.0.tar.gz) with a published SHA-256. Pin both in CI.
- --show-values is refused on CI runners (CI, GITHUB_ACTIONS, GITLAB_CI, TF_BUILD, JENKINS_URL, BUILDKITE, BITRISE_IO, CIRCLECI, TEAMCITY_VERSION, CODEBUILD_BUILD_ID and other common CI variables), unless you also pass --allow-show-values-in-ci.
- Free stays report-only (a FAIL exits 0) with a truthful verdict; an incomplete scan exits 2 on every tier. Pro enforces for one app; Team covers every developer and runner of an app.
- Mac app, Free tier: the first 5 surviving secrets of every check open in full detail.
Website and docs
- New positioning: “Prove your app forgets”. Claims the product could not back were removed.
- security.txt, a vulnerability disclosure policy and the public advisory WM-2026-10-001.
- New guides: Android (beta) and React Native / Flutter.
0.2.1 · 2026-10
Hotfix for the issues in advisory WM-2026-10-001.
Security advisory WM-2026-10-001
Fixed
- Licence validation: release builds carry the WatchMemory store and product IDs and fail closed without them. Cached validations from other Lemon Squeezy stores are deleted, with no offline grace.
- Canary mode: launch with -WMCanaryMode 1 (or SIMCTL_CHILD_WM_CANARY_MODE=1) and call WatchMemory.canaryIfRequested(); the app generates its own canary. A process with WMCANARY_ in its launch arguments or environment is refused (exit 2) because that value can never disappear.
- The CI recipe sets SIMCTL_CHILD_MallocScribble=1 and SIMCTL_CHILD_MallocNanoZone=0 so freed native memory isn't reported as surviving. The CI guide, README and examples were corrected.
- Generic findings stored under a refresh, access, ID, session or auth token key are rated High, so a surviving refresh token fails the gate at the default threshold.
- SQLite write-ahead logs (-wal) are read, so the latest Core Data / SQLite writes are no longer missed.
- A canary is no longer counted twice.
0.2.0 · 2026-10
New
- Residue Check: capture before and after logout and see which secrets survived, appeared or were released, with an HTML evidence report.
- Memory scans read all writable memory with stated coverage (UTF-8 and UTF-16); Full scan option.
- Simulator container scan: UserDefaults, URLCache, SQLite/Core Data cells, plists, Documents, tmp.
- Validated detectors (cards with issuer check, IBAN, T.C. Kimlik No, JWT, cloud keys) and fewer false positives.
- CLI for CI: ci, assert, residue, doctor; SARIF, JUnit, Markdown and HTML reports; GitHub Action.
- MCP server for AI coding agents; System check and update notifications in the app.
- The scan engine ships inside the app and runs with the system python3.