-
-
Notifications
You must be signed in to change notification settings - Fork 12
feat: add superstruct.sensitive
#577
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
177728c
fd44c23
0943fa9
dfe1c7f
795469d
240fd94
309ca2d
c9ed2e1
e069665
8cc422c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -138,6 +138,36 @@ const config = createConfig([ | |
| }, | ||
| }, | ||
|
|
||
| // @metamask/keyring-api | ||
| { | ||
| files: ['packages/keyring-api/src/**/*.ts'], | ||
| rules: { | ||
| // Prevent importing superstruct's `object` directly. The keyring-utils | ||
| // wrapper must be used instead: it supports `exactOptional()` fields and, | ||
| // critically, redacts sensitive() field values from sibling-failure | ||
| // branches so secrets never appear in error metadata. | ||
| 'no-restricted-imports': [ | ||
| 'error', | ||
| { | ||
| paths: [ | ||
| { | ||
| name: '@metamask/superstruct', | ||
| importNames: ['object'], | ||
| message: | ||
| "Import object() from '@metamask/keyring-utils' instead — it supports exactOptional() and sensitive() field redaction.", | ||
| }, | ||
| { | ||
| name: '@metamask/superstruct', | ||
| importNames: ['exactOptional'], | ||
| message: | ||
| "Import exactOptional() from '@metamask/keyring-utils' instead — mixing superstruct's exactOptional with keyring-utils' object() breaks ExactOptionalize and makes optional fields appear required.", | ||
| }, | ||
|
Comment on lines
+159
to
+164
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, we cannot use the |
||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| }, | ||
|
|
||
| // @metamask/keyring-eth-trezor | ||
| { | ||
| files: ['packages/keyring-eth-trezor/src/**/*.ts'], | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import { SENSITIVE_REDACTED } from '@metamask/keyring-utils'; | ||
| import type { StructError } from '@metamask/superstruct'; | ||
| import { assert } from '@metamask/superstruct'; | ||
|
|
||
| import { PrivateKeyExportedAccountStruct } from './private-key'; | ||
|
|
||
| const RAW_PRIVATE_KEY = | ||
| '0xdeadbeef1234567890abcdef1234567890abcdef1234567890abcdef12345678'; | ||
|
|
||
| describe('PrivateKeyExportedAccountStruct', () => { | ||
| it('accepts a valid exported account', () => { | ||
| expect(() => | ||
| assert( | ||
| { | ||
| type: 'private-key', | ||
| privateKey: RAW_PRIVATE_KEY, | ||
| encoding: 'hexadecimal', | ||
| }, | ||
| PrivateKeyExportedAccountStruct, | ||
| ), | ||
| ).not.toThrow(); | ||
| }); | ||
|
|
||
| it('redacts the private key value from the error when `privateKey` is invalid', () => { | ||
| let error: StructError | undefined; | ||
| try { | ||
| assert( | ||
| { type: 'private-key', privateKey: 123, encoding: 'hexadecimal' }, | ||
| PrivateKeyExportedAccountStruct, | ||
| ); | ||
| } catch (caughtError) { | ||
| error = caughtError as StructError; | ||
| } | ||
| expect(error?.value).toBe(SENSITIVE_REDACTED); | ||
| expect(error?.message).toContain(SENSITIVE_REDACTED); | ||
| expect(error?.message).not.toContain('123'); | ||
| }); | ||
|
|
||
| it('redacts the private key from `branch` when a sibling field fails', () => { | ||
| let error: StructError | undefined; | ||
| try { | ||
| assert( | ||
| { | ||
| type: 'private-key', | ||
| privateKey: RAW_PRIVATE_KEY, | ||
| encoding: 'invalid-encoding', | ||
| }, | ||
| PrivateKeyExportedAccountStruct, | ||
| ); | ||
| } catch (caughtError) { | ||
| error = caughtError as StructError; | ||
| } | ||
| expect(error?.message).toContain('encoding'); | ||
| const allBranchItems = (error?.failures() ?? []).flatMap( | ||
| (failure) => failure.branch, | ||
| ); | ||
| expect(allBranchItems).not.toContainEqual( | ||
| expect.objectContaining({ privateKey: RAW_PRIVATE_KEY }), | ||
| ); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import { literal, object, string } from '@metamask/superstruct'; | ||
| import { object, sensitive } from '@metamask/keyring-utils'; | ||
| import { literal, string } from '@metamask/superstruct'; | ||
| import type { Infer } from '@metamask/superstruct'; | ||
|
|
||
| import { PrivateKeyEncodingStruct } from '../private-key'; | ||
|
|
@@ -14,7 +15,7 @@ export const PrivateKeyExportedAccountStruct = object({ | |
| /** | ||
| * The private key of the exported account. | ||
| */ | ||
| privateKey: string(), | ||
| privateKey: sensitive(string()), | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since it's marked as |
||
| /** | ||
| * The encoding of the exported private key. | ||
| */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This forces us to use our own
objecttype that supports ourexactOptionalAND also allows us to use the newsensitivedecorator.