@metamask/device-mcp 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -1
- package/README.md +110 -21
- package/dist/android/NOTICE.md +65 -0
- package/dist/android/device-mcp-android-snapshot-helper-0.4.0.apk +0 -0
- package/dist/android/device-mcp-android-snapshot-helper-0.4.0.apk.idsig +0 -0
- package/dist/android/device-mcp-android-snapshot-helper-0.4.0.manifest.json +10 -0
- package/dist/backends/adb-backend.cjs +326 -17
- package/dist/backends/adb-backend.cjs.map +1 -1
- package/dist/backends/adb-backend.d.cts +1 -0
- package/dist/backends/adb-backend.d.cts.map +1 -1
- package/dist/backends/adb-backend.d.mts +1 -0
- package/dist/backends/adb-backend.d.mts.map +1 -1
- package/dist/backends/adb-backend.mjs +327 -18
- package/dist/backends/adb-backend.mjs.map +1 -1
- package/dist/backends/android-instrumentation/adb.cjs +28 -0
- package/dist/backends/android-instrumentation/adb.cjs.map +1 -0
- package/dist/backends/android-instrumentation/adb.d.cts +24 -0
- package/dist/backends/android-instrumentation/adb.d.cts.map +1 -0
- package/dist/backends/android-instrumentation/adb.d.mts +24 -0
- package/dist/backends/android-instrumentation/adb.d.mts.map +1 -0
- package/dist/backends/android-instrumentation/adb.mjs +25 -0
- package/dist/backends/android-instrumentation/adb.mjs.map +1 -0
- package/dist/backends/android-instrumentation/artifact.cjs +139 -0
- package/dist/backends/android-instrumentation/artifact.cjs.map +1 -0
- package/dist/backends/android-instrumentation/artifact.d.cts +28 -0
- package/dist/backends/android-instrumentation/artifact.d.cts.map +1 -0
- package/dist/backends/android-instrumentation/artifact.d.mts +28 -0
- package/dist/backends/android-instrumentation/artifact.d.mts.map +1 -0
- package/dist/backends/android-instrumentation/artifact.mjs +154 -0
- package/dist/backends/android-instrumentation/artifact.mjs.map +1 -0
- package/dist/backends/android-instrumentation/errors.cjs +79 -0
- package/dist/backends/android-instrumentation/errors.cjs.map +1 -0
- package/dist/backends/android-instrumentation/errors.d.cts +68 -0
- package/dist/backends/android-instrumentation/errors.d.cts.map +1 -0
- package/dist/backends/android-instrumentation/errors.d.mts +68 -0
- package/dist/backends/android-instrumentation/errors.d.mts.map +1 -0
- package/dist/backends/android-instrumentation/errors.mjs +73 -0
- package/dist/backends/android-instrumentation/errors.mjs.map +1 -0
- package/dist/backends/android-instrumentation/installer.cjs +235 -0
- package/dist/backends/android-instrumentation/installer.cjs.map +1 -0
- package/dist/backends/android-instrumentation/installer.d.cts +123 -0
- package/dist/backends/android-instrumentation/installer.d.cts.map +1 -0
- package/dist/backends/android-instrumentation/installer.d.mts +123 -0
- package/dist/backends/android-instrumentation/installer.d.mts.map +1 -0
- package/dist/backends/android-instrumentation/installer.mjs +225 -0
- package/dist/backends/android-instrumentation/installer.mjs.map +1 -0
- package/dist/backends/android-instrumentation/signer.cjs +435 -0
- package/dist/backends/android-instrumentation/signer.cjs.map +1 -0
- package/dist/backends/android-instrumentation/signer.d.cts +40 -0
- package/dist/backends/android-instrumentation/signer.d.cts.map +1 -0
- package/dist/backends/android-instrumentation/signer.d.mts +40 -0
- package/dist/backends/android-instrumentation/signer.d.mts.map +1 -0
- package/dist/backends/android-instrumentation/signer.mjs +431 -0
- package/dist/backends/android-instrumentation/signer.mjs.map +1 -0
- package/dist/backends/android-instrumentation/snapshot.cjs +159 -0
- package/dist/backends/android-instrumentation/snapshot.cjs.map +1 -0
- package/dist/backends/android-instrumentation/snapshot.d.cts +67 -0
- package/dist/backends/android-instrumentation/snapshot.d.cts.map +1 -0
- package/dist/backends/android-instrumentation/snapshot.d.mts +67 -0
- package/dist/backends/android-instrumentation/snapshot.d.mts.map +1 -0
- package/dist/backends/android-instrumentation/snapshot.mjs +154 -0
- package/dist/backends/android-instrumentation/snapshot.mjs.map +1 -0
- package/dist/backends/appium-backend.cjs +19 -1
- package/dist/backends/appium-backend.cjs.map +1 -1
- package/dist/backends/appium-backend.d.cts +1 -0
- package/dist/backends/appium-backend.d.cts.map +1 -1
- package/dist/backends/appium-backend.d.mts +1 -0
- package/dist/backends/appium-backend.d.mts.map +1 -1
- package/dist/backends/appium-backend.mjs +19 -1
- package/dist/backends/appium-backend.mjs.map +1 -1
- package/dist/backends/idb-backend.cjs +128 -31
- package/dist/backends/idb-backend.cjs.map +1 -1
- package/dist/backends/idb-backend.d.cts +1 -0
- package/dist/backends/idb-backend.d.cts.map +1 -1
- package/dist/backends/idb-backend.d.mts +1 -0
- package/dist/backends/idb-backend.d.mts.map +1 -1
- package/dist/backends/idb-backend.mjs +129 -32
- package/dist/backends/idb-backend.mjs.map +1 -1
- package/dist/backends/index.cjs +3 -0
- package/dist/backends/index.cjs.map +1 -1
- package/dist/backends/index.d.cts.map +1 -1
- package/dist/backends/index.d.mts.map +1 -1
- package/dist/backends/index.mjs +3 -0
- package/dist/backends/index.mjs.map +1 -1
- package/dist/backends/types.cjs.map +1 -1
- package/dist/backends/types.d.cts +6 -0
- package/dist/backends/types.d.cts.map +1 -1
- package/dist/backends/types.d.mts +6 -0
- package/dist/backends/types.d.mts.map +1 -1
- package/dist/backends/types.mjs.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/tools/swipe.cjs +3 -1
- package/dist/tools/swipe.cjs.map +1 -1
- package/dist/tools/swipe.d.cts.map +1 -1
- package/dist/tools/swipe.d.mts.map +1 -1
- package/dist/tools/swipe.mjs +3 -1
- package/dist/tools/swipe.mjs.map +1 -1
- package/dist/utils/output-path.cjs +13 -0
- package/dist/utils/output-path.cjs.map +1 -1
- package/dist/utils/output-path.d.cts +10 -0
- package/dist/utils/output-path.d.cts.map +1 -1
- package/dist/utils/output-path.d.mts +10 -0
- package/dist/utils/output-path.d.mts.map +1 -1
- package/dist/utils/output-path.mjs +12 -0
- package/dist/utils/output-path.mjs.map +1 -1
- package/dist/utils/platform.cjs +65 -7
- package/dist/utils/platform.cjs.map +1 -1
- package/dist/utils/platform.d.cts +33 -0
- package/dist/utils/platform.d.cts.map +1 -1
- package/dist/utils/platform.d.mts +33 -0
- package/dist/utils/platform.d.mts.map +1 -1
- package/dist/utils/platform.mjs +63 -7
- package/dist/utils/platform.mjs.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error types for the Android snapshot-helper trust + install pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Two failure classes are deliberately distinct because the ADB backend treats
|
|
5
|
+
* them differently (see `adb-backend.ts`):
|
|
6
|
+
*
|
|
7
|
+
* - {@link SnapshotHelperError} — a *generic* failure (install failed, adb
|
|
8
|
+
* hiccup, malformed artifact). The `auto` snapshot strategy is allowed to fall
|
|
9
|
+
* back to `uiautomator dump` when one of these occurs.
|
|
10
|
+
* - {@link UntrustedHelperError} — the installed helper's signing certificate
|
|
11
|
+
* does NOT match the pinned signer. This means a possibly-malicious app is
|
|
12
|
+
* squatting the helper's package name. The snapshot flow MUST fail closed:
|
|
13
|
+
* it is never swallowed and never triggers a dump fallback, because silently
|
|
14
|
+
* returning a dump snapshot would hide a compromised device from a wallet
|
|
15
|
+
* test harness.
|
|
16
|
+
*
|
|
17
|
+
* Structure adapted from agent-device (MIT, Copyright (c) Callstack); the
|
|
18
|
+
* `UntrustedHelperError` trust class and messaging are ours.
|
|
19
|
+
*/
|
|
20
|
+
/** Machine-readable code for a {@link SnapshotHelperError}. */
|
|
21
|
+
export type SnapshotHelperErrorCode = 'COMMAND_FAILED' | 'INVALID_ARGS' | 'ARTIFACT_INVALID';
|
|
22
|
+
/**
|
|
23
|
+
* A generic, recoverable snapshot-helper failure. Callers in `auto` mode may
|
|
24
|
+
* fall back to `uiautomator dump` after catching one of these.
|
|
25
|
+
*/
|
|
26
|
+
export declare class SnapshotHelperError extends Error {
|
|
27
|
+
readonly code: SnapshotHelperErrorCode;
|
|
28
|
+
/**
|
|
29
|
+
* @param code - Machine-readable failure category.
|
|
30
|
+
* @param message - Human-readable description.
|
|
31
|
+
* @param options - Standard error options (e.g. `cause`).
|
|
32
|
+
*/
|
|
33
|
+
constructor(code: SnapshotHelperErrorCode, message: string, options?: ErrorOptions);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A trust failure: the installed helper is not signed by our pinned key.
|
|
37
|
+
*
|
|
38
|
+
* This is a hard stop. It must propagate out of the snapshot flow — never be
|
|
39
|
+
* converted to `null`, never be collected into a generic failure list, and
|
|
40
|
+
* never trigger a `uiautomator dump` fallback.
|
|
41
|
+
*/
|
|
42
|
+
export declare class UntrustedHelperError extends Error {
|
|
43
|
+
/** The signer SHA-256 we required. */
|
|
44
|
+
readonly expectedSignerSha256: string;
|
|
45
|
+
/** The signer SHA-256 actually found on the device, if one was extracted. */
|
|
46
|
+
readonly actualSignerSha256?: string;
|
|
47
|
+
/**
|
|
48
|
+
* @param message - Human-readable description of the mismatch.
|
|
49
|
+
* @param details - The expected and (optionally) observed signer digests.
|
|
50
|
+
* @param details.expectedSignerSha256 - The pinned signer SHA-256.
|
|
51
|
+
* @param details.actualSignerSha256 - The observed signer SHA-256, if known.
|
|
52
|
+
* @param options - Standard error options (e.g. `cause`).
|
|
53
|
+
*/
|
|
54
|
+
constructor(message: string, details: {
|
|
55
|
+
expectedSignerSha256: string;
|
|
56
|
+
actualSignerSha256?: string;
|
|
57
|
+
}, options?: ErrorOptions);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Type guard for the fail-closed trust error. The ADB backend uses this to
|
|
61
|
+
* decide whether an instrumentation failure may fall back to `dump` (generic)
|
|
62
|
+
* or must propagate (trust).
|
|
63
|
+
*
|
|
64
|
+
* @param error - The value to test.
|
|
65
|
+
* @returns True when `error` is an {@link UntrustedHelperError}.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isUntrustedHelperError(error: unknown): error is UntrustedHelperError;
|
|
68
|
+
//# sourceMappingURL=errors.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.cts","sourceRoot":"","sources":["../../../src/backends/android-instrumentation/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,+DAA+D;AAC/D,MAAM,MAAM,uBAAuB,GAC/B,gBAAgB,GAChB,cAAc,GACd,kBAAkB,CAAC;AAEvB;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAgB,IAAI,EAAE,uBAAuB,CAAC;IAE9C;;;;OAIG;gBAED,IAAI,EAAE,uBAAuB,EAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY;CAQzB;AAED;;;;;;GAMG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,sCAAsC;IACtC,SAAgB,oBAAoB,EAAE,MAAM,CAAC;IAE7C,6EAA6E;IAC7E,SAAgB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5C;;;;;;OAMG;gBAED,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QACP,oBAAoB,EAAE,MAAM,CAAC;QAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;KAC7B,EACD,OAAO,CAAC,EAAE,YAAY;CASzB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,oBAAoB,CAE/B"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error types for the Android snapshot-helper trust + install pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Two failure classes are deliberately distinct because the ADB backend treats
|
|
5
|
+
* them differently (see `adb-backend.ts`):
|
|
6
|
+
*
|
|
7
|
+
* - {@link SnapshotHelperError} — a *generic* failure (install failed, adb
|
|
8
|
+
* hiccup, malformed artifact). The `auto` snapshot strategy is allowed to fall
|
|
9
|
+
* back to `uiautomator dump` when one of these occurs.
|
|
10
|
+
* - {@link UntrustedHelperError} — the installed helper's signing certificate
|
|
11
|
+
* does NOT match the pinned signer. This means a possibly-malicious app is
|
|
12
|
+
* squatting the helper's package name. The snapshot flow MUST fail closed:
|
|
13
|
+
* it is never swallowed and never triggers a dump fallback, because silently
|
|
14
|
+
* returning a dump snapshot would hide a compromised device from a wallet
|
|
15
|
+
* test harness.
|
|
16
|
+
*
|
|
17
|
+
* Structure adapted from agent-device (MIT, Copyright (c) Callstack); the
|
|
18
|
+
* `UntrustedHelperError` trust class and messaging are ours.
|
|
19
|
+
*/
|
|
20
|
+
/** Machine-readable code for a {@link SnapshotHelperError}. */
|
|
21
|
+
export type SnapshotHelperErrorCode = 'COMMAND_FAILED' | 'INVALID_ARGS' | 'ARTIFACT_INVALID';
|
|
22
|
+
/**
|
|
23
|
+
* A generic, recoverable snapshot-helper failure. Callers in `auto` mode may
|
|
24
|
+
* fall back to `uiautomator dump` after catching one of these.
|
|
25
|
+
*/
|
|
26
|
+
export declare class SnapshotHelperError extends Error {
|
|
27
|
+
readonly code: SnapshotHelperErrorCode;
|
|
28
|
+
/**
|
|
29
|
+
* @param code - Machine-readable failure category.
|
|
30
|
+
* @param message - Human-readable description.
|
|
31
|
+
* @param options - Standard error options (e.g. `cause`).
|
|
32
|
+
*/
|
|
33
|
+
constructor(code: SnapshotHelperErrorCode, message: string, options?: ErrorOptions);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A trust failure: the installed helper is not signed by our pinned key.
|
|
37
|
+
*
|
|
38
|
+
* This is a hard stop. It must propagate out of the snapshot flow — never be
|
|
39
|
+
* converted to `null`, never be collected into a generic failure list, and
|
|
40
|
+
* never trigger a `uiautomator dump` fallback.
|
|
41
|
+
*/
|
|
42
|
+
export declare class UntrustedHelperError extends Error {
|
|
43
|
+
/** The signer SHA-256 we required. */
|
|
44
|
+
readonly expectedSignerSha256: string;
|
|
45
|
+
/** The signer SHA-256 actually found on the device, if one was extracted. */
|
|
46
|
+
readonly actualSignerSha256?: string;
|
|
47
|
+
/**
|
|
48
|
+
* @param message - Human-readable description of the mismatch.
|
|
49
|
+
* @param details - The expected and (optionally) observed signer digests.
|
|
50
|
+
* @param details.expectedSignerSha256 - The pinned signer SHA-256.
|
|
51
|
+
* @param details.actualSignerSha256 - The observed signer SHA-256, if known.
|
|
52
|
+
* @param options - Standard error options (e.g. `cause`).
|
|
53
|
+
*/
|
|
54
|
+
constructor(message: string, details: {
|
|
55
|
+
expectedSignerSha256: string;
|
|
56
|
+
actualSignerSha256?: string;
|
|
57
|
+
}, options?: ErrorOptions);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Type guard for the fail-closed trust error. The ADB backend uses this to
|
|
61
|
+
* decide whether an instrumentation failure may fall back to `dump` (generic)
|
|
62
|
+
* or must propagate (trust).
|
|
63
|
+
*
|
|
64
|
+
* @param error - The value to test.
|
|
65
|
+
* @returns True when `error` is an {@link UntrustedHelperError}.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isUntrustedHelperError(error: unknown): error is UntrustedHelperError;
|
|
68
|
+
//# sourceMappingURL=errors.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.mts","sourceRoot":"","sources":["../../../src/backends/android-instrumentation/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,+DAA+D;AAC/D,MAAM,MAAM,uBAAuB,GAC/B,gBAAgB,GAChB,cAAc,GACd,kBAAkB,CAAC;AAEvB;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAgB,IAAI,EAAE,uBAAuB,CAAC;IAE9C;;;;OAIG;gBAED,IAAI,EAAE,uBAAuB,EAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY;CAQzB;AAED;;;;;;GAMG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,sCAAsC;IACtC,SAAgB,oBAAoB,EAAE,MAAM,CAAC;IAE7C,6EAA6E;IAC7E,SAAgB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5C;;;;;;OAMG;gBAED,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QACP,oBAAoB,EAAE,MAAM,CAAC;QAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;KAC7B,EACD,OAAO,CAAC,EAAE,YAAY;CASzB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,oBAAoB,CAE/B"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error types for the Android snapshot-helper trust + install pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Two failure classes are deliberately distinct because the ADB backend treats
|
|
5
|
+
* them differently (see `adb-backend.ts`):
|
|
6
|
+
*
|
|
7
|
+
* - {@link SnapshotHelperError} — a *generic* failure (install failed, adb
|
|
8
|
+
* hiccup, malformed artifact). The `auto` snapshot strategy is allowed to fall
|
|
9
|
+
* back to `uiautomator dump` when one of these occurs.
|
|
10
|
+
* - {@link UntrustedHelperError} — the installed helper's signing certificate
|
|
11
|
+
* does NOT match the pinned signer. This means a possibly-malicious app is
|
|
12
|
+
* squatting the helper's package name. The snapshot flow MUST fail closed:
|
|
13
|
+
* it is never swallowed and never triggers a dump fallback, because silently
|
|
14
|
+
* returning a dump snapshot would hide a compromised device from a wallet
|
|
15
|
+
* test harness.
|
|
16
|
+
*
|
|
17
|
+
* Structure adapted from agent-device (MIT, Copyright (c) Callstack); the
|
|
18
|
+
* `UntrustedHelperError` trust class and messaging are ours.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* A generic, recoverable snapshot-helper failure. Callers in `auto` mode may
|
|
22
|
+
* fall back to `uiautomator dump` after catching one of these.
|
|
23
|
+
*/
|
|
24
|
+
export class SnapshotHelperError extends Error {
|
|
25
|
+
/**
|
|
26
|
+
* @param code - Machine-readable failure category.
|
|
27
|
+
* @param message - Human-readable description.
|
|
28
|
+
* @param options - Standard error options (e.g. `cause`).
|
|
29
|
+
*/
|
|
30
|
+
constructor(code, message, options) {
|
|
31
|
+
super(message, options);
|
|
32
|
+
this.name = 'SnapshotHelperError';
|
|
33
|
+
this.code = code;
|
|
34
|
+
// Restore prototype chain for `instanceof` across the dual ESM/CJS build.
|
|
35
|
+
Object.setPrototypeOf(this, SnapshotHelperError.prototype);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A trust failure: the installed helper is not signed by our pinned key.
|
|
40
|
+
*
|
|
41
|
+
* This is a hard stop. It must propagate out of the snapshot flow — never be
|
|
42
|
+
* converted to `null`, never be collected into a generic failure list, and
|
|
43
|
+
* never trigger a `uiautomator dump` fallback.
|
|
44
|
+
*/
|
|
45
|
+
export class UntrustedHelperError extends Error {
|
|
46
|
+
/**
|
|
47
|
+
* @param message - Human-readable description of the mismatch.
|
|
48
|
+
* @param details - The expected and (optionally) observed signer digests.
|
|
49
|
+
* @param details.expectedSignerSha256 - The pinned signer SHA-256.
|
|
50
|
+
* @param details.actualSignerSha256 - The observed signer SHA-256, if known.
|
|
51
|
+
* @param options - Standard error options (e.g. `cause`).
|
|
52
|
+
*/
|
|
53
|
+
constructor(message, details, options) {
|
|
54
|
+
super(message, options);
|
|
55
|
+
this.name = 'UntrustedHelperError';
|
|
56
|
+
this.expectedSignerSha256 = details.expectedSignerSha256;
|
|
57
|
+
this.actualSignerSha256 = details.actualSignerSha256;
|
|
58
|
+
// Restore prototype chain for `instanceof` across the dual ESM/CJS build.
|
|
59
|
+
Object.setPrototypeOf(this, UntrustedHelperError.prototype);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Type guard for the fail-closed trust error. The ADB backend uses this to
|
|
64
|
+
* decide whether an instrumentation failure may fall back to `dump` (generic)
|
|
65
|
+
* or must propagate (trust).
|
|
66
|
+
*
|
|
67
|
+
* @param error - The value to test.
|
|
68
|
+
* @returns True when `error` is an {@link UntrustedHelperError}.
|
|
69
|
+
*/
|
|
70
|
+
export function isUntrustedHelperError(error) {
|
|
71
|
+
return error instanceof UntrustedHelperError;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=errors.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.mjs","sourceRoot":"","sources":["../../../src/backends/android-instrumentation/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH;;;GAGG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAG5C;;;;OAIG;IACH,YACE,IAA6B,EAC7B,OAAe,EACf,OAAsB;QAEtB,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,0EAA0E;QAC1E,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAC7D,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAO7C;;;;;;OAMG;IACH,YACE,OAAe,EACf,OAGC,EACD,OAAsB;QAEtB,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;QACnC,IAAI,CAAC,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC;QACzD,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;QACrD,0EAA0E;QAC1E,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAC9D,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,KAAc;IAEd,OAAO,KAAK,YAAY,oBAAoB,CAAC;AAC/C,CAAC","sourcesContent":["/**\n * Error types for the Android snapshot-helper trust + install pipeline.\n *\n * Two failure classes are deliberately distinct because the ADB backend treats\n * them differently (see `adb-backend.ts`):\n *\n * - {@link SnapshotHelperError} — a *generic* failure (install failed, adb\n * hiccup, malformed artifact). The `auto` snapshot strategy is allowed to fall\n * back to `uiautomator dump` when one of these occurs.\n * - {@link UntrustedHelperError} — the installed helper's signing certificate\n * does NOT match the pinned signer. This means a possibly-malicious app is\n * squatting the helper's package name. The snapshot flow MUST fail closed:\n * it is never swallowed and never triggers a dump fallback, because silently\n * returning a dump snapshot would hide a compromised device from a wallet\n * test harness.\n *\n * Structure adapted from agent-device (MIT, Copyright (c) Callstack); the\n * `UntrustedHelperError` trust class and messaging are ours.\n */\n\n/** Machine-readable code for a {@link SnapshotHelperError}. */\nexport type SnapshotHelperErrorCode =\n | 'COMMAND_FAILED'\n | 'INVALID_ARGS'\n | 'ARTIFACT_INVALID';\n\n/**\n * A generic, recoverable snapshot-helper failure. Callers in `auto` mode may\n * fall back to `uiautomator dump` after catching one of these.\n */\nexport class SnapshotHelperError extends Error {\n public readonly code: SnapshotHelperErrorCode;\n\n /**\n * @param code - Machine-readable failure category.\n * @param message - Human-readable description.\n * @param options - Standard error options (e.g. `cause`).\n */\n public constructor(\n code: SnapshotHelperErrorCode,\n message: string,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'SnapshotHelperError';\n this.code = code;\n // Restore prototype chain for `instanceof` across the dual ESM/CJS build.\n Object.setPrototypeOf(this, SnapshotHelperError.prototype);\n }\n}\n\n/**\n * A trust failure: the installed helper is not signed by our pinned key.\n *\n * This is a hard stop. It must propagate out of the snapshot flow — never be\n * converted to `null`, never be collected into a generic failure list, and\n * never trigger a `uiautomator dump` fallback.\n */\nexport class UntrustedHelperError extends Error {\n /** The signer SHA-256 we required. */\n public readonly expectedSignerSha256: string;\n\n /** The signer SHA-256 actually found on the device, if one was extracted. */\n public readonly actualSignerSha256?: string;\n\n /**\n * @param message - Human-readable description of the mismatch.\n * @param details - The expected and (optionally) observed signer digests.\n * @param details.expectedSignerSha256 - The pinned signer SHA-256.\n * @param details.actualSignerSha256 - The observed signer SHA-256, if known.\n * @param options - Standard error options (e.g. `cause`).\n */\n public constructor(\n message: string,\n details: {\n expectedSignerSha256: string;\n actualSignerSha256?: string;\n },\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'UntrustedHelperError';\n this.expectedSignerSha256 = details.expectedSignerSha256;\n this.actualSignerSha256 = details.actualSignerSha256;\n // Restore prototype chain for `instanceof` across the dual ESM/CJS build.\n Object.setPrototypeOf(this, UntrustedHelperError.prototype);\n }\n}\n\n/**\n * Type guard for the fail-closed trust error. The ADB backend uses this to\n * decide whether an instrumentation failure may fall back to `dump` (generic)\n * or must propagate (trust).\n *\n * @param error - The value to test.\n * @returns True when `error` is an {@link UntrustedHelperError}.\n */\nexport function isUntrustedHelperError(\n error: unknown,\n): error is UntrustedHelperError {\n return error instanceof UntrustedHelperError;\n}\n"]}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Installs and TRUSTS the bundled Android snapshot-helper instrumentation APK.
|
|
4
|
+
*
|
|
5
|
+
* The APK is built during `prepack` into `dist/android/` and shipped with the
|
|
6
|
+
* npm package (`files: ["dist"]`). Before the ADB backend runs `am instrument`
|
|
7
|
+
* it must guarantee that the package on the device is OUR helper — not a
|
|
8
|
+
* malicious app squatting the package name. Package name and versionCode are
|
|
9
|
+
* attacker-controlled metadata; the only trust anchor is the signing
|
|
10
|
+
* certificate. So `ensureHelperTrusted` installs/upgrades by versionCode
|
|
11
|
+
* (freshness only) and then UNCONDITIONALLY verifies the installed APK's v2/v3
|
|
12
|
+
* signer against the pinned SHA-256 from the build manifest. A mismatch throws
|
|
13
|
+
* {@link UntrustedHelperError} and the caller must fail closed (no dump
|
|
14
|
+
* fallback) — see `adb-backend.ts`.
|
|
15
|
+
*
|
|
16
|
+
* `__dirname` is used for path resolution because it is portable across the
|
|
17
|
+
* dual ESM/CJS build: ts-bridge rewrites it to an `import.meta.url`-based shim
|
|
18
|
+
* in the `.mjs` output and leaves the native binding in the `.cjs` output.
|
|
19
|
+
*
|
|
20
|
+
* Version/policy/signer-verify flow adapted from agent-device (MIT, Copyright
|
|
21
|
+
* (c) Callstack) onto our source-built APK and pure-JS signer verifier.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.INSTRUMENTATION_NOT_FOUND_SIGNATURE = void 0;
|
|
25
|
+
exports.resolveHelperApkPath = resolveHelperApkPath;
|
|
26
|
+
exports.isHelperInstalled = isHelperInstalled;
|
|
27
|
+
exports.installHelper = installHelper;
|
|
28
|
+
exports.verifyInstalledSigner = verifyInstalledSigner;
|
|
29
|
+
exports.ensureHelperInstalled = ensureHelperInstalled;
|
|
30
|
+
exports.assertInstalledHelperTrusted = assertInstalledHelperTrusted;
|
|
31
|
+
exports.ensureHelperTrusted = ensureHelperTrusted;
|
|
32
|
+
const node_fs_1 = require("node:fs");
|
|
33
|
+
const node_path_1 = require("node:path");
|
|
34
|
+
const adb_js_1 = require("./adb.cjs");
|
|
35
|
+
const artifact_js_1 = require("./artifact.cjs");
|
|
36
|
+
const errors_js_1 = require("./errors.cjs");
|
|
37
|
+
const signer_js_1 = require("./signer.cjs");
|
|
38
|
+
const snapshot_js_1 = require("./snapshot.cjs");
|
|
39
|
+
const exec_js_1 = require("../../utils/exec.cjs");
|
|
40
|
+
const output_path_js_1 = require("../../utils/output-path.cjs");
|
|
41
|
+
const INSTALL_TIMEOUT_MS = 60000;
|
|
42
|
+
const QUERY_TIMEOUT_MS = 10000;
|
|
43
|
+
const PULL_TIMEOUT_MS = 30000;
|
|
44
|
+
/**
|
|
45
|
+
* `am instrument` prints this when the target instrumentation is not installed.
|
|
46
|
+
* The ADB backend keys reactive re-installs off this signature.
|
|
47
|
+
*/
|
|
48
|
+
exports.INSTRUMENTATION_NOT_FOUND_SIGNATURE = 'INSTRUMENTATION_FAILED';
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the path to the bundled helper APK (validated against its manifest).
|
|
51
|
+
*
|
|
52
|
+
* @returns The absolute path to the APK.
|
|
53
|
+
* @throws When the bundled artifact is missing, malformed, or its digest does
|
|
54
|
+
* not match the manifest.
|
|
55
|
+
*/
|
|
56
|
+
async function resolveHelperApkPath() {
|
|
57
|
+
const artifact = await (0, artifact_js_1.loadHelperArtifact)();
|
|
58
|
+
return artifact.apkPath;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check whether the helper package is present on the device.
|
|
62
|
+
*
|
|
63
|
+
* @param serial - The target device serial.
|
|
64
|
+
* @returns True when the helper package is installed (any signer).
|
|
65
|
+
*/
|
|
66
|
+
async function isHelperInstalled(serial) {
|
|
67
|
+
const result = await (0, exec_js_1.exec)('adb', ['-s', serial, 'shell', 'pm', 'list', 'packages', snapshot_js_1.HELPER_PACKAGE], { timeoutMs: QUERY_TIMEOUT_MS });
|
|
68
|
+
return result.stdout.includes(`package:${snapshot_js_1.HELPER_PACKAGE}`);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Read the installed helper's versionCode via `dumpsys package`.
|
|
72
|
+
*
|
|
73
|
+
* versionCode is attacker-controlled metadata: it is used ONLY to decide
|
|
74
|
+
* whether to (re)install a fresher build, never as a trust signal.
|
|
75
|
+
*
|
|
76
|
+
* @param serial - The target device serial.
|
|
77
|
+
* @returns The installed versionCode, or null when not installed / unreadable.
|
|
78
|
+
*/
|
|
79
|
+
async function readInstalledVersionCode(serial) {
|
|
80
|
+
const result = await (0, exec_js_1.exec)('adb', ['-s', serial, 'shell', 'dumpsys', 'package', snapshot_js_1.HELPER_PACKAGE], { timeoutMs: QUERY_TIMEOUT_MS });
|
|
81
|
+
const match = /versionCode=(\d+)/u.exec(result.stdout);
|
|
82
|
+
return match ? Number(match[1]) : null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Android install-result tokens that specifically indicate the already-
|
|
86
|
+
* installed same-package app was signed by a DIFFERENT certificate, so the
|
|
87
|
+
* reinstall was refused on trust grounds (not a generic install failure). These
|
|
88
|
+
* constant names are stable across API 23-35 and are printed by PackageManager
|
|
89
|
+
* in the `Failure [<TOKEN>: ...]` line (on stdout; we scan both streams).
|
|
90
|
+
*
|
|
91
|
+
* Deliberately excludes generic failures like INSTALL_FAILED_TEST_ONLY,
|
|
92
|
+
* INSTALL_FAILED_VERSION_DOWNGRADE, INSTALL_FAILED_DUPLICATE_PERMISSION, and
|
|
93
|
+
* the parse-time no/inconsistent-certificate codes: none of those mean a
|
|
94
|
+
* squatter is signed by a different key on a normal reinstall.
|
|
95
|
+
*/
|
|
96
|
+
const SIGNER_CONFLICT_TOKENS = [
|
|
97
|
+
'INSTALL_FAILED_UPDATE_INCOMPATIBLE',
|
|
98
|
+
'INSTALL_FAILED_SHARED_USER_INCOMPATIBLE',
|
|
99
|
+
];
|
|
100
|
+
/**
|
|
101
|
+
* Install the bundled helper APK onto the device.
|
|
102
|
+
*
|
|
103
|
+
* `-r` reinstalls over an existing copy signed by the SAME key and `-t` allows
|
|
104
|
+
* the `testOnly` APK. It CANNOT overwrite a package signed by a different key:
|
|
105
|
+
* Android refuses with `INSTALL_FAILED_UPDATE_INCOMPATIBLE` (or
|
|
106
|
+
* `INSTALL_FAILED_SHARED_USER_INCOMPATIBLE`). That is a squatter signed by a
|
|
107
|
+
* foreign certificate, so it is a TRUST failure, not a generic one — we throw
|
|
108
|
+
* {@link UntrustedHelperError} to make the caller fail closed, consistent with
|
|
109
|
+
* the equal/higher-versionCode squatter path (which fails closed via the signer
|
|
110
|
+
* verification). Without this, a lower-versionCode squatter would surface as a
|
|
111
|
+
* generic Error and `auto` mode would silently fall back to `uiautomator dump`.
|
|
112
|
+
*
|
|
113
|
+
* @param serial - The target device serial.
|
|
114
|
+
* @param artifact - The bundled artifact carrying the pinned signer SHA-256,
|
|
115
|
+
* used both to locate the APK and to describe a trust failure.
|
|
116
|
+
* @throws {@link UntrustedHelperError} when the install is refused because the
|
|
117
|
+
* installed package is signed by a different certificate.
|
|
118
|
+
* @throws When the install command fails for a generic reason.
|
|
119
|
+
*/
|
|
120
|
+
async function installHelper(serial, artifact) {
|
|
121
|
+
const resolved = artifact ?? (await (0, artifact_js_1.loadHelperArtifact)());
|
|
122
|
+
const { apkPath } = resolved;
|
|
123
|
+
const result = await (0, exec_js_1.exec)('adb', ['-s', serial, 'install', '-r', '-t', apkPath], { timeoutMs: INSTALL_TIMEOUT_MS });
|
|
124
|
+
if (result.exitCode !== 0 || !result.stdout.includes('Success')) {
|
|
125
|
+
const output = `${result.stdout.trim()} ${result.stderr.trim()}`.trim();
|
|
126
|
+
if (SIGNER_CONFLICT_TOKENS.some((token) => output.includes(token))) {
|
|
127
|
+
throw new errors_js_1.UntrustedHelperError(`Snapshot helper install was refused because the installed package is ` +
|
|
128
|
+
`signed by a different certificate (a squatter). ${output}`, { expectedSignerSha256: resolved.signerSha256 });
|
|
129
|
+
}
|
|
130
|
+
throw new Error(`Failed to install snapshot helper APK (${apkPath}).\n${output}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Pull every installed APK path for the helper package (base + splits) into a
|
|
135
|
+
* private temp dir and verify each is signed by the pinned certificate.
|
|
136
|
+
*
|
|
137
|
+
* @param serial - The target device serial.
|
|
138
|
+
* @param artifact - The bundled artifact carrying the pinned signer SHA-256.
|
|
139
|
+
* @throws {@link UntrustedHelperError} when the installed signer does not match
|
|
140
|
+
* the pin.
|
|
141
|
+
*/
|
|
142
|
+
async function verifyInstalledSigner(serial, artifact) {
|
|
143
|
+
const run = (0, adb_js_1.createAndroidAdbExecutor)(serial);
|
|
144
|
+
const pathsResult = await run(['shell', 'pm', 'path', snapshot_js_1.HELPER_PACKAGE], {
|
|
145
|
+
timeoutMs: QUERY_TIMEOUT_MS,
|
|
146
|
+
});
|
|
147
|
+
const remotePaths = pathsResult.stdout
|
|
148
|
+
.split('\n')
|
|
149
|
+
.map((line) => line.trim())
|
|
150
|
+
.filter((line) => line.startsWith('package:'))
|
|
151
|
+
.map((line) => line.slice('package:'.length));
|
|
152
|
+
if (remotePaths.length === 0) {
|
|
153
|
+
throw new errors_js_1.UntrustedHelperError('Snapshot helper is installed but its APK path could not be resolved', { expectedSignerSha256: artifact.signerSha256 });
|
|
154
|
+
}
|
|
155
|
+
const tempDir = (0, output_path_js_1.createPrivateTempDir)('helper-verify');
|
|
156
|
+
try {
|
|
157
|
+
let index = 0;
|
|
158
|
+
for (const remotePath of remotePaths) {
|
|
159
|
+
const localPath = (0, node_path_1.join)(tempDir, `apk-${index}.apk`);
|
|
160
|
+
index += 1;
|
|
161
|
+
const pull = await run(['pull', remotePath, localPath], {
|
|
162
|
+
timeoutMs: PULL_TIMEOUT_MS,
|
|
163
|
+
});
|
|
164
|
+
if (pull.exitCode !== 0) {
|
|
165
|
+
throw new errors_js_1.UntrustedHelperError(`Could not pull installed helper APK for verification (${remotePath})`, { expectedSignerSha256: artifact.signerSha256 });
|
|
166
|
+
}
|
|
167
|
+
await (0, signer_js_1.assertApkSignerSha256)(localPath, artifact.signerSha256);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
(0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Install/upgrade the bundled helper by versionCode (freshness only).
|
|
176
|
+
*
|
|
177
|
+
* versionCode is attacker-controlled metadata, so this is NOT a trust decision:
|
|
178
|
+
* it only decides whether a fresher build should be pushed. It performs NO
|
|
179
|
+
* signer verification. Callers MUST separately call
|
|
180
|
+
* {@link assertInstalledHelperTrusted} before every use of the helper.
|
|
181
|
+
*
|
|
182
|
+
* This is safe to gate behind a per-process "installed" cache because a stale
|
|
183
|
+
* cache at worst skips a redundant (idempotent) install; it never skips a trust
|
|
184
|
+
* check.
|
|
185
|
+
*
|
|
186
|
+
* @param serial - The target device serial.
|
|
187
|
+
* @throws When install fails for a generic reason.
|
|
188
|
+
*/
|
|
189
|
+
async function ensureHelperInstalled(serial) {
|
|
190
|
+
const artifact = await (0, artifact_js_1.loadHelperArtifact)();
|
|
191
|
+
const installedVersion = await readInstalledVersionCode(serial);
|
|
192
|
+
if (installedVersion === null || installedVersion < artifact.versionCode) {
|
|
193
|
+
await installHelper(serial, artifact);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Verify that the helper currently installed on the device is signed by the
|
|
198
|
+
* pinned certificate. This is the trust invariant and MUST run before EVERY
|
|
199
|
+
* `am instrument`, never cached: package name and versionCode are
|
|
200
|
+
* attacker-controlled, so an actor who can install packages could replace a
|
|
201
|
+
* previously-trusted helper with a malicious same-package instrumentation
|
|
202
|
+
* between snapshots. Re-verifying the installed signer on every use (pm path +
|
|
203
|
+
* pull + hash — cheap relative to the instrumentation run) is what keeps the
|
|
204
|
+
* signature pin a per-use invariant instead of a one-time check.
|
|
205
|
+
*
|
|
206
|
+
* @param serial - The target device serial.
|
|
207
|
+
* @throws {@link UntrustedHelperError} when the installed helper is signed by
|
|
208
|
+
* an unexpected certificate (a squatter). The caller MUST NOT fall back to
|
|
209
|
+
* `uiautomator dump` in that case.
|
|
210
|
+
*/
|
|
211
|
+
async function assertInstalledHelperTrusted(serial) {
|
|
212
|
+
const artifact = await (0, artifact_js_1.loadHelperArtifact)();
|
|
213
|
+
await verifyInstalledSigner(serial, artifact);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Ensure a TRUSTED helper is installed: install/upgrade by versionCode
|
|
217
|
+
* (freshness), then unconditionally verify the installed signer against the
|
|
218
|
+
* pinned certificate. Fails closed on a signer mismatch.
|
|
219
|
+
*
|
|
220
|
+
* Convenience wrapper over {@link ensureHelperInstalled} +
|
|
221
|
+
* {@link assertInstalledHelperTrusted}. Callers that cache the install step
|
|
222
|
+
* MUST still call {@link assertInstalledHelperTrusted} on every use rather than
|
|
223
|
+
* caching this whole call.
|
|
224
|
+
*
|
|
225
|
+
* @param serial - The target device serial.
|
|
226
|
+
* @throws {@link UntrustedHelperError} when the installed helper is signed by
|
|
227
|
+
* an unexpected certificate (a squatter). The caller MUST NOT fall back to
|
|
228
|
+
* `uiautomator dump` in that case.
|
|
229
|
+
* @throws When install fails for a generic reason.
|
|
230
|
+
*/
|
|
231
|
+
async function ensureHelperTrusted(serial) {
|
|
232
|
+
await ensureHelperInstalled(serial);
|
|
233
|
+
await assertInstalledHelperTrusted(serial);
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=installer.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installer.cjs","sourceRoot":"","sources":["../../../src/backends/android-instrumentation/installer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Installs and TRUSTS the bundled Android snapshot-helper instrumentation APK.\n *\n * The APK is built during `prepack` into `dist/android/` and shipped with the\n * npm package (`files: [\"dist\"]`). Before the ADB backend runs `am instrument`\n * it must guarantee that the package on the device is OUR helper — not a\n * malicious app squatting the package name. Package name and versionCode are\n * attacker-controlled metadata; the only trust anchor is the signing\n * certificate. So `ensureHelperTrusted` installs/upgrades by versionCode\n * (freshness only) and then UNCONDITIONALLY verifies the installed APK's v2/v3\n * signer against the pinned SHA-256 from the build manifest. A mismatch throws\n * {@link UntrustedHelperError} and the caller must fail closed (no dump\n * fallback) — see `adb-backend.ts`.\n *\n * `__dirname` is used for path resolution because it is portable across the\n * dual ESM/CJS build: ts-bridge rewrites it to an `import.meta.url`-based shim\n * in the `.mjs` output and leaves the native binding in the `.cjs` output.\n *\n * Version/policy/signer-verify flow adapted from agent-device (MIT, Copyright\n * (c) Callstack) onto our source-built APK and pure-JS signer verifier.\n */\n\nimport { rmSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { createAndroidAdbExecutor } from './adb.js';\nimport { loadHelperArtifact } from './artifact.js';\nimport type { HelperArtifact } from './artifact.js';\nimport { UntrustedHelperError } from './errors.js';\nimport { assertApkSignerSha256 } from './signer.js';\nimport { HELPER_PACKAGE } from './snapshot.js';\nimport { exec } from '../../utils/exec.js';\nimport { createPrivateTempDir } from '../../utils/output-path.js';\n\nconst INSTALL_TIMEOUT_MS = 60_000;\n\nconst QUERY_TIMEOUT_MS = 10_000;\n\nconst PULL_TIMEOUT_MS = 30_000;\n\n/**\n * `am instrument` prints this when the target instrumentation is not installed.\n * The ADB backend keys reactive re-installs off this signature.\n */\nexport const INSTRUMENTATION_NOT_FOUND_SIGNATURE = 'INSTRUMENTATION_FAILED';\n\n/**\n * Resolve the path to the bundled helper APK (validated against its manifest).\n *\n * @returns The absolute path to the APK.\n * @throws When the bundled artifact is missing, malformed, or its digest does\n * not match the manifest.\n */\nexport async function resolveHelperApkPath(): Promise<string> {\n const artifact = await loadHelperArtifact();\n return artifact.apkPath;\n}\n\n/**\n * Check whether the helper package is present on the device.\n *\n * @param serial - The target device serial.\n * @returns True when the helper package is installed (any signer).\n */\nexport async function isHelperInstalled(serial: string): Promise<boolean> {\n const result = await exec(\n 'adb',\n ['-s', serial, 'shell', 'pm', 'list', 'packages', HELPER_PACKAGE],\n { timeoutMs: QUERY_TIMEOUT_MS },\n );\n return result.stdout.includes(`package:${HELPER_PACKAGE}`);\n}\n\n/**\n * Read the installed helper's versionCode via `dumpsys package`.\n *\n * versionCode is attacker-controlled metadata: it is used ONLY to decide\n * whether to (re)install a fresher build, never as a trust signal.\n *\n * @param serial - The target device serial.\n * @returns The installed versionCode, or null when not installed / unreadable.\n */\nasync function readInstalledVersionCode(\n serial: string,\n): Promise<number | null> {\n const result = await exec(\n 'adb',\n ['-s', serial, 'shell', 'dumpsys', 'package', HELPER_PACKAGE],\n { timeoutMs: QUERY_TIMEOUT_MS },\n );\n const match = /versionCode=(\\d+)/u.exec(result.stdout);\n return match ? Number(match[1]) : null;\n}\n\n/**\n * Android install-result tokens that specifically indicate the already-\n * installed same-package app was signed by a DIFFERENT certificate, so the\n * reinstall was refused on trust grounds (not a generic install failure). These\n * constant names are stable across API 23-35 and are printed by PackageManager\n * in the `Failure [<TOKEN>: ...]` line (on stdout; we scan both streams).\n *\n * Deliberately excludes generic failures like INSTALL_FAILED_TEST_ONLY,\n * INSTALL_FAILED_VERSION_DOWNGRADE, INSTALL_FAILED_DUPLICATE_PERMISSION, and\n * the parse-time no/inconsistent-certificate codes: none of those mean a\n * squatter is signed by a different key on a normal reinstall.\n */\nconst SIGNER_CONFLICT_TOKENS = [\n 'INSTALL_FAILED_UPDATE_INCOMPATIBLE',\n 'INSTALL_FAILED_SHARED_USER_INCOMPATIBLE',\n];\n\n/**\n * Install the bundled helper APK onto the device.\n *\n * `-r` reinstalls over an existing copy signed by the SAME key and `-t` allows\n * the `testOnly` APK. It CANNOT overwrite a package signed by a different key:\n * Android refuses with `INSTALL_FAILED_UPDATE_INCOMPATIBLE` (or\n * `INSTALL_FAILED_SHARED_USER_INCOMPATIBLE`). That is a squatter signed by a\n * foreign certificate, so it is a TRUST failure, not a generic one — we throw\n * {@link UntrustedHelperError} to make the caller fail closed, consistent with\n * the equal/higher-versionCode squatter path (which fails closed via the signer\n * verification). Without this, a lower-versionCode squatter would surface as a\n * generic Error and `auto` mode would silently fall back to `uiautomator dump`.\n *\n * @param serial - The target device serial.\n * @param artifact - The bundled artifact carrying the pinned signer SHA-256,\n * used both to locate the APK and to describe a trust failure.\n * @throws {@link UntrustedHelperError} when the install is refused because the\n * installed package is signed by a different certificate.\n * @throws When the install command fails for a generic reason.\n */\nexport async function installHelper(\n serial: string,\n artifact?: HelperArtifact,\n): Promise<void> {\n const resolved = artifact ?? (await loadHelperArtifact());\n const { apkPath } = resolved;\n const result = await exec(\n 'adb',\n ['-s', serial, 'install', '-r', '-t', apkPath],\n { timeoutMs: INSTALL_TIMEOUT_MS },\n );\n if (result.exitCode !== 0 || !result.stdout.includes('Success')) {\n const output = `${result.stdout.trim()} ${result.stderr.trim()}`.trim();\n if (SIGNER_CONFLICT_TOKENS.some((token) => output.includes(token))) {\n throw new UntrustedHelperError(\n `Snapshot helper install was refused because the installed package is ` +\n `signed by a different certificate (a squatter). ${output}`,\n { expectedSignerSha256: resolved.signerSha256 },\n );\n }\n throw new Error(\n `Failed to install snapshot helper APK (${apkPath}).\\n${output}`,\n );\n }\n}\n\n/**\n * Pull every installed APK path for the helper package (base + splits) into a\n * private temp dir and verify each is signed by the pinned certificate.\n *\n * @param serial - The target device serial.\n * @param artifact - The bundled artifact carrying the pinned signer SHA-256.\n * @throws {@link UntrustedHelperError} when the installed signer does not match\n * the pin.\n */\nexport async function verifyInstalledSigner(\n serial: string,\n artifact: HelperArtifact,\n): Promise<void> {\n const run = createAndroidAdbExecutor(serial);\n const pathsResult = await run(['shell', 'pm', 'path', HELPER_PACKAGE], {\n timeoutMs: QUERY_TIMEOUT_MS,\n });\n const remotePaths = pathsResult.stdout\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line.startsWith('package:'))\n .map((line) => line.slice('package:'.length));\n if (remotePaths.length === 0) {\n throw new UntrustedHelperError(\n 'Snapshot helper is installed but its APK path could not be resolved',\n { expectedSignerSha256: artifact.signerSha256 },\n );\n }\n\n const tempDir = createPrivateTempDir('helper-verify');\n try {\n let index = 0;\n for (const remotePath of remotePaths) {\n const localPath = join(tempDir, `apk-${index}.apk`);\n index += 1;\n const pull = await run(['pull', remotePath, localPath], {\n timeoutMs: PULL_TIMEOUT_MS,\n });\n if (pull.exitCode !== 0) {\n throw new UntrustedHelperError(\n `Could not pull installed helper APK for verification (${remotePath})`,\n { expectedSignerSha256: artifact.signerSha256 },\n );\n }\n await assertApkSignerSha256(localPath, artifact.signerSha256);\n }\n } finally {\n rmSync(tempDir, { recursive: true, force: true });\n }\n}\n\n/**\n * Install/upgrade the bundled helper by versionCode (freshness only).\n *\n * versionCode is attacker-controlled metadata, so this is NOT a trust decision:\n * it only decides whether a fresher build should be pushed. It performs NO\n * signer verification. Callers MUST separately call\n * {@link assertInstalledHelperTrusted} before every use of the helper.\n *\n * This is safe to gate behind a per-process \"installed\" cache because a stale\n * cache at worst skips a redundant (idempotent) install; it never skips a trust\n * check.\n *\n * @param serial - The target device serial.\n * @throws When install fails for a generic reason.\n */\nexport async function ensureHelperInstalled(serial: string): Promise<void> {\n const artifact = await loadHelperArtifact();\n const installedVersion = await readInstalledVersionCode(serial);\n\n if (installedVersion === null || installedVersion < artifact.versionCode) {\n await installHelper(serial, artifact);\n }\n}\n\n/**\n * Verify that the helper currently installed on the device is signed by the\n * pinned certificate. This is the trust invariant and MUST run before EVERY\n * `am instrument`, never cached: package name and versionCode are\n * attacker-controlled, so an actor who can install packages could replace a\n * previously-trusted helper with a malicious same-package instrumentation\n * between snapshots. Re-verifying the installed signer on every use (pm path +\n * pull + hash — cheap relative to the instrumentation run) is what keeps the\n * signature pin a per-use invariant instead of a one-time check.\n *\n * @param serial - The target device serial.\n * @throws {@link UntrustedHelperError} when the installed helper is signed by\n * an unexpected certificate (a squatter). The caller MUST NOT fall back to\n * `uiautomator dump` in that case.\n */\nexport async function assertInstalledHelperTrusted(\n serial: string,\n): Promise<void> {\n const artifact = await loadHelperArtifact();\n await verifyInstalledSigner(serial, artifact);\n}\n\n/**\n * Ensure a TRUSTED helper is installed: install/upgrade by versionCode\n * (freshness), then unconditionally verify the installed signer against the\n * pinned certificate. Fails closed on a signer mismatch.\n *\n * Convenience wrapper over {@link ensureHelperInstalled} +\n * {@link assertInstalledHelperTrusted}. Callers that cache the install step\n * MUST still call {@link assertInstalledHelperTrusted} on every use rather than\n * caching this whole call.\n *\n * @param serial - The target device serial.\n * @throws {@link UntrustedHelperError} when the installed helper is signed by\n * an unexpected certificate (a squatter). The caller MUST NOT fall back to\n * `uiautomator dump` in that case.\n * @throws When install fails for a generic reason.\n */\nexport async function ensureHelperTrusted(serial: string): Promise<void> {\n await ensureHelperInstalled(serial);\n await assertInstalledHelperTrusted(serial);\n}\n"]}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installs and TRUSTS the bundled Android snapshot-helper instrumentation APK.
|
|
3
|
+
*
|
|
4
|
+
* The APK is built during `prepack` into `dist/android/` and shipped with the
|
|
5
|
+
* npm package (`files: ["dist"]`). Before the ADB backend runs `am instrument`
|
|
6
|
+
* it must guarantee that the package on the device is OUR helper — not a
|
|
7
|
+
* malicious app squatting the package name. Package name and versionCode are
|
|
8
|
+
* attacker-controlled metadata; the only trust anchor is the signing
|
|
9
|
+
* certificate. So `ensureHelperTrusted` installs/upgrades by versionCode
|
|
10
|
+
* (freshness only) and then UNCONDITIONALLY verifies the installed APK's v2/v3
|
|
11
|
+
* signer against the pinned SHA-256 from the build manifest. A mismatch throws
|
|
12
|
+
* {@link UntrustedHelperError} and the caller must fail closed (no dump
|
|
13
|
+
* fallback) — see `adb-backend.ts`.
|
|
14
|
+
*
|
|
15
|
+
* `__dirname` is used for path resolution because it is portable across the
|
|
16
|
+
* dual ESM/CJS build: ts-bridge rewrites it to an `import.meta.url`-based shim
|
|
17
|
+
* in the `.mjs` output and leaves the native binding in the `.cjs` output.
|
|
18
|
+
*
|
|
19
|
+
* Version/policy/signer-verify flow adapted from agent-device (MIT, Copyright
|
|
20
|
+
* (c) Callstack) onto our source-built APK and pure-JS signer verifier.
|
|
21
|
+
*/
|
|
22
|
+
import type { HelperArtifact } from "./artifact.cjs";
|
|
23
|
+
/**
|
|
24
|
+
* `am instrument` prints this when the target instrumentation is not installed.
|
|
25
|
+
* The ADB backend keys reactive re-installs off this signature.
|
|
26
|
+
*/
|
|
27
|
+
export declare const INSTRUMENTATION_NOT_FOUND_SIGNATURE = "INSTRUMENTATION_FAILED";
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the path to the bundled helper APK (validated against its manifest).
|
|
30
|
+
*
|
|
31
|
+
* @returns The absolute path to the APK.
|
|
32
|
+
* @throws When the bundled artifact is missing, malformed, or its digest does
|
|
33
|
+
* not match the manifest.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveHelperApkPath(): Promise<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Check whether the helper package is present on the device.
|
|
38
|
+
*
|
|
39
|
+
* @param serial - The target device serial.
|
|
40
|
+
* @returns True when the helper package is installed (any signer).
|
|
41
|
+
*/
|
|
42
|
+
export declare function isHelperInstalled(serial: string): Promise<boolean>;
|
|
43
|
+
/**
|
|
44
|
+
* Install the bundled helper APK onto the device.
|
|
45
|
+
*
|
|
46
|
+
* `-r` reinstalls over an existing copy signed by the SAME key and `-t` allows
|
|
47
|
+
* the `testOnly` APK. It CANNOT overwrite a package signed by a different key:
|
|
48
|
+
* Android refuses with `INSTALL_FAILED_UPDATE_INCOMPATIBLE` (or
|
|
49
|
+
* `INSTALL_FAILED_SHARED_USER_INCOMPATIBLE`). That is a squatter signed by a
|
|
50
|
+
* foreign certificate, so it is a TRUST failure, not a generic one — we throw
|
|
51
|
+
* {@link UntrustedHelperError} to make the caller fail closed, consistent with
|
|
52
|
+
* the equal/higher-versionCode squatter path (which fails closed via the signer
|
|
53
|
+
* verification). Without this, a lower-versionCode squatter would surface as a
|
|
54
|
+
* generic Error and `auto` mode would silently fall back to `uiautomator dump`.
|
|
55
|
+
*
|
|
56
|
+
* @param serial - The target device serial.
|
|
57
|
+
* @param artifact - The bundled artifact carrying the pinned signer SHA-256,
|
|
58
|
+
* used both to locate the APK and to describe a trust failure.
|
|
59
|
+
* @throws {@link UntrustedHelperError} when the install is refused because the
|
|
60
|
+
* installed package is signed by a different certificate.
|
|
61
|
+
* @throws When the install command fails for a generic reason.
|
|
62
|
+
*/
|
|
63
|
+
export declare function installHelper(serial: string, artifact?: HelperArtifact): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Pull every installed APK path for the helper package (base + splits) into a
|
|
66
|
+
* private temp dir and verify each is signed by the pinned certificate.
|
|
67
|
+
*
|
|
68
|
+
* @param serial - The target device serial.
|
|
69
|
+
* @param artifact - The bundled artifact carrying the pinned signer SHA-256.
|
|
70
|
+
* @throws {@link UntrustedHelperError} when the installed signer does not match
|
|
71
|
+
* the pin.
|
|
72
|
+
*/
|
|
73
|
+
export declare function verifyInstalledSigner(serial: string, artifact: HelperArtifact): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Install/upgrade the bundled helper by versionCode (freshness only).
|
|
76
|
+
*
|
|
77
|
+
* versionCode is attacker-controlled metadata, so this is NOT a trust decision:
|
|
78
|
+
* it only decides whether a fresher build should be pushed. It performs NO
|
|
79
|
+
* signer verification. Callers MUST separately call
|
|
80
|
+
* {@link assertInstalledHelperTrusted} before every use of the helper.
|
|
81
|
+
*
|
|
82
|
+
* This is safe to gate behind a per-process "installed" cache because a stale
|
|
83
|
+
* cache at worst skips a redundant (idempotent) install; it never skips a trust
|
|
84
|
+
* check.
|
|
85
|
+
*
|
|
86
|
+
* @param serial - The target device serial.
|
|
87
|
+
* @throws When install fails for a generic reason.
|
|
88
|
+
*/
|
|
89
|
+
export declare function ensureHelperInstalled(serial: string): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Verify that the helper currently installed on the device is signed by the
|
|
92
|
+
* pinned certificate. This is the trust invariant and MUST run before EVERY
|
|
93
|
+
* `am instrument`, never cached: package name and versionCode are
|
|
94
|
+
* attacker-controlled, so an actor who can install packages could replace a
|
|
95
|
+
* previously-trusted helper with a malicious same-package instrumentation
|
|
96
|
+
* between snapshots. Re-verifying the installed signer on every use (pm path +
|
|
97
|
+
* pull + hash — cheap relative to the instrumentation run) is what keeps the
|
|
98
|
+
* signature pin a per-use invariant instead of a one-time check.
|
|
99
|
+
*
|
|
100
|
+
* @param serial - The target device serial.
|
|
101
|
+
* @throws {@link UntrustedHelperError} when the installed helper is signed by
|
|
102
|
+
* an unexpected certificate (a squatter). The caller MUST NOT fall back to
|
|
103
|
+
* `uiautomator dump` in that case.
|
|
104
|
+
*/
|
|
105
|
+
export declare function assertInstalledHelperTrusted(serial: string): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Ensure a TRUSTED helper is installed: install/upgrade by versionCode
|
|
108
|
+
* (freshness), then unconditionally verify the installed signer against the
|
|
109
|
+
* pinned certificate. Fails closed on a signer mismatch.
|
|
110
|
+
*
|
|
111
|
+
* Convenience wrapper over {@link ensureHelperInstalled} +
|
|
112
|
+
* {@link assertInstalledHelperTrusted}. Callers that cache the install step
|
|
113
|
+
* MUST still call {@link assertInstalledHelperTrusted} on every use rather than
|
|
114
|
+
* caching this whole call.
|
|
115
|
+
*
|
|
116
|
+
* @param serial - The target device serial.
|
|
117
|
+
* @throws {@link UntrustedHelperError} when the installed helper is signed by
|
|
118
|
+
* an unexpected certificate (a squatter). The caller MUST NOT fall back to
|
|
119
|
+
* `uiautomator dump` in that case.
|
|
120
|
+
* @throws When install fails for a generic reason.
|
|
121
|
+
*/
|
|
122
|
+
export declare function ensureHelperTrusted(serial: string): Promise<void>;
|
|
123
|
+
//# sourceMappingURL=installer.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installer.d.cts","sourceRoot":"","sources":["../../../src/backends/android-instrumentation/installer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAsB;AAapD;;;GAGG;AACH,eAAO,MAAM,mCAAmC,2BAA2B,CAAC;AAE5E;;;;;;GAMG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAG5D;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOxE;AAwCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAqCf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOzE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAGf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvE"}
|