feat: add eslint-plugin-security rules for static analysis
Adds 12 security-focused ESLint rules (unsafe-regex, eval, timing attacks, child-process, bidi-characters, etc). One legitimate non-literal RegExp in answers.ts suppressed with inline comment. CI already runs pnpm lint + pnpm audit for SAST coverage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
abc081487c
commit
e2dc2bbd70
@@ -1,5 +1,6 @@
|
||||
import tseslint from '@typescript-eslint/eslint-plugin'
|
||||
import tsparser from '@typescript-eslint/parser'
|
||||
import security from 'eslint-plugin-security'
|
||||
|
||||
export default [
|
||||
{
|
||||
@@ -15,10 +16,24 @@ export default [
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': tseslint,
|
||||
security: security,
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||
// Security rules (from eslint-plugin-security)
|
||||
'security/detect-buffer-noassert': 'warn',
|
||||
'security/detect-child-process': 'warn',
|
||||
'security/detect-eval-with-expression': 'error',
|
||||
'security/detect-new-buffer': 'warn',
|
||||
'security/detect-non-literal-regexp': 'warn',
|
||||
'security/detect-non-literal-require': 'warn',
|
||||
'security/detect-possible-timing-attacks': 'warn',
|
||||
'security/detect-pseudoRandomBytes': 'warn',
|
||||
'security/detect-unsafe-regex': 'error',
|
||||
'security/detect-bidi-characters': 'error',
|
||||
// detect-object-injection has too many false positives — skip
|
||||
// detect-non-literal-fs-filename too noisy for server code — skip
|
||||
},
|
||||
},
|
||||
// Frontend game engine: fire-and-forget async (audio, animations) is intentional
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"@typescript-eslint/parser": "^8.56.1",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^10.0.3",
|
||||
"eslint-plugin-security": "^4.0.0",
|
||||
"typescript": "^5.7.3",
|
||||
"vitest": "^3.1.1"
|
||||
}
|
||||
|
||||
Generated
+24
@@ -27,6 +27,9 @@ importers:
|
||||
eslint:
|
||||
specifier: ^10.0.3
|
||||
version: 10.0.3(jiti@2.6.1)
|
||||
eslint-plugin-security:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
typescript:
|
||||
specifier: ^5.7.3
|
||||
version: 5.9.3
|
||||
@@ -2162,6 +2165,10 @@ packages:
|
||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
eslint-plugin-security@4.0.0:
|
||||
resolution: {integrity: sha512-tfuQT8K/Li1ZxhFzyD8wPIKtlzZxqBcPr9q0jFMQ77wWAbKBVEhaMPVQRTMTvCMUDhwBe5vPVqQPwAGk/ASfxQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
eslint-scope@9.1.2:
|
||||
resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
@@ -3051,6 +3058,10 @@ packages:
|
||||
regenerate@1.4.2:
|
||||
resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
|
||||
|
||||
regexp-tree@0.1.27:
|
||||
resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==}
|
||||
hasBin: true
|
||||
|
||||
regexp.prototype.flags@1.5.4:
|
||||
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -3114,6 +3125,9 @@ packages:
|
||||
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
safe-regex@2.1.1:
|
||||
resolution: {integrity: sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==}
|
||||
|
||||
saxes@6.0.0:
|
||||
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
||||
engines: {node: '>=v12.22.7'}
|
||||
@@ -5791,6 +5805,10 @@ snapshots:
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
|
||||
eslint-plugin-security@4.0.0:
|
||||
dependencies:
|
||||
safe-regex: 2.1.1
|
||||
|
||||
eslint-scope@9.1.2:
|
||||
dependencies:
|
||||
'@types/esrecurse': 4.3.1
|
||||
@@ -6681,6 +6699,8 @@ snapshots:
|
||||
|
||||
regenerate@1.4.2: {}
|
||||
|
||||
regexp-tree@0.1.27: {}
|
||||
|
||||
regexp.prototype.flags@1.5.4:
|
||||
dependencies:
|
||||
call-bind: 1.0.8
|
||||
@@ -6786,6 +6806,10 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
is-regex: 1.2.1
|
||||
|
||||
safe-regex@2.1.1:
|
||||
dependencies:
|
||||
regexp-tree: 0.1.27
|
||||
|
||||
saxes@6.0.0:
|
||||
dependencies:
|
||||
xmlchars: 2.2.0
|
||||
|
||||
@@ -155,6 +155,7 @@ export function checkAnswer(response: string | null, acceptedAnswers: string[]):
|
||||
if (accNum !== null) {
|
||||
const numStr = String(accNum)
|
||||
// Only match if the number appears as a whole token, not as a substring of a larger number
|
||||
// eslint-disable-next-line security/detect-non-literal-regexp -- numStr is a parsed number, escaped
|
||||
const numRegex = new RegExp(`(?<![\\d])${numStr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\d])`)
|
||||
if (numRegex.test(normResponse)) return 1.0
|
||||
// Check number words in response
|
||||
|
||||
Reference in New Issue
Block a user