MOB-39: plugins can contribute AndroidManifest components + res files#32
Conversation
Adds two optional android: manifest keys: - manifest_application_snippets: XML fragments spliced into the app's <application> (a <service>/<receiver>/<provider>), idempotent per android:name. - res_files: plugin-relative paths copied into the app res/ tree at their derived res/<type>/<file> destination. Closes the gap that forced a plugin needing a manifest component + resource (e.g. mob_nfc's HCE HostApduService + apduservice.xml) to make it a manual host_requirement. - Merge: android_manifest_snippets/1 + android_res_files/1 gatherers. - Validator: both classified in the conflict surface — duplicate component names / res destinations across plugins are build errors. - Manifest: schema validation (lists of strings; res_files need a res segment). - NativeBuild: splice components before </application> + copy res files, both ledger-pruned like bridge_kt; build-time res-dest collision guard. Tests across merge/manifest/conflict_surface/native_build (307 in those files). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review — needs-work (one must-fix blocker; several defense-in-depth gaps)Nice feature — closing the But there's one blocker and a few gaps I'd want addressed: 🔴 Must-fix before merge — path traversal in
|
|
Device-verified end-to-end on a Moto G power 5G (2024): with the HCE |
…sign bytes Addresses review of #32: - F1 (blocker): res_files path traversal. Manifest validation now rejects a '..' segment, and native_build's copy resolves via __res_target__/2 which Path.expands and requires the destination stay under the app res/ dir — so a '..'-bearing path can't make File.cp! write plugin bytes anywhere on the host. - F2: host-resource clobber + prune-deletes-host-file. The copy now refuses to overwrite a file this build didn't write on a prior run (ledger-tracked), so a plugin can't replace a host-owned resource — which also stops the prune from ever deleting a host file. - F3: res_files bytes are now in the signed payload (Sign.compute_file_hashes), same as bridge_kt/jni_source — copied resource bytes are tamper-evident. Tests: '..' rejection, __res_target__ containment (incl. res-vs-resx boundary), res_files signing. 322 in the affected files; credo --strict clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the careful review — pushed 81f442d addressing the blocker and the two data-loss follow-ups. 🔴 F1 (blocker) — path traversal → FIXED. Two layers:
F2 — host clobber + prune-deletes-host → FIXED. The copy now refuses to overwrite any existing target that isn't in the prior artifact ledger (i.e. host-owned, not written by a previous plugin run) → F3 — res_files bytes not signed → FIXED. Added F4 — injected components not pruned on plugin removal → acknowledged, propose separate issue. This is consistent with existing behavior: F5 — snippet XML validation → acknowledged, low-pri. Could add a light well-formedness / single-root-element check at validate time; the snippet is already in the signed payload and injection is gated on signed+trusted, so it's a UX nicety (catch a plugin author's typo early) rather than a security boundary. Will fold into the F4 follow-up if you agree. 322 tests pass in the affected files; |
Re-review (81f442d) — fixes verified, approving ✅Checked all three against the new commit:
F4/F5 deferral to MOB-40 — agreed. On F4 specifically: the dangling- CI green, CLEAN. Nice turnaround. LGTM — approving for merge; MOB-40 as the fast-follow. |
What
Two new optional
android:plugin-manifest keys so a plugin can ship the manifest half of a component it needs, instead of making it a manualhost_requirement:manifest_application_snippets— XML fragments (a<service>,<receiver>,<provider>, …) spliced into the app's<application>block. Idempotent perandroid:name.res_files— plugin-relative paths copied into the app'sres/tree at their derivedres/<type>/<file>destination (from the lastres/path segment).Why
Hit building the
mob_nfcHCE feature (MOB-16): Android Host Card Emulation needs aHostApduServiceregistered as a manifest<service>+ ares/xml/apduservice.xmlAID group. The service class already ships fine (rides in the plugin'sbridge_kt), but the manifest entry + res file couldn't be contributed — so they were a copy-pastehost_requirement. With this, HCE (and any plugin needing a manifest component + resource) becomes turnkey.Changes
android_manifest_snippets/1+android_res_files/1gatherers (src absolute, dest derived).android:names / res destinations across plugins are build errors (andconflict_surface_test's completeness guard is satisfied).res_filesmust contain aressegment).</application>(indentation-preserving) + copy res files, both ledger-pruned likebridge_kt; build-time res-dest collision guard.Tests
New tests in
merge_test,manifest_test,conflict_surface_test,native_build_test(full T4T-style: happy path, idempotency, no-</application>safety, nested-indent preservation, cross-plugin collisions, validation errors).mix credo --strictclean on changed files.Follow-ups
mob/MOB_PLUGINS.md(separate repo).mob_nfcwill bump toplugin_spec_version: 2, adopt these keys, and drop its HCEhost_requirement.🤖 Generated with Claude Code