mirror of
https://github.com/ever-co/ever-gauzy.git
synced 2026-10-02 01:54:50 +08:00
* fix(security): compile MJML without file includes, lock down integration settings GHSA-48h9-vwf5-h8m7 (critical): an email or accounting template containing mj-include with a path was compiled with the file loader enabled, so any caller who could preview or save a template could read files from the API container, including .env and /proc/self/environ. All nine mjml2html call sites now go through compileMjml(), which passes ignoreIncludes: true (verified against mjml-parser-xml 4.18.0, which returns before any read) and coerces the source to a string, so a JSON body can no longer arrive as a pre-parsed Handlebars AST. An ESLint rule keeps raw mjml imports out of the package, and the preview routes now validate their bodies. GHSA-4rwq-65wh-45h4 (high): PUT /integration-setting/:id could rewrite server-managed settings such as a GitHub installation_id, binding another tenant's installation. Updates are now tenant-scoped, restricted to an allow-list of user-editable keys, and pin settingsName and integrationId; the inherited POST /integration-tenant route that reached the same sink is gated; and installation_id is canonicalised so the uniqueness check compares like with like. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(cspell): add the new vocabulary and use US spellings Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(security): harden template source coercion, address review findings Review follow-ups on the GHSA-48h9 / GHSA-4rwq fix. No security boundary is relaxed; the coercion helper gets strictly stronger. - toTemplateSource(): map every non-primitive (a JSON object or array, a Handlebars AST, a function) to '' instead of stringifying it. `String(value)` produced a useless '[object Object]' and, for an object whose `toString` and `valueOf` are not callable ({"toString":1,"valueOf":2} is valid JSON), threw a TypeError out of the request handler instead of rendering an empty template. The preview DTOs already reject a non-string `data`, so this is the second layer, and it is the layer the stored-`hbs` render path relies on. Primitives still stringify. Spec updated to assert the stronger outcome (the AST now renders to '', not to '[object Object]') and extended with the throwing shape. Also clears SonarCloud "'source ?? ''' will use Object's default stringification format". - IntegrationTenantController.create(): replace the nested ternary that encoded "not an array means refuse" as a sentinel `[null]` element with an explicit guard. Same three outcomes (absent settings allowed, array checked element-wise, anything else forbidden), one fewer indirection. Clears SonarCloud "Extract this nested ternary operation". - GITHUB_INSTALLATION_ID_PATTERN: `\d` for `[0-9]`. Identical in JS (`\d` is ASCII-only, with or without the `u` flag); clears a SonarCloud nitpick. - integration-setting.utils.ts: record WHY the allowlist lookup stays on `Object.prototype.hasOwnProperty.call`. SonarCloud asks for `Object.hasOwn`, but that is ES2022 and packages/core/tsconfig.lib.json targets es2021 with no `lib` override, so it fails to compile (TS2550, verified with tsc). Tests: packages/core email-template + accounting-template + integration-setting + integration-tenant = 8 suites, 123 tests passed; integration-github = 2 suites, 48 tests passed. ESLint over the changed core files: 0 errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
58 lines
2.0 KiB
JavaScript
58 lines
2.0 KiB
JavaScript
// cspell:ignore esquery
|
|
const baseConfig = require('../../eslint.config.js');
|
|
|
|
// The MJML compiler reads files from disk for `<mj-include>` unless told not to, and email/accounting
|
|
// templates are tenant-editable (GHSA-48h9-vwf5-h8m7). Every compile must go through
|
|
// `src/lib/email-template/compile-mjml.ts`, which disables includes; importing the compiler anywhere
|
|
// else in this package is a lint error. Covers `import` and `import x = require()`
|
|
// (`no-restricted-imports`) as well as `require()` and dynamic `import()` (`no-restricted-syntax`).
|
|
const MJML_MODULES = ['mjml', 'mjml-core', 'mjml-parser-xml'];
|
|
const MJML_MESSAGE =
|
|
'Compile MJML through compileMjml() in src/lib/email-template/compile-mjml.ts; it disables <mj-include> file reads (GHSA-48h9-vwf5-h8m7).';
|
|
// esquery regex literal; no '/' allowed inside. Matches `mjml`, `mjml-core`, `mjml-parser-xml` and their subpaths.
|
|
const MJML_MODULE_REGEX = '/^mjml(-core|-parser-xml)?(?![A-Za-z0-9_.-])/';
|
|
const restrictMjmlImports = {
|
|
name: 'gauzy/core/restrict-mjml-imports',
|
|
files: ['**/*.ts', '**/*.js', '**/*.cts', '**/*.mts', '**/*.cjs', '**/*.mjs'],
|
|
ignores: ['**/src/lib/email-template/compile-mjml.ts'],
|
|
rules: {
|
|
'no-restricted-imports': [
|
|
'error',
|
|
{
|
|
paths: MJML_MODULES.map((name) => ({ name, message: MJML_MESSAGE })),
|
|
patterns: [{ group: MJML_MODULES.map((name) => `${name}/*`), message: MJML_MESSAGE }]
|
|
}
|
|
],
|
|
'no-restricted-syntax': [
|
|
'error',
|
|
{
|
|
selector: `CallExpression[callee.name='require'][arguments.0.value=${MJML_MODULE_REGEX}]`,
|
|
message: MJML_MESSAGE
|
|
},
|
|
{
|
|
selector: `ImportExpression[source.value=${MJML_MODULE_REGEX}]`,
|
|
message: MJML_MESSAGE
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
module.exports = [
|
|
...baseConfig,
|
|
restrictMjmlImports,
|
|
{
|
|
files: ['**/*.json'],
|
|
rules: {
|
|
'@nx/dependency-checks': [
|
|
'error',
|
|
{
|
|
ignoredFiles: ['{projectRoot}/eslint.config.{js,cjs,mjs}']
|
|
}
|
|
]
|
|
},
|
|
languageOptions: {
|
|
parser: require('jsonc-eslint-parser')
|
|
}
|
|
}
|
|
];
|