Skip to content

fix: vulnerabilities #989

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

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require('eslint-plugin-escompat');

module.exports = {
parser: '@babel/eslint-parser',
env : {
Expand Down
11,132 changes: 5,683 additions & 5,449 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"livereload": "grunt livereload",
"serve": "grunt serve",
"start": "grunt start",
"eslint": "eslint --fix \"src/**/*.jsx\" src/"
"eslint": "eslint --fix \"src/**/*.jsx\" src/",
"optimize-svgs": "node scripts/optimize-svgs.js"
},
"repository": {
"type": "git",
Expand All @@ -21,8 +22,8 @@
"homepage": "https://github.com/deriv-com/smarttrader",
"devDependencies": {
"@babel/core": "7.24.0",
"@babel/eslint-parser": "7.23.10",
"@babel/parser": "7.25.0",
"@babel/eslint-parser": "^7.23.10",
"@babel/parser": "^7.25.0",
"@babel/plugin-proposal-decorators": "^7.24.7",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-class-properties": "^7.24.7",
Expand Down Expand Up @@ -53,6 +54,7 @@
"eslint-config-binary": "1.0.2",
"eslint-config-prettier": "2.9.0",
"eslint-import-resolver-alias": "1.1.1",
"eslint-plugin-escompat": "^3.11.4",
"eslint-plugin-import": "2.25.3",
"eslint-plugin-react": "7.28.0",
"estraverse": "4.2.0",
Expand All @@ -67,7 +69,7 @@
"grunt-contrib-copy": "1.0.0",
"grunt-contrib-cssmin": "5.0.0",
"grunt-contrib-watch": "1.1.0",
"grunt-eslint": "24.3.0",
"grunt-eslint": "^24.3.0",
"grunt-gh-pages": "4.0.0",
"grunt-hashres": "^0.4.1",
"grunt-mocha-test": "0.13.3",
Expand Down Expand Up @@ -142,8 +144,8 @@
},
"overrides": {
"braces": "^3.0.3",
"@babel/parser": "7.25.0",
"@babel/plugin-proposal-optional-chaining": "7.22.5"
"@babel/plugin-proposal-optional-chaining": "7.22.5",
"browserslist": "4.24.4"
},
"engines": {
"node": "18.x"
Expand Down
44 changes: 42 additions & 2 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ In order to make sure that no strings are missed by the extractor code while pus
1. Refactor the code so that the first argument passed to the `localize()` method is a string literal.
i.e.
```js
const text = localize(is_started ? 'Sell at market' : 'Sell');
const text = localize(is_started ? "Sell at market" : "Sell");
```
would change to:
```js
const text = is_started ? localize('Sell at market') : localize('Sell');
const text = is_started ? localize("Sell at market") : localize("Sell");
```
2. If there is no way to have the string literal in js code (i.e. API texts which are not translated), add them to `scripts/js_texts/static_strings_app.js`.

Expand All @@ -66,3 +66,43 @@ During the translation update process, the source file `messages.pot` will be up

- The list of paths to include in `sitemap.xml` is here: [config/sitemap_urls.js](config/sitemap_urls.js)
- Once the paths are updated in the above file, run `./scripts/sitemap.js` or `grunt shell:sitemap` to generate new `sitemap.xml` files in `src/root_files` according to each section.

## SVG Optimization

The project enforces that all SVG files follow a specific format to ensure optimal performance and compatibility. The test `should be valid svgs` in `scripts/__tests__/svg_test.js` checks that SVG files are properly formatted.

### SVG Validation Rules

- SVGs should be on a single line (not multi-line)
- Must have proper opening `<svg>` and closing `</svg>` tags
- Must follow the pattern: `/(?!\n)(<svg)(.*)(>).*(<\/\s?svg)>/i`

### How to Optimize SVGs

If you have SVG files that don't pass the validation, use the `optimize-svgs.js` script to fix them:

```bash
# Using npm script
npm run optimize-svgs

# Or directly
node scripts/optimize-svgs.js
```

The script:

1. Identifies all unoptimized SVG files in the project
2. Asks for confirmation before optimization
3. Uses SVGO (SVG Optimizer) to optimize the files
4. Reports results

### Ignoring SVGs

If specific SVG files should be excluded from optimization for valid reasons, add them to the `ignored_files` array in:

- `scripts/__tests__/svg_test.js`
- `scripts/optimize-svgs.js`

Currently ignored files:

- `src/images/pages/regulation/map.svg`
95 changes: 74 additions & 21 deletions scripts/__tests__/svg_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,88 @@ let changed_files = [];

describe('check svg file format', () => {
const fetchFiles = async (command) => {
const { stdout, stderr } = await exec(command);
if (stderr) {
throw new Error(stderr);
try {
const { stdout, stderr } = await exec(command);
if (stderr && !stderr.includes('Not a git repository')) {
throw new Error(stderr);
}
return stdout.split('\n').filter(dir => dir.length);
} catch (err) {
// If git command fails, it might be a non-git environment (e.g. CI)
// Just return an empty array in that case
if (err.message.includes('Not a git repository')) {
return [];
}
throw err;
}

return stdout.split('\n').filter(dir => dir.length);
};

it('should be valid svgs', async () => {
// Increase timeout to 10 seconds to give git operations more time
it('should be valid svgs', async function() {
this.timeout(10000); // 10 seconds

try {
await exec('git fetch origin master --depth 1');
changed_files = [
...await fetchFiles('git diff --name-only -- *.svg'),
...await fetchFiles('git diff HEAD origin/master --name-only -- *.svg'),
];
} catch (err) {
console.error(err);
}
// Get all changed SVG files using git
// This might fail if not in a git repo, but we'll handle that
try {
await exec('git fetch origin master --depth 1');
changed_files = [
...await fetchFiles('git diff --name-only -- *.svg'),
...await fetchFiles('git diff HEAD origin/master --name-only -- *.svg'),
];
} catch (err) {
// If git commands fail, fallback to checking all SVG files
if (err.message.includes('Not a git repository')) {
// Find all SVG files in the project
const { stdout } = await exec('find src -name "*.svg" 2>/dev/null');
changed_files = stdout.split('\n').filter(dir => dir.length);
} else {
expect.fail(`Error fetching changed files: ${err.message}`);
}
}

// If no files were found through git, find all SVGs in source directory
if (changed_files.length === 0) {
const { stdout } = await exec('find src -name "*.svg" 2>/dev/null');
changed_files = stdout.split('\n').filter(dir => dir.length);
}

changed_files.filter(item =>
!ignored_files.some(ignored => path.resolve(common.root_path, ignored) === item) &&
fs.existsSync(path.resolve(item)))
.forEach(item => {
// Filter files and check each one
const files_to_check = changed_files.filter(item =>
!ignored_files.some(ignored => path.resolve(common.root_path, ignored) === item) &&
fs.existsSync(path.resolve(item))
);

if (files_to_check.length === 0) {
// Skip test if no applicable files found
this.skip();
return;
}

const unoptimized_files = [];

files_to_check.forEach(item => {
const stats = fs.statSync(path.resolve(item));
if (stats.isSymbolicLink()) return;

const file = fs.readFileSync(path.resolve(item), 'utf-8');
expect(file, `Unoptimized svg at ${item}\n Please run the following command on your terminal and commit the result: \n svgo ${item} \n`)
.to
.match(/(?!\n)(<svg)(.*)(>).*(<\/\s?svg)>/i);

if (!(/(?!\n)(<svg)(.*)(>).*(<\/\s?svg)>/i).test(file)) {
unoptimized_files.push(item);
}
});

if (unoptimized_files.length > 0) {
const file_list = unoptimized_files.map(f => `- ${f}`).join('\n');
const error_message = `The following SVG files need to be optimized:\n${file_list}\n\nPlease run the following command to optimize these files:\nnode scripts/optimize-svgs.js\n`;

expect.fail(error_message);
}
} catch (err) {
if (err.name !== 'AssertionError') {
expect.fail(`Unexpected error: ${err.message}`);
}
throw err;
}
});
});
102 changes: 102 additions & 0 deletions scripts/optimize-svgs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env node
/* eslint-disable no-console */

/**
* This script finds and optimizes SVG files that don't match the required format.
* It helps fix the "should be valid svgs" test failures.
*/

const fs = require('fs');
const util = require('util');
const exec = util.promisify(require('child_process').exec);

const ignored_files = [
'src/images/pages/regulation/map.svg',
];

const findSvgFiles = async () => {
try {
// Find all SVG files in the project
const { stdout } = await exec('find . -name "*.svg" -not -path "*/node_modules/*" -not -path "*/\\.git/*"');
return stdout.split('\n').filter(filepath => filepath.length);
} catch (err) {
console.error('Error finding SVG files:', err.message);
return [];
}
};

const optimizeSvg = async (filepath) => {
try {
console.log(`Optimizing: ${filepath}`);
await exec(`npx svgo "${filepath}"`);
return true;
} catch (err) {
console.error(`Failed to optimize ${filepath}:`, err.message);
return false;
}
};

const isSvgOptimized = (content) => /(?!\n)(<svg)(.*)(>).*(<\/\s?svg)>/i.test(content);

const main = async () => {
const files = await findSvgFiles();
const unoptimized_files = [];

files.forEach(filepath => {
// Skip ignored files
if (ignored_files.some(ignored => filepath.includes(ignored))) {
return;
}

try {
const stats = fs.statSync(filepath);
if (stats.isSymbolicLink()) return;

const content = fs.readFileSync(filepath, 'utf-8');
if (!isSvgOptimized(content)) {
unoptimized_files.push(filepath);
}
} catch (err) {
console.error(`Error processing ${filepath}:`, err.message);
}
});

if (unoptimized_files.length === 0) {
console.log('All SVG files are already optimized!');
return;
}

console.log(`Found ${unoptimized_files.length} unoptimized SVG file(s):`);
unoptimized_files.forEach(file => console.log(`- ${file}`));

const answer = await promptYesNo('\nWould you like to optimize these files now? (y/n) ');
if (answer) {
console.log('\nOptimizing SVG files...');
const results = await Promise.all(
unoptimized_files.map(async file => ({
file,
success: await optimizeSvg(file),
}))
);

const success_count = results.filter(r => r.success).length;
console.log(`\nSuccessfully optimized ${success_count} of ${unoptimized_files.length} file(s).`);
}
};

const promptYesNo = async (question) => {
process.stdout.write(question);

return new Promise(resolve => {
process.stdin.once('data', (data) => {
const answer = data.toString().trim().toLowerCase();
resolve(answer === 'y' || answer === 'yes');
});
});
};

// Run the script
main().catch(err => {
console.error('Unexpected error:', err);
process.exit(1);
});
Loading
Loading