Surfaced as a deferred follow-up from PR #8446.
Problem
`DaemonSpawnConfig.password` and `DaemonSpawnConfig.srp` are typed as plain `string`. They flow through several layers:
- oclif flag parsing in `packages/wallet-cli/src/commands/daemon/start.ts`
- `MM_WALLET_PASSWORD` / `MM_WALLET_SRP` env-var reads in `packages/wallet-cli/src/daemon/daemon-entry.ts`
- `createWallet` config in `packages/wallet-cli/src/daemon/wallet-factory.ts`
- `importSecretRecoveryPhrase` in `@metamask/wallet`
At every hop the value is a `string` with no friction against accidental logging, JSON serialization, or display. The next "log the spawn config for debugging" PR is one line away from leaking the SRP into `daemon.log`.
Proposed direction
Introduce opaque branded types in `packages/wallet-cli/src/daemon/types.ts` (or a new `secrets.ts`):
```ts
declare const PasswordBrand: unique symbol;
export type Password = string & { readonly [PasswordBrand]: true };
export function asPassword(value: string): Password { /* validate non-empty */ }
declare const SrpBrand: unique symbol;
export type Srp = string & { readonly [SrpBrand]: true };
export function asSrp(value: string): Srp { /* validate BIP-39 word count */ }
```
Optionally wrap each in a small class with a redacting `toString` / `util.inspect.custom` so accidental `console.log`/`JSON.stringify` prints `'[redacted]'` instead of the value. `importSecretRecoveryPhrase` in `@metamask/wallet` may need its signature updated to accept the branded types (or stay on `string` and let the CLI `unwrap()` at the boundary).
Acceptance
- `DaemonSpawnConfig`, `start.ts` flags, env-var reads, `createWallet` config, and any other call sites use `Password`/`Srp` instead of `string`.
- A unit test demonstrates that a `util.inspect` of a value typed as `Password` does not leak the underlying string.
- No new prod logger paths can compile while passing a `Password`/`Srp` directly.
Context
Surfaced as a deferred follow-up from PR #8446.
Problem
`DaemonSpawnConfig.password` and `DaemonSpawnConfig.srp` are typed as plain `string`. They flow through several layers:
At every hop the value is a `string` with no friction against accidental logging, JSON serialization, or display. The next "log the spawn config for debugging" PR is one line away from leaking the SRP into `daemon.log`.
Proposed direction
Introduce opaque branded types in `packages/wallet-cli/src/daemon/types.ts` (or a new `secrets.ts`):
```ts
declare const PasswordBrand: unique symbol;
export type Password = string & { readonly [PasswordBrand]: true };
export function asPassword(value: string): Password { /* validate non-empty */ }
declare const SrpBrand: unique symbol;
export type Srp = string & { readonly [SrpBrand]: true };
export function asSrp(value: string): Srp { /* validate BIP-39 word count */ }
```
Optionally wrap each in a small class with a redacting `toString` / `util.inspect.custom` so accidental `console.log`/`JSON.stringify` prints `'[redacted]'` instead of the value. `importSecretRecoveryPhrase` in `@metamask/wallet` may need its signature updated to accept the branded types (or stay on `string` and let the CLI `unwrap()` at the boundary).
Acceptance
Context