diff --git a/eslint.config.mjs b/eslint.config.mjs index 529bfef..41211f3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -8,6 +8,7 @@ import laststanceReactNextPlugin from '@laststance/react-next-eslint-plugin' import tsPrefixer from 'eslint-config-ts-prefixer' import reactYouMightNotNeedAnEffect from 'eslint-plugin-react-you-might-not-need-an-effect' import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript' +import browserSecurity from 'eslint-plugin-browser-security' export default defineConfig([ // ts-prefixer: @typescript-eslint + import-x + parser @@ -202,6 +203,49 @@ export default defineConfig([ }, }, + // ── Browser security — eight runtime XSS / token-storage rules ── + // Mirrors eslint-config-ts-prefixer#636. Added directly because that PR is + // not yet released; delete this block once gitbox's eslint-config-ts-prefixer + // is bumped to a version that already ships these rules. + // https://github.com/laststance/eslint-config-ts-prefixer/pull/636 + { + files: ['**/*.{js,jsx,mjs,ts,tsx,mts,cts}'], + plugins: { + 'browser-security': browserSecurity, + }, + rules: { + // Disallow assigning to innerHTML/outerHTML — the most common XSS sink in + // browser code, and not something a type checker can catch. + 'browser-security/no-innerhtml': 'error', + + // Disallow eval() and its string-compiling relatives. + 'browser-security/no-eval': 'error', + + // Disallow storing a JWT in localStorage/sessionStorage: any XSS on the + // page can read it, unlike an HttpOnly cookie. + 'browser-security/no-jwt-in-storage': 'error', + + // Same reasoning for other secrets kept in Web Storage. + 'browser-security/no-sensitive-localstorage': 'error', + + // Disallow credentials in query strings — they land in browser history, + // Referer headers, and server access logs. + 'browser-security/no-credentials-in-query-params': 'error', + + // Require Secure and SameSite when setting cookies from JS. (HttpOnly is + // deliberately absent — a cookie set through document.cookie cannot be + // HttpOnly, by definition.) + 'browser-security/require-cookie-secure-attrs': 'error', + + // Disallow postMessage(..., '*') — an origin wildcard leaks the payload + // to whatever happens to be framed. + 'browser-security/no-postmessage-wildcard-origin': 'error', + + // Disallow redirects built from unvalidated input (open redirect). + 'browser-security/no-insecure-redirects': 'error', + }, + }, + // @laststance/react-next-eslint-plugin rules { plugins: { diff --git a/package.json b/package.json index 9023914..c32c578 100644 --- a/package.json +++ b/package.json @@ -138,6 +138,7 @@ "eslint": "^10.9.1", "eslint-config-ts-prefixer": "^4.3.0", "eslint-import-resolver-typescript": "^4.4.5", + "eslint-plugin-browser-security": "^2.0.6", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-you-might-not-need-an-effect": "^1.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4abdb3..9dcff88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -294,6 +294,9 @@ importers: eslint-import-resolver-typescript: specifier: ^4.4.5 version: 4.4.5(eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.67.0(eslint@10.9.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.9.1(jiti@2.7.0)))(eslint@10.9.1(jiti@2.7.0)) + eslint-plugin-browser-security: + specifier: ^2.0.6 + version: 2.0.6(@typescript-eslint/utils@8.67.0(eslint@10.9.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.9.1(jiti@2.7.0))(oxc-resolver@11.24.2)(typescript@6.0.3) eslint-plugin-jsx-a11y: specifier: ^6.10.2 version: 6.10.2(eslint@10.9.1(jiti@2.7.0)) @@ -1049,6 +1052,22 @@ packages: '@types/node': optional: true + '@interlace/eslint-devkit@1.17.3': + resolution: {integrity: sha512-XCklAAGpWEvYtRtBLfbQ06hpO/0VkwumzmgJtJw8ZbLzltI7LYJmhXQN4HSUxI4s/F/UkcEwgcvDTxkUgjlMHQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@typescript-eslint/utils': ^7.0.0 || ^8.0.0 + eslint: ^8.40.0 || ^9.0.0 || ^10.0.0 + oxc-resolver: ^11.24.2 + typescript: '>=4.8.4' + peerDependenciesMeta: + '@typescript-eslint/utils': + optional: true + oxc-resolver: + optional: true + typescript: + optional: true + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0': resolution: {integrity: sha512-qvsTEwEFefhdirGOPnu9Wp6ChfIwy2dBCRuETU3uE+4cC+PFoxMSiiEhxk4lOluA34eARHA0OxqsEUYDqRMgeQ==} peerDependencies: @@ -4265,6 +4284,12 @@ packages: eslint-plugin-import-x: optional: true + eslint-plugin-browser-security@2.0.6: + resolution: {integrity: sha512-QMS7w4M8yw6+vPRuskBg9jBK08qPOeuO2BNSf64y+/H+Qo8cseD4tG5GT2VyDdQpH7HNzhdtCOvfcQL+JIZK1A==} + engines: {node: '>=18.0.0'} + peerDependencies: + eslint: ^8.40.0 || ^9.0.0 || ^10.0.0 + eslint-plugin-import-x@4.17.1: resolution: {integrity: sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -7418,6 +7443,14 @@ snapshots: optionalDependencies: '@types/node': 26.2.0 + '@interlace/eslint-devkit@1.17.3(@typescript-eslint/utils@8.67.0(eslint@10.9.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.9.1(jiti@2.7.0))(oxc-resolver@11.24.2)(typescript@6.0.3)': + dependencies: + eslint: 10.9.1(jiti@2.7.0) + optionalDependencies: + '@typescript-eslint/utils': 8.67.0(eslint@10.9.1(jiti@2.7.0))(typescript@6.0.3) + oxc-resolver: 11.24.2 + typescript: 6.0.3 + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@6.0.3)(vite@8.2.2(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(tsx@4.23.12)(yaml@2.9.0))': dependencies: glob: 13.0.6 @@ -10514,6 +10547,15 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-browser-security@2.0.6(@typescript-eslint/utils@8.67.0(eslint@10.9.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.9.1(jiti@2.7.0))(oxc-resolver@11.24.2)(typescript@6.0.3): + dependencies: + '@interlace/eslint-devkit': 1.17.3(@typescript-eslint/utils@8.67.0(eslint@10.9.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.9.1(jiti@2.7.0))(oxc-resolver@11.24.2)(typescript@6.0.3) + eslint: 10.9.1(jiti@2.7.0) + transitivePeerDependencies: + - '@typescript-eslint/utils' + - oxc-resolver + - typescript + eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.67.0(eslint@10.9.1(jiti@2.7.0))(typescript@6.0.3))(eslint@10.9.1(jiti@2.7.0)): dependencies: '@typescript-eslint/types': 8.67.0