Skip to content
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

Fix: LW-12003 #1614

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['lodash'],
plugins: ['lodash', 'local-rules'],
extends: [
'@atixlabs/eslint-config/configurations/react',
'plugin:@typescript-eslint/recommended',
Expand Down Expand Up @@ -33,7 +33,9 @@ module.exports = {
'@typescript-eslint/no-explicit-any': ['error'],
'no-console': ['error', { allow: ['warn', 'error', 'info', 'debug'] }],
'lodash/import-scope': ['error', 'method'],
'promise/avoid-new': 'off'
'promise/avoid-new': 'off',
// detect + prevent usage of sensitive props from useSecrets being memoized
'local-rules/prevent-memoization-of-sensitive-values': 'error'
},
overrides: [
{
Expand Down
3 changes: 3 additions & 0 deletions eslint-local-rules/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
'prevent-memoization-of-sensitive-values': require('./prevent-memoization-of-sensitive-values')
};
61 changes: 61 additions & 0 deletions eslint-local-rules/prevent-memoization-of-sensitive-values.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Disallow sensitive values from useSecrets in useCallback or useMemo dependencies',
recommended: false
},
messages: {
disallowedDependency: "Avoid using '{{value}}' from useSecrets in {{type}} dependencies."
},
schema: [] // No options needed for this rule
},

create(context) {
const sensitiveProperties = ['password', 'passwordConfirmation', 'passwordRepeat'];

let declaredSecrets = new Set();

return {
VariableDeclarator(node) {
// Detect `const { password, passwordConfirmation } = useSecrets();`
if (
node.init &&
node.init.type === 'CallExpression' &&
node.init.callee.name === 'useSecrets' &&
node.id.type === 'ObjectPattern'
) {
node.id.properties.forEach((property) => {
if (property.type === 'Property' && sensitiveProperties.includes(property.key.name)) {
declaredSecrets.add(property.key.name);
}
});
}
},

CallExpression(node) {
// Detect calls to `useCallback` or `useMemo`
if (node.callee.name === 'useCallback' || node.callee.name === 'useMemo') {
const dependencies = node.arguments[1];

if (dependencies && dependencies.type === 'ArrayExpression') {
dependencies.elements.forEach((element) => {
if (element && element.type === 'Identifier' && declaredSecrets.has(element.name)) {
context.report({
node: element,
messageId: 'disallowedDependency',
data: { value: element.name, type: node.callee.name }
});
}
});
}
}
},

'Program:exit'() {
// Clean up the declaredSecrets set at the end of the program
declaredSecrets.clear();
}
};
}
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jest": "^24.4.0",
"eslint-plugin-local-rules": "^3.0.2",
"eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-promise": "^5.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Feature: Wallet accounts
And I click "Confirm" button on the account unlock drawer
Then I see "Account #3 activated" toast
And I wait for main loader to disappear
And valid password is not in snapshot
And I do not see account unlock drawer with all elements in extended mode
When I click "Receive" button on page header
Then I see "Wallet Address" page in extended mode for account: 3 and wallet "MultiAccActive1"
Expand Down
25 changes: 25 additions & 0 deletions packages/e2e-tests/src/steps/commonSteps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
import MainLoader from '../elements/MainLoader';
import Modal from '../elements/modal';
import { setCameraAccessPermission } from '../utils/browserPermissionsUtils';
import { findNeedleInJSONKeyOrValue } from '../utils/textUtils';

Given(/^Lace is ready for test$/, async () => {
await MainLoader.waitUntilLoaderDisappears();
Expand Down Expand Up @@ -431,3 +432,27 @@ When(
await browser.refresh();
}
);

Then(
/(invalid|valid|N_8J@bne87A) password is not in snapshot/,
async (password: 'invalid' | 'valid' | 'N_8J@bne87A') => {
await browser.cdp('HeapProfiler', 'collectGarbage');
const snapshot = await browser.takeHeapSnapshot();

let needle = '';
switch (password) {
case 'valid':
needle = String(getTestWallet(TestWalletName.MultiAccActive1).password);
break;
case 'invalid':
needle = 'somePassword';
break;
case 'N_8J@bne87A':
needle = 'N_8J@bne87A';
break;
}
const needlesFound = findNeedleInJSONKeyOrValue(snapshot, needle);

expect(needlesFound.length).toEqual(0);
}
);
52 changes: 52 additions & 0 deletions packages/e2e-tests/src/utils/textUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,55 @@ export const generateRandomString = async (length: number): Promise<string> =>
.randomBytes(Math.ceil(length / 2))
.toString('hex')
.slice(0, length);

export type JSONValue = string | number | boolean | null | undefined | JSONValue[] | { [key: string]: JSONValue };

interface Match {
path: string; // Path to the matched key or value
key?: string; // The key where the match occurred
value?: JSONValue; // The value where the match occurred
}

/**
* Recursively searches for a specific needle in the keys or values of a JSON structure.
* @param data - The JSON structure to search.
* @param searchString - The string to search for.
* @param currentPath - (Internal) The current path in the structure during recursion.
* @returns An array of matches, including the path and matched key/value.
*/
export const findNeedleInJSONKeyOrValue = (
data: JSONValue,
searchString: string,
currentPath: string[] = []
): Match[] => {
const matches: Match[] = [];

const traverse = (value: JSONValue, path: string[]) => {
if (value && typeof value === 'object') {
if (Array.isArray(value)) {
// Handle arrays
value.forEach((item, index) => {
traverse(item, [...path, index.toString()]);
});
} else {
// Handle objects
Object.entries(value).forEach(([key, val]) => {
const keyLower = key.toLowerCase();
if (keyLower.includes(searchString)) {
matches.push({ path: [...path, key].join('.'), key });
}
if (typeof val === 'string' && val.toLowerCase().includes(searchString)) {
matches.push({
path: [...path, key].join('.'),
value: val
});
}
traverse(val, [...path, key]);
});
}
}
};

traverse(data, currentPath);
return matches;
};
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -35984,6 +35984,13 @@ __metadata:
languageName: node
linkType: hard

"eslint-plugin-local-rules@npm:^3.0.2":
version: 3.0.2
resolution: "eslint-plugin-local-rules@npm:3.0.2"
checksum: ad883be0739f022bcd0f49de45354c792bc2ef23ea0030789c3f90b10288b4346ab509fbfd451e87282c0f92393251e51a5a69a2f3ba3865a1a1f780cebef7cb
languageName: node
linkType: hard

"eslint-plugin-lodash@npm:^7.4.0":
version: 7.4.0
resolution: "eslint-plugin-lodash@npm:7.4.0"
Expand Down Expand Up @@ -43927,6 +43934,7 @@ __metadata:
eslint-plugin-filenames: ^1.3.2
eslint-plugin-import: ^2.23.4
eslint-plugin-jest: ^24.4.0
eslint-plugin-local-rules: ^3.0.2
eslint-plugin-lodash: ^7.4.0
eslint-plugin-prettier: ^4.0.0
eslint-plugin-promise: ^5.1.0
Expand Down
Loading