Roadmap¶
This is the forward-looking plan for rosetta-frida: what's next, why
each item matters (purpose), and what it buys us (benefit). It is
the companion to changelog.md (what already shipped)
and reference/design.md (how the current
architecture is shaped).
Each item lists:
- Purpose — what the task does and the problem it targets.
- Benefit — the concrete payoff once it lands.
- Scope / dependencies — rough shape of the work and what it needs first.
- Status —
planned,in progress, orblocked.
Milestones are ordered by priority, not by strict release boundaries —
the V1.5 / V2 / V3 grouping mirrors changelog.md but several items can
move between them as priorities shift.
Milestones at a glance¶
| Version | Focus |
|---|---|
| V1.0 (current) | Java-only Frida runtime + CLI. Maps ship in-repo. |
| V1.5 | rosetta diff / merge / types / verify CLI commands. Multi-version registry bundles. Fuzzy version matching. |
| V2 | Self-healing runtime discovery. Public rosetta-maps community repo (canonical schema owner; scaffolded). Frida-compile plugin for transparent marker-block injection. |
| V3 | Native (JNI / ELF) symbol mapping. Non-Frida runtimes — rosetta-xposed (Xposed / LSPosed / LSPatch, scaffolded). Hosted resolution service. |
Housekeeping (do first)¶
Fix the test-app integration pipeline (pipeline.yml) and get it green¶
- Purpose. The
pipeline.ymlGitHub Actions job builds both test-app APKs, runs them through sigmatcher → the adapter, and diffs the emitted map against the committed goldens (tests/fixtures/test-app/expected/v1.0.0.json/v1.1.0.json). It is currently failing — and appears to have never been green. - Root cause (confirmed). The AIDL fixture
tests/fixtures/test-app/app/src/main/aidl/com/example/testapp/IRemoteService.aidldeclaredrequestTickettwice ("to exercise the multi-overload form"). AIDL does not support method overloading — interface methods must have unique names — so:app:compileReleaseAidlfails and the APK never builds. Pre-existing since the fixture's founding commit (6ebd5a6); the schema-v2 work was simply the first change to touch this job's trigger paths and make it run. - Why it stayed green / invisible. Two independent gaps, neither
of which built the APK on a normal branch: (1) the main
ci.ymlverifyjob (.github/workflows/ci.yml) never touches the test-app — it runs typecheck/lint/format/schema-version/tests only; (2) the only workflow that does build the APK,pipeline.yml, is both path-gated (paths:totests/fixtures/test-app/**,tools/adapters/**,src/parse/**,src/validate/**,pipeline.yml) and only triggerspush/pull_requestagainstmaster(plus a weekly cron). So a duplicate-AIDL change on a feature branch never built the APK, and the goldens were hand-authored rather than verified against a real build — the failure could only ever surface onmasteror the Monday canary. - Fixed. The duplicate
requestTicketoverload was removed (7d44dfb);IRemoteServicenow declares a singlerequestTicketplusrequestPrompt, and the Java source / goldens / README were made consistent. A new SDK-free structural guard (scripts/lint-aidl.mjs, wired intonpm run verify/verify:fastasaidl:lint, and into theci.ymlverifyjob) fails fast on any duplicate interface-method name, with a regression test attests/lint-aidl.test.ts. This closes the duplicate-AIDL-method visibility gap on every branch without needing an Android SDK in CI. (The broader class of build-visibility gaps — end-to-end APK compilation and golden diffs — remains open; closing that requires hardeningpipeline.yml's trigger scope, a separate follow-up.) - Why it matters. This is the only check that exercises the real sigmatcher output ordering end-to-end; the unit suite can't. Until it's green, the goldens and the adapter's class/method emission order are unverified against a real sigmatcher run (the goldens are hand-authored today and almost certainly do not byte-match a real build).
- Remaining work. The AIDL fix above unblocks the build; the goldens themselves are still hand-authored and have not been byte-verified against a real sigmatcher run. That last step needs the Android SDK + sigmatcher and remains the open item:
Remove the same-name AIDL overload— done (7d44dfb); the multi-overload schema feature is exercised byBlobCache.put(put_2arg/put_3argin themethodNameMap).- Regenerate the goldens with
tests/fixtures/test-app/regenerate-goldens.sh(it now passes--version-code) against a real build, and commit them — the hand-authored goldens won't byte-match a fresh sigmatcher run. - Confirm
pipeline.ymlis green; the first error likely masks others, so iterate against a real build rather than fixing blind. - Scope / dependencies. Requires the Android SDK + sigmatcher,
which the default web environment's network policy blocks
(
dl.google.com/maven.google.comreturn 403 while PyPI/GitHub are reachable). Do this in a session/environment where those hosts are allowlisted (or against CI). - Pipeline-trigger hardening — done.
pipeline.yml'spull_requesttrigger no longer gates onbranches: [master]: it now fires on PRs targeting any branch (path filter unchanged —tests/fixtures/test-app/**already covers the Java/AIDL sources, signatures, and goldens), so a fixture/adapter change that breaks the real APK build surfaces on the feature-branch PR that introduces it rather than only onmasteror the Monday canary. The APK build stays its ownpipelinejob, so the fast SDK-freeverifyjob inci.yml(which includes theaidl:lintguard) remains the required gate. Because the build needs the Android SDK and pulls from network-restricted Google Maven hosts (dl.google.com/maven.google.com), thepipelinejob is advisory /continue-on-error: a flaky SDK/Maven outage reports on the PR but does not block unrelated merges. Promote it to a required check only once it is reliably green in a network environment where those hosts are allowlisted. The remaining golden-regeneration step below still needs that environment. - Status. AIDL root cause fixed + an SDK-free duplicate-AIDL-
method guard now runs in
verify/ci.yml; thepipeline.ymltrigger is now broadened to all-branch PRs (advisory). The end-to-end golden-diff (full APK build + sigmatcher, byte-verified goldens) remains blocked on a real Android-SDK environment.
Deferred follow-ups from the schema-v2 change¶
These close out the app-identity work that landed the version_code and
signer_sha256 fields.
On-device signer_sha256 enforcement¶
- Purpose. The schema carries an optional
signer_sha256(the hash of the APK signing certificate); the session attach path now reads the running app's signing certificate (viaPackageManagersigning info), hashes it, and compares it againstmap.signer_sha256when the field is present. - Benefit. Turns the field from documentation into a real trust
gate: a map cannot be silently applied to a repackaged or spoofed
build that happens to share the same
version_code. This is the authenticity half of the "right map for the right app" guarantee. - What shipped. A sibling module
src/session/signer-detect.tsreads signers in-process (GET_SIGNING_CERTIFICATES→signingInfo.apkContentsSignerson API 28+,GET_SIGNATURES→packageInfo.signaturesas the pre-28 fallback), SHA-256's each certificate viajava.security.MessageDigest, and normalizes to lowercase colon-free hex. The session compares after version selection and before the health check, fails closed with the newSignerMismatchError, and emits a structuredsigner-checkdiagnostic event. An app may carry multiple signers (key-rotation lineage); a match on any one is accepted. TheSessionOptions.enforceSignerknob (defaulttrue, the secure default) opts out; the check is skipped entirely when the map has nosigner_sha256. - Status. done (V1.5). Reference:
api/session.md,reference/errors.md,reference/events.md.
rosetta migrate + schema migrators¶
- Purpose. The
1 → 2bump was a hard cutover — schema-1 maps are rejected outright (z.literal(2)). Add an in-tree migrator chain and arosetta migrate <map>command that upgrades older maps to the current schema. - Benefit. Future schema bumps stop being breaking changes. Community-contributed maps and pinned bundles keep loading after a bump instead of failing hard, which is essential once the public maps repo exists.
- Scope / dependencies. A registry of
(from, to)migrator functions; a CLI command; a--in-place/-oflag. Best paired with the next schema bump so there's a concrete1 → 2/2 → 3migrator to validate against. (changelog.mdlists this under V1.5.) - Status. planned.
Broader arc — cross-framework¶
A second framework binding (Xposed / LSPosed)¶
- Purpose. The system is structured as four layers (signature
authoring, canonical map artifact, resolution semantics, runtime
binding) where only the bottom layer is Frida-specific. Build a
second binding that applies resolved names through the Xposed /
LSPosed API instead of
Java.use. - Benefit. Proves the artifact + resolution layers are genuinely framework-neutral (not accidentally Frida-shaped), and unlocks the large Xposed-module audience reusing the exact same maps. The maps repo then serves both ecosystems from one source of truth.
- Scope / dependencies. A new binding package mirroring
src/proxy/against the Xposed hooking API; the resolver and map layers are reused unchanged. Largest item here; a V2/V3-scale effort. - Status. in progress — scaffolded at
Xiddoc/rosetta-xposed. The neutral core (a Kotlin twin of the map model + resolver, kept honest by a shared conformance suite) and the static layer-4 binding (resolve real → obf → ajava.lang.reflect.Memberhanded to the developer's hook API) are built and JVM-tested. Remaining: the DexKit dynamic / self-healing backend, deferred binding for late-loaded dex, andsigner_sha256enforcement.
Developer experience & tech debt¶
Centralize schema_version / version_code in test fixtures¶
- Purpose. The schema version is now DRY in the source code (one
CURRENT_SCHEMA_VERSIONconstant) and guarded in docs + the sample map (npm run schema-version:check). The remaining hand-maintained surface is the test suite: ~30 test files inlineschema_version: 2(andversion_code: 1) in map literals. - Why it wasn't auto-fixed. A blind codemod over the tests is unsafe
because the suite deliberately mixes two kinds of literal: valid
maps that should track the current version, and invalid maps that
intentionally pin an old/wrong version for rejection tests (e.g.
schema_version: 1"is rejected",99"is invalid", "expected 2"). A find-replace can't tell them apart and would corrupt the negative tests. - Suggested approach. Introduce a shared test map factory — e.g.
tests/helpers/maps.tsexportingvalidMap(overrides?: Partial<RosettaMap>): RosettaMap— that defaultsschema_version: CURRENT_SCHEMA_VERSION, aversion_code,app,version, and an emptyclasses, merged with per-test overrides. Migrate the valid fixtures across the suite to it (or, more minimally, just importCURRENT_SCHEMA_VERSIONfor theschema_versionfield). Leave the negative-test literals explicit and clearly flagged (e.g. a// schema-keep: intentional old versioncomment). - Benefit. A future schema bump then touches one constant and the
valid fixtures follow automatically — eliminating the last big manual
surface. As a bonus, a factory de-duplicates fixture boilerplate
(
version_code,app,classes) and makes test intent clearer (factory + overrides instead of copy-pasted literals). - Scope / care. Broad but mechanical (~30 files). Do it as its own
PR so the diff is reviewable; keep the 100% coverage gate; don't fold
it into a feature change. Verify the negative tests still assert
rejection with explicit literals afterward. (Optionally extend
scripts/check-schema-version.mjsto assert no stray valid-lookingschema_versionliteral remains intests/, but once fixtures use the factory there's little left to guard.) - Status. planned (deliberately deferred from the schema-version-DRY change because it's invasive enough to deserve its own review).
Rename the inner *Map Record aliases (reserve "map" for the artifact)¶
- Purpose. "Map" is used in two senses in
src/types/map.ts: the translation artifact (RosettaMap, one(app, version_code); andRosettaMapRegistry, a record of them) versus plain lookup dictionaries (ClassMap = Record<realFQN, ClassEntry>,MethodMap,FieldMap). Rename the dictionary aliases to*Table(or*Index) — e.g.ClassMap → ClassTable,MethodMap → MethodTable,FieldMap → FieldTable— and keepRosettaMap/RosettaMapRegistryas-is. - Benefit. "Map" then means exactly one thing (the Rosetta artifact),
matching the
rosetta-mapsrepo name; the dictionaries read as lookups. New contributors stop conflating "the map" with "a Record". - Scope / care. Mechanical rename across
src/, tests, and docs. Note these aliases are part of the public type surface (re-exported fromsrc/types/index.ts), so it's a breaking type-name change — ship it behind deprecated re-exports (export type ClassMap = ClassTable) or fold it into the next breaking release. Keep the 100% coverage gate; do it as its own PR. Low urgency — clarity, not correctness. - Status. planned.
Reconcile provenance counts (MapSource.classes vs per-class source)¶
- Purpose. Each class is already attributed to a tool via
ClassEntry.source(e.g."sigmatcher"), so "which classes came from which tool" is answerable from the data. The top-levelMapSource.classescount duplicates that — it's a hand-maintained rollup that can drift out of sync with the per-class tags, and nothing validates the two agree. - Benefit. A single source of truth for provenance. No stale or
contradictory counts; the authoritative per-class
sourceanswers the "which ones?" question precisely, and tooling derives the totals. - Options / scope. Either (a) derive the count on demand
(compute it in
inspect/ a provenance report) and drop the stored field on the next schema bump; or (b) keep the field but add avalidate-time check/warning that it equals the per-class tally. Recommend (b) now (non-breaking —classesis already optional), (a) at the next bump. Either way, keepsources[]for the per-tool metadata that has nowhere else to live (config,notes). - Status. planned.
V1.5 — tooling for map maintainers¶
The theme: make authoring, maintaining, and shipping maps fast. These are CLI/tooling additions on top of the stable V1 runtime.
rosetta diff <a.json> <b.json>¶
- Purpose. Show the real → obfuscated rotation deltas between two versions' maps: which classes/methods/fields changed obfuscated names, which were added/removed.
- Benefit. Turns "what rotated this release?" from a manual jadx + hand-diff session into a single command — directly attacking the core pain that motivated the whole project.
- Scope / dependencies. Pure data operation over two validated
RosettaMaps; structured + human-readable output. No runtime dependency. - Status. planned.
rosetta merge <a.json> <b.json> [...]¶
- Purpose. Combine partial maps from different sources (sigmatcher output + hand-authored corrections + runtime-discovered entries) into one map, with a defined precedence/conflict policy.
- Benefit. Lets multiple sources and contributors compose a map
without hand-merging JSON, and matches the multi-
sourcesprovenance model the schema already supports. - Scope / dependencies. Merge strategy + conflict reporting; reuses
the validator. Pairs naturally with
diff. - Status. planned.
rosetta merge-bundle <bundle.js> <maps...> -o <out>¶
- Purpose. Fold several single-version maps into one
RosettaMapRegistryembedded as a single marker block in a compiled bundle. - Benefit. One compiled hook ships support for many app versions —
the "write once, hook many versions" promise realized at the
distribution layer. The runtime already selects the right entry by
version_code. - Scope / dependencies. Builds on the existing marker emit/patch
code (
src/marker/); needs registry assembly + dedupe. - Status. planned.
rosetta types <map.json> -o <out.d.ts>¶
- Purpose. Generate per-map TypeScript declarations for the real class/method/field names a map covers.
- Benefit. Compile-time autocomplete and typo-catching on names in
hook source, so a misspelled real name fails at
tsctime instead of becoming a silent runtime resolution miss. - Scope / dependencies. A
.d.tsemitter over a validated map. Standalone. - Status. planned.
rosetta verify --device <id>¶
- Purpose. Run the attach-time health check live against
frida-serveron a connected device, outside of a full hook script. - Benefit. A CI / pre-flight answer to "does this map still resolve against the real app on a device?" without writing or running a hook — catches a stale map before it ships.
- Scope / dependencies. Reuses
src/session/health-check.ts; adds a device-connection driver (the first piece of tooling that talks to a livefrida-serverrather than running in-process). - Status. planned.
frida-compile plugin for auto-marker-wrapping¶
- Purpose. Embed the marker block automatically at compile time
instead of the current manual "emit marker + concat" build step
(documented in
examples/sample-hook/README.md). - Benefit.
inspect/extract/patchwork on every compiled bundle by default, removing a manual build step and its footguns. - Scope / dependencies. A
frida-compileplugin hook that callsemitMarkerBlock/emitMarkerRegistryon the imported map. Depends onfrida-compile's plugin API surface. - Status. planned.
Multi-session support on the rosetta namespace¶
- Purpose. Today
rosetta.session(...)sets a single module-level ambient session. Allow more than one active session (e.g. several processes/apps) without juggling explicit imports. - Benefit. Unblocks fleet / multi-target orchestration; lets one script drive hooks against multiple apps or versions concurrently.
- Scope / dependencies. Rework the ambient singleton in
src/api/rosetta.tsinto a session-scoped handle while keeping the ergonomic single-session default. Touches the tier-½/3 composition. - Status. planned.
V2 — the distribution flywheel¶
The theme: turn rosetta-frida from a library into an ecosystem.
Public rosetta-maps repository¶
- Purpose. A separate, community-contributed repository of map
files, PR-gated by automated schema validation (no code review), keyed
by
(app, version_code)with thesigner_sha256authenticity guard. At build time, a developer pulls the map for the version they want to support and bundles it into their script — the device never fetches a map from the cloud. - Benefit. The killer feature. A hook works against a version its
author never tested, because someone else contributed that version's
map — a shared, community-contributed obfuscation-map database. Schema v2 was deliberately
shaped (authoritative
version_codekey, signer guard) to make this selection and trust model sound. - Scope / dependencies. A new repo with CI validation using this
library's validator; a build-time
rosetta pullverb that fetches a single verified map into the developer's project (no device-side client) — shipped, seerosetta pull; a contribution + provenance workflow. Depends onsigner_sha256enforcement andmigratefor long-term map durability. - Status. in progress — scaffolded at
Xiddoc/rosetta-maps. The repo layout (signatures/source-of-truth +maps/<app>/<version_code>.jsonartifacts), the structural validation CI (reusing this library'srosetta validate, no APK hosted), the filename↔version_codeconvention check, a JSON-Schema editor aid, and a worked example are in place. The build-timerosetta pullverb shipped here (fetch + schema-validate + identity cross-check + write). Remaining: populating the corpus, plus the attestation / trusted-runner / device-telemetry trust tiers there.
Runtime map injection¶
- Purpose. Populate the marker block's reserved placeholder form
(
let __rosetta_map = null;) at attach time viarosetta.injectMap(...), rather than only at compile time. - Benefit. Hot-swap a map without recompiling the bundle — the map is supplied by the controlling Frida host (on the developer's machine), not fetched by the device. The mechanism behind fleet-management workflows where the host pushes an updated map into a live session.
- Scope / dependencies. The marker-block placeholder seam already exists in the spec; needs the injection API + lifecycle handling (re-binding the resolver mid-session). Pairs with the maps repo.
- Status. planned.
Self-healing discovery¶
- Purpose. When a lookup misses, run runtime discovery strategies to
find the right name anyway: AIDL-descriptor matching, signature scan
within a known class, superclass matching, and stable-string anchors.
The resolver's lookup chain already reserves the slot for this (see
reference/design.md, "Lookup chain"). - Benefit. Hooks survive a rotation before anyone publishes an
updated map — the runtime degrades gracefully instead of failing. This
is the long-term robustness story. The discovery evidence (AIDL
descriptors, anchor strings) lives in the signatures source, not the
schema_version: 5map (which is a pure name mapping); a V2 runtime strategy would consult that signatures-side evidence, not the map. - Scope / dependencies. A pluggable strategy registry invoked at the resolver's failure slot; needs the signatures-side discovery evidence plus runtime class enumeration. Largest V2 runtime item.
- Status. planned.
V3 — frontier¶
Longer-horizon items from changelog.md, captured here for continuity:
- Native (JNI / ELF symbol) mapping — extend the model below the Java layer to native function pointers / demangled symbols / base offsets. A different mapping shape; deliberately out of V1/V2 scope.
- Non-Frida runtimes (Xposed, ART, Riru, Zygisk) — the binding-layer generalization above, taken to its conclusion.
- AI-assisted mapping generation — propose map entries from static analysis + diffs.
- Hosted resolution service — resolve names as a service rather than a bundled artifact.
- IDE plugin — obfuscated-name overlays, map-coverage warnings, and go-to-definition against jadx output.
Maintaining this file¶
When you finish an item, move its essence to changelog.md (what
shipped) and delete it here, or mark it done with a one-line pointer.
Keep purpose/benefit framing on new items so the next contributor can
tell why something is on the list, not just what it is.