eslint-plugin-sonarjs
Version:
SonarJS rules for ESLint
160 lines (159 loc) • 5.75 kB
JavaScript
/*
* SonarQube JavaScript Plugin
* Copyright (C) 2011-2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
// https://sonarsource.github.io/rspec/#/rspec/S6418/javascript
Object.defineProperty(exports, "__esModule", { value: true });
exports.rule = void 0;
const index_js_1 = require("../helpers/index.js");
const meta_js_1 = require("./meta.js");
const DEFAULT_SECRET_WORDS = 'api[_.-]?key,auth,credential,secret,token';
const DEFAULT_RANDOMNESS_SENSIBILITY = 5.0;
const POSTVALIDATION_PATTERN = /[a-zA-Z0-9_.+/~$-]([a-zA-Z0-9_.+/=~$-]|\\\\\\\\(?![ntr"])){14,1022}[a-zA-Z0-9_.+/=~$-]/;
function message(name) {
return `"${name}" detected here, make sure this is not a hard-coded secret.`;
}
let randomnessSensibility;
let secretWordRegexps;
exports.rule = {
meta: (0, index_js_1.generateMeta)(meta_js_1.meta, { schema: meta_js_1.schema }, false /* true if secondary locations */),
create(context) {
// get typed rule options with FromSchema helper
const secretWords = context.options[0]?.['secretWords'] ?? DEFAULT_SECRET_WORDS;
secretWordRegexps = buildSecretWordRegexps(secretWords);
randomnessSensibility =
context.options[0]?.['randomnessSensibility'] ??
DEFAULT_RANDOMNESS_SENSIBILITY;
return {
AssignmentExpression(node) {
handleAssignmentExpression(context, node);
},
AssignmentPattern(node) {
handleAssignmentPattern(context, node);
},
Property(node) {
handlePropertyAndPropertyDefinition(context, node);
},
PropertyDefinition(node) {
handlePropertyAndPropertyDefinition(context, node);
},
VariableDeclarator(node) {
handleVariableDeclarator(context, node);
},
};
},
};
function handleAssignmentExpression(context, node) {
const keySuspect = findKeySuspect(node.left);
const valueSuspect = findValueSuspect(extractDefaultOperatorIfNeeded(node));
if (keySuspect && valueSuspect) {
context.report({
node: node.right,
message: message(keySuspect),
});
}
function extractDefaultOperatorIfNeeded(node) {
const defaultOperators = ['??', '||'];
if ((0, index_js_1.isLogicalExpression)(node.right) &&
defaultOperators.includes(node.right.operator)) {
return node.right.right;
}
else {
return node.right;
}
}
}
function handleAssignmentPattern(context, node) {
const keySuspect = findKeySuspect(node.left);
const valueSuspect = findValueSuspect(node.right);
if (keySuspect && valueSuspect) {
context.report({
node: node.right,
message: message(keySuspect),
});
}
}
function handlePropertyAndPropertyDefinition(context, node) {
const keySuspect = findKeySuspect(node.key);
const valueSuspect = findValueSuspect(node.value);
if (keySuspect && valueSuspect) {
context.report({
node: node.value,
message: message(keySuspect),
});
}
}
function handleVariableDeclarator(context, node) {
const keySuspect = findKeySuspect(node.id);
const valueSuspect = findValueSuspect(node.init);
if (keySuspect && valueSuspect) {
context.report({
node: node.init,
message: message(keySuspect),
});
}
}
function findKeySuspect(node) {
if ((0, index_js_1.isIdentifier)(node) && secretWordRegexps.some(pattern => pattern.test(node.name))) {
return node.name;
}
else {
return undefined;
}
}
function findValueSuspect(node) {
if (node &&
(0, index_js_1.isStringLiteral)(node) &&
valuePassesPostValidation(node.value) &&
entropyShouldRaise(node.value)) {
return node;
}
else {
return undefined;
}
}
function valuePassesPostValidation(value) {
return POSTVALIDATION_PATTERN.test(value);
}
function buildSecretWordRegexps(secretWords) {
try {
return secretWords.split(',').map(word => new RegExp(`(${word})`, 'i'));
}
catch (e) {
console.error(`Invalid characters provided to rule S6418 'no-hardcoded-secrets' parameter "secretWords": "${secretWords}" falling back to default: "${DEFAULT_SECRET_WORDS}". Error: ${e}`);
return buildSecretWordRegexps(DEFAULT_SECRET_WORDS);
}
}
function entropyShouldRaise(value) {
return ShannonEntropy.calculate(value) > randomnessSensibility;
}
const ShannonEntropy = {
calculate: (str) => {
if (!str) {
return 0;
}
const lettersTotal = str.length;
const occurences = {};
for (const letter of [...str]) {
occurences[letter] = (occurences[letter] ?? 0) + 1;
}
const values = Object.values(occurences);
return (values
.map(count => count / lettersTotal)
.map(frequency => -frequency * Math.log(frequency))
.reduce((acc, entropy) => acc + entropy, 0) / Math.log(2));
},
};
;