Skip to content

Commit

Permalink
Merge pull request #1171 from oasisprotocol/lw/dont-store-sensitive
Browse files Browse the repository at this point in the history
Prevent browsers from writing sensitive form inputs to user data
  • Loading branch information
lukaw3d authored Nov 24, 2022
2 parents 1cf589d + cb64546 commit 32fd270
Show file tree
Hide file tree
Showing 13 changed files with 50 additions and 16 deletions.
2 changes: 2 additions & 0 deletions src/app/components/MnemonicField/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react'
import { FormField, TextArea } from 'grommet'
import { preventSavingInputsToUserData } from 'app/lib/preventSavingInputsToUserData'

interface Props {
placeholder?: string
Expand All @@ -24,6 +25,7 @@ export function MnemonicField(props: Props) {
size="medium"
rows={5}
fill
{...preventSavingInputsToUserData}
/>
</FormField>
)
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/MnemonicValidation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Header } from 'app/components/Header'
import { MnemonicField } from 'app/components/MnemonicField'
import { preventSavingInputsToUserData } from 'app/lib/preventSavingInputsToUserData'

interface Props {
/** Called once the mnemonic is confirmed */
Expand Down Expand Up @@ -42,7 +43,7 @@ export function MnemonicValidation(props: Props) {
border={{ color: 'background-front-border', size: '1px' }}
>
<Grid gap="small" pad="none" columns={size === 'small' ? '100%' : ['1fr', '1fr']}>
<Form onSubmit={onSubmit}>
<Form onSubmit={onSubmit} {...preventSavingInputsToUserData}>
<Header>{t('openWallet.mnemonic.header', 'Enter your keyphrase')}</Header>
<Paragraph>
{t(
Expand Down
5 changes: 3 additions & 2 deletions src/app/components/PasswordField/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { preventSavingInputsToUserData } from 'app/lib/preventSavingInputsToUserData'
import { Form, FormField, TextInput } from 'grommet'
import * as React from 'react'
import { PasswordField } from '..'
Expand All @@ -18,6 +19,7 @@ describe('<PasswordField />', () => {
expect(value.privateKey.length).toBeGreaterThanOrEqual(5)
props.onSubmit(value)
}}
{...preventSavingInputsToUserData}
>
<FormField name="name">
<TextInput name="name" value="name" />
Expand All @@ -26,7 +28,6 @@ describe('<PasswordField />', () => {
inputElementId="privateKey"
name="privateKey"
label="privateKey"
autoComplete="current-password"
validate={(privateKey, form) => {
return privateKey.length < 5 ? 'invalid' : undefined
}}
Expand Down Expand Up @@ -60,6 +61,7 @@ describe('<PasswordField />', () => {
// expect(privateKey.length).toBeGreaterThanOrEqual(5)
props.onSubmit({ name, privateKey })
}}
{...preventSavingInputsToUserData}
>
<FormField name="name">
<TextInput name="name" value={name} onChange={event => setName(event.target.value)} />
Expand All @@ -68,7 +70,6 @@ describe('<PasswordField />', () => {
inputElementId="privateKey"
name="privateKey"
label="privateKey"
autoComplete="off"
value={privateKey}
onChange={event => setPrivateKey(event.target.value)}
error={privateKey.length < 5 ? 'invalid' : undefined}
Expand Down
7 changes: 3 additions & 4 deletions src/app/components/PasswordField/__tests__/type-only.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { preventSavingInputsToUserData } from 'app/lib/preventSavingInputsToUserData'
import { Form, FormField, TextInput } from 'grommet'
import * as React from 'react'
import { PasswordField } from '..'
Expand All @@ -16,6 +17,7 @@ describe('type-only test', () => {
expect(value.privateKey !== 5).toBeTruthy()
expect(value.privateKey !== '5').toBeTruthy()
}}
{...preventSavingInputsToUserData}
>
<FormField name="name">
<TextInput name="name" value="name" />
Expand All @@ -24,8 +26,6 @@ describe('type-only test', () => {
inputElementId="privateKey"
name="privateKey"
label="privateKey"
// @ts-expect-error Detect incorrect value
autoComplete="password"
validate={(privateKey, form) => {
// @ts-expect-error Detect incorrect type
expect(privateKey !== 5).toBeTruthy()
Expand All @@ -47,7 +47,6 @@ describe('type-only test', () => {
inputElementId="privateKey3"
// @ts-expect-error Detect missing field
name="privateKey3"
autoComplete="current-password"
showTip="show"
hideTip="hide"
/>
Expand All @@ -63,6 +62,7 @@ describe('type-only test', () => {
// @ts-expect-error Doesn't know about any fields
expect(value.privateKey !== '5').toBeTruthy()
}}
{...preventSavingInputsToUserData}
>
<FormField name="name">
<TextInput name="name" value="name" />
Expand All @@ -71,7 +71,6 @@ describe('type-only test', () => {
inputElementId="privateKey"
name="privateKey"
label="privateKey"
autoComplete="off"
validate={(privateKey, form) => {
// @ts-expect-error Detect incorrect type
expect(privateKey !== 5).toBeTruthy()
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/PasswordField/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { preventSavingInputsToUserData } from 'app/lib/preventSavingInputsToUserData'
import { Box, FormField, Button, TextInput, Tip } from 'grommet'
import { View, Hide } from 'grommet-icons'
import * as React from 'react'
Expand All @@ -8,7 +9,6 @@ interface Props<TFormValue> {
placeholder?: string
inputElementId: string

autoComplete: 'on' | 'off' | 'new-password' | 'current-password'
autoFocus?: boolean
value?: string
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
Expand Down Expand Up @@ -42,9 +42,9 @@ export function PasswordField<TFormValue = any>(props: Props<TFormValue>) {
onChange={props.onChange}
type={passwordIsVisible ? 'text' : 'password'}
required={props.required}
autoComplete={props.autoComplete}
autoFocus={props.autoFocus}
plain
{...preventSavingInputsToUserData}
/>
<Tip content={passwordIsVisible ? props.hideTip : props.showTip}>
<Button
Expand Down
21 changes: 21 additions & 0 deletions src/app/lib/preventSavingInputsToUserData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Usage: Add to sensitive inputs, textareas, and parent forms (because Chrome
* doesn't support it on textareas).
*
* Browsers write visible input fields from any website to user data to enable
* restoring sessions. Browsers exclude inputs with autocomplete="off" from
* cached form data in the session history (even though they ignore it and still
* offer autofill from saved passwords).
*
* To ensure this fixes vulnerability:
* - Manually checked
* grep --text t.e.s.t.M.n.e.m.o.n.i.c -r ~/.config/google-chrome
* grep --text testMnemonic -r ~/.mozilla/firefox
*
* References:
* - https://nvd.nist.gov/vuln/detail/CVE-2022-32969
* - https://coinyuppie.com/slow-mist-a-brief-analysis-of-the-metamask-wallet-demonic-vulnerability/
* - https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
* - https://nvd.nist.gov/vuln/detail/CVE-2022-0167
*/
export const preventSavingInputsToUserData = { autoComplete: 'off' }
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,9 @@ exports[`<FromMnemonic/> should match snapshot 1`] = `
<div
class="c2"
>
<form>
<form
autocomplete="off"
>
<h1
class="c3"
>
Expand All @@ -422,6 +424,7 @@ exports[`<FromMnemonic/> should match snapshot 1`] = `
class="c6 "
>
<textarea
autocomplete="off"
class="c7"
id="mnemonic"
placeholder="openWallet.mnemonic.enterPhraseHere"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,9 @@ exports[`<FromPrivateKey /> should match snapshot 1`] = `
<div
class="c1"
>
<form>
<form
autocomplete="off"
>
<h1
class="c2"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { base64ToUint, uint2hex } from 'app/lib/helpers'
import { useTranslation } from 'react-i18next'
import { PasswordField } from 'app/components/PasswordField'
import { Header } from 'app/components/Header'
import { preventSavingInputsToUserData } from 'app/lib/preventSavingInputsToUserData'

interface Props {}

Expand Down Expand Up @@ -51,7 +52,7 @@ export function FromPrivateKey(props: Props) {
round="5px"
border={{ color: 'background-front-border', size: '1px' }}
>
<Form<FormValue> onSubmit={onSubmit}>
<Form<FormValue> onSubmit={onSubmit} {...preventSavingInputsToUserData}>
<Header>{t('openWallet.privateKey.header', 'Enter your private key')}</Header>
<Paragraph>
<label htmlFor="privatekey">
Expand All @@ -63,7 +64,6 @@ export function FromPrivateKey(props: Props) {
inputElementId="privatekey"
name="privateKey"
placeholder={t('openWallet.privateKey.enterPrivateKeyHere', 'Enter your private key here')}
autoComplete="off"
autoFocus
validate={privateKey =>
isValidKey(privateKey) ? undefined : t('openWallet.privateKey.error', 'Invalid private key')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,9 @@ exports[`<ImportAccountsSelectionModal /> should match snapshot 1`] = `
tabindex="-1"
/>
<div>
<form>
<form
autocomplete="off"
>
<div
class="c5"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { walletActions } from 'app/state/wallet'
import { Box, Button, Form, ResponsiveContext, Spinner, Text } from 'grommet'
import { numberOfAccountPages } from 'app/state/importaccounts/saga'
import { WalletType } from 'app/state/wallet/types'
import { preventSavingInputsToUserData } from 'app/lib/preventSavingInputsToUserData'

interface ImportAccountsSelectorSelectorProps {
accounts: ImportAccountsListAccount[]
Expand Down Expand Up @@ -98,7 +99,7 @@ export function ImportAccountsSelectionModal(props: ImportAccountsSelectionModal

return (
<ResponsiveLayer onEsc={props.abort} onClickOutside={props.abort} modal background="background-front">
<Form<FormValue> onSubmit={openAccounts}>
<Form<FormValue> onSubmit={openAccounts} {...preventSavingInputsToUserData}>
<Box width="800px" pad="medium">
<ModalSplitHeader
side={t('openWallet.importAccounts.accountCounter', '{{ count }} accounts selected', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ exports[`<TransactionRecipient /> should render component 1`] = `
class="c4"
>
<form
autocomplete="off"
style="width: 465px;"
>
<div
Expand Down
3 changes: 2 additions & 1 deletion src/app/pages/ParaTimesPage/TransactionRecipient/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ParaTimeFormFooter } from '../ParaTimeFormFooter'
import { useParaTimes } from '../useParaTimes'
import { useParaTimesNavigation } from '../useParaTimesNavigation'
import { PasswordField } from 'app/components/PasswordField'
import { preventSavingInputsToUserData } from 'app/lib/preventSavingInputsToUserData'

export const TransactionRecipient = () => {
const { t } = useTranslation()
Expand Down Expand Up @@ -56,6 +57,7 @@ export const TransactionRecipient = () => {
onSubmit={navigateToAmount}
value={transactionForm}
style={{ width: isMobile ? '100%' : '465px' }}
{...preventSavingInputsToUserData}
>
<Box margin={{ bottom: 'medium' }}>
{isEvmcParaTime && !isDepositing && (
Expand All @@ -80,7 +82,6 @@ export const TransactionRecipient = () => {
'Enter Ethereum-compatible private key',
)}
value={transactionForm.ethPrivateKey}
autoComplete="off"
showTip={t('openWallet.privateKey.showPrivateKey', 'Show private key')}
hideTip={t('openWallet.privateKey.hidePrivateKey', 'Hide private key')}
/>
Expand Down

0 comments on commit 32fd270

Please sign in to comment.