Configures and runs `eslint-plugin-security` (14 detect-* rules covering injection, path traversal, ReDoS, unsafe buffers, and bidi trojan-source) plus `eslint-plugin-no-unsanitized` (DOM XSS via `innerHTML`, `outerHTML`, `document.write`, `insertAdjacentHTML`) as the JS/TS first-party SAST layer; covers flat config setup, per-rule suppression with justification templates, SARIF output via `@microsoft/eslint-formatter-sarif` for GitHub Code Scanning upload, and CI gating on ESLint exit code 1. Use when the project is JS or TS and needs an in-process security lint pass without a separate SAST server.
75
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Two npm plugins add a security lint layer inside the standard ESLint pipeline, no external server required. They emit JSON or SARIF for a multi-scanner triage step.
Per github.com/eslint-community/eslint-plugin-security:
"This project will help identify potential security hotspots, but finds a lot of false positives which need triage by a human."
Per github.com/mozilla/eslint-plugin-no-unsanitized, the
no-unsanitized plugin adds "basic security checks" for DOM sink
operations (innerHTML, insertAdjacentHTML, document.write).
innerHTML, insertAdjacentHTML,
document.write) that need sink-level XSS coverage.Per esp-sec:
npm install --save-dev eslint-plugin-securityPer esp-xss:
npm install --save-dev eslint-plugin-no-unsanitizedFor SARIF output, per github.com/microsoft/sarif-js-sdk:
npm install --save-dev @microsoft/eslint-formatter-sarif// eslint.config.js
import pluginSecurity from "eslint-plugin-security";
import nounsanitized from "eslint-plugin-no-unsanitized";
export default [
pluginSecurity.configs.recommended,
nounsanitized.configs.recommended,
];pluginSecurity.configs.recommended enables all 14 detect-* rules.
nounsanitized.configs.recommended enables nounsanitized/method
and nounsanitized/property.
For legacy .eslintrc configs, per esp-sec:
module.exports = {
extends: ["plugin:security/recommended-legacy"],
};pluginSecurity.configs.recommended enables all 14 detect-* rules
(injection, path traversal, ReDoS, unsafe buffers, bidi trojan-source);
nounsanitized.configs.recommended enables nounsanitized/method and
nounsanitized/property for DOM-sink XSS. The full per-rule tables and
the safe DOM alternatives are in
references/eslint-security-reference.md.
security/detect-object-injection is the highest-volume false
positive: any obj[key] access triggers it, including safe patterns
like array indexing. Standard triage approaches:
Per-line suppression with mandatory justification:
// eslint-disable-next-line security/detect-object-injection
// Reason: key is validated against allowedKeys before this point
// Reviewer: eng@example.com (2026-06-04)
// Expires: 2026-12-04
const value = config[key];Block-level suppression for generated or vendored code:
/* eslint-disable security/detect-object-injection */
// Reason: auto-generated lookup table; keys are compile-time constants
/* eslint-enable security/detect-object-injection */Rule-level severity downgrade when a rule produces only noise on a specific codebase:
// eslint.config.js
export default [
pluginSecurity.configs.recommended,
{
rules: {
"security/detect-object-injection": "warn", // downgrade from error
},
},
];Suppression cadence: audit all eslint-disable comments quarterly.
Suppressions without Reason: + Reviewer: + Expires: are treated
as unreviewed debt.
Per sarif-sdk, the @microsoft/eslint-formatter-sarif
package cannot be invoked with the abbreviated -f sarif form because
its name is scoped. Use the full package name:
npx eslint \
--format @microsoft/eslint-formatter-sarif \
--output-file eslint-security.sarif \
"src/**/*.{js,ts}"To embed analyzed source content in the SARIF output:
SARIF_ESLINT_EMBED=true npx eslint \
--format @microsoft/eslint-formatter-sarif \
--output-file eslint-security.sarif \
"src/**/*.{js,ts}"Upload to GitHub Code Scanning:
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: eslint-security.sarifPer eslint.org/docs/latest/use/command-line-interface:
ESLint exit codes: 0 = no errors; 1 = errors found; 2 = config
error. Gate CI on exit code 1. The complete GitHub Actions workflow
(JSON pass for the triager, SARIF pass that propagates the exit code,
SARIF upload) is in
references/eslint-security-reference.md.
Per eslint-cli, --format json produces an array of
file result objects, each with a messages array containing ruleId,
severity, line, column, and message. Feed eslint-security.json
into a multi-scanner triage step alongside semgrep.json and other
scanner outputs; normalize ruleId to CWE for deduplication across
scanners.
Triggering finding:
const userData = req.body;
element.innerHTML = userData.bio; // nounsanitized/property
const file = fs.readFileSync(req.query.path); // security/detect-non-literal-fs-filenameESLint output (stylish):
src/profile.js
12:3 error Unsafe assignment to innerHTML nounsanitized/property
18:3 error Found non-literal argument to readFileSync security/detect-non-literal-fs-filenameSafe rewrites:
// XSS: use textContent for plain text; DOMPurify for rich HTML
element.textContent = userData.bio;
// or: element.setHTML(sanitize(userData.bio));
// Path traversal: validate against an allowlist
const allowed = ["/var/data/a.txt", "/var/data/b.txt"];
if (!allowed.includes(req.query.path)) throw new Error("invalid path");
const file = fs.readFileSync(req.query.path);detect-object-injection fires on all variable-keyed property
accesses; expect high false-positive volume on data-heavy code.
Pair with a code review step rather than blocking CI on it alone.semgrep-rules or
codeql-queries.eslint-plugin-security does not cover server-side template
injection or SQL injection natively; use Semgrep p/owasp-top-ten
for those patterns.eval called through a proxy chain will not be caught.@microsoft/eslint-formatter-sarif, scoped -f usage, SARIF_ESLINT_EMBED--format, --output-file, exit codessemgrep-rules,
codeql-queries,
sonarqube-rules - complementary scanners