Skip to content

Commit a2ffbd3

Browse files
feat: add security tooling with ESLint plugin and Semgrep CI
- Add eslint-plugin-security with detect-object-injection disabled - Add pnpm audit:check script for dependency scanning - Add GitHub Actions workflow for Semgrep SAST on push/PR - Document security tooling and patterns in CLAUDE.md and README.md - Add eslint-disable comments for legitimate non-literal RegExp in rss-fetcher - Upgrade vite 5→7, vitest 1→4, @vitejs/plugin-vue 5→6
1 parent 4885246 commit a2ffbd3

7 files changed

Lines changed: 480 additions & 479 deletions

File tree

.github/workflows/security.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Security
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
semgrep:
11+
name: Semgrep Scan
12+
runs-on: ubuntu-latest
13+
container:
14+
image: semgrep/semgrep
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Run Semgrep
19+
# Excluded rules produce false positives in this codebase:
20+
# - unsafe-formatstring: LOG_PREFIX is a hardcoded constant, not user input
21+
# - detect-non-literal-regexp: tagName/attrName are internal XML parsing params
22+
run: >-
23+
semgrep scan
24+
--config auto
25+
--config p/javascript
26+
--config p/typescript
27+
--exclude-rule javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
28+
--exclude-rule javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp
29+
--error

CLAUDE.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pnpm dev # Start dev server with hot reload
1313
pnpm build # Production build (runs vue-tsc first)
1414
pnpm test # Run all tests once
1515
pnpm test:watch # Run tests in watch mode
16+
pnpm lint # Run ESLint (includes security rules)
17+
pnpm audit:check # Scan dependencies for vulnerabilities
1618
```
1719

1820
**Loading the extension in Chrome:**
@@ -122,6 +124,39 @@ pnpm build # Type check + build
122124
pnpm lint:fix # Auto-fix lint issues
123125
pnpm format # Auto-fix formatting
124126
pnpm test # Run tests
127+
pnpm audit:check # Check for vulnerable dependencies
125128
```
126129

127130
Fix any errors before considering the implementation complete.
131+
132+
## Security
133+
134+
### Security Tooling
135+
136+
The project uses three layers of security scanning:
137+
138+
1. **eslint-plugin-security** - Integrated into ESLint, runs on every `pnpm lint`
139+
- Detects eval, non-literal RegExp, child_process usage, unsafe regex patterns
140+
- `detect-object-injection` disabled (too many false positives with TS)
141+
142+
2. **pnpm audit** - Dependency vulnerability scanning via `pnpm audit:check`
143+
- Fails on moderate+ severity vulnerabilities
144+
- Run before releases or when updating dependencies
145+
146+
3. **Semgrep** - SAST in GitHub Actions CI (`.github/workflows/security.yml`)
147+
- Runs on push/PR to main
148+
- Uses `auto`, `p/javascript`, `p/typescript` rulesets
149+
- Two rules excluded as false positives (see workflow comments)
150+
151+
### Security Patterns in Codebase
152+
153+
- **Origin validation**: `src/background/index.ts` validates content script origins against `ALLOWED_CONTENT_SCRIPT_ORIGINS`
154+
- **Input validation**: `src/shared/validation.ts` sanitizes all user inputs
155+
- **Secure storage**: API keys in `chrome.storage.local`, never in sync storage or exposed to content scripts
156+
- **No dynamic code**: ESLint blocks eval/Function patterns
157+
158+
### Running Semgrep Locally
159+
160+
```bash
161+
docker run --rm -v "$(pwd):/src" semgrep/semgrep semgrep scan --config auto --config p/javascript --config p/typescript /src
162+
```

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,48 @@ See [privacy-policy.md](./privacy-policy.md) for the full privacy policy.
230230

231231
---
232232

233+
## Security
234+
235+
ReplyQueue uses multiple layers of security tooling to catch vulnerabilities early.
236+
237+
### Static Analysis
238+
239+
| Tool | Purpose | Command |
240+
|------|---------|---------|
241+
| `eslint-plugin-security` | Detects common security anti-patterns in JS/TS | `pnpm lint` |
242+
| `pnpm audit` | Scans dependencies for known CVEs | `pnpm audit:check` |
243+
| Semgrep | SAST scanning for injection, XSS, and more | CI only (or Docker locally) |
244+
245+
### CI Integration
246+
247+
The `.github/workflows/security.yml` workflow runs Semgrep on every push and PR to `main`, using:
248+
- `auto` - Semgrep's recommended security rules
249+
- `p/javascript` - JavaScript-specific security patterns
250+
- `p/typescript` - TypeScript-specific security patterns
251+
252+
### Running Security Checks Locally
253+
254+
```bash
255+
# ESLint security rules (included in lint)
256+
pnpm lint
257+
258+
# Dependency vulnerability scan
259+
pnpm audit:check
260+
261+
# Semgrep via Docker
262+
docker run --rm -v "$(pwd):/src" semgrep/semgrep semgrep scan --config auto --config p/javascript --config p/typescript /src
263+
```
264+
265+
### Security Practices
266+
267+
- **Origin validation** - Background script validates message origins against allowlist
268+
- **Input sanitization** - All user inputs validated before processing
269+
- **No eval/Function** - Dynamic code execution patterns are blocked by ESLint
270+
- **Secure storage** - API keys stored in `chrome.storage.local`, never exposed to content scripts
271+
- **CSP compliance** - Extension follows Chrome's Content Security Policy requirements
272+
273+
---
274+
233275
## Troubleshooting
234276

235277
### Extension not loading

eslint.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import tseslint from '@typescript-eslint/eslint-plugin';
33
import tsparser from '@typescript-eslint/parser';
44
import vue from 'eslint-plugin-vue';
55
import vueParser from 'vue-eslint-parser';
6+
import security from 'eslint-plugin-security';
67
import globals from 'globals';
78

89
export default [
@@ -27,10 +28,13 @@ export default [
2728
plugins: {
2829
'@typescript-eslint': tseslint,
2930
vue,
31+
security,
3032
},
3133
rules: {
3234
...tseslint.configs.recommended.rules,
3335
...vue.configs['flat/recommended'].rules,
36+
...security.configs.recommended.rules,
37+
'security/detect-object-injection': 'off', // Too many false positives with TypeScript strict mode
3438
'vue/multi-word-component-names': 'off',
3539
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
3640
'@typescript-eslint/no-explicit-any': 'warn',
@@ -61,9 +65,12 @@ export default [
6165
},
6266
plugins: {
6367
'@typescript-eslint': tseslint,
68+
security,
6469
},
6570
rules: {
6671
...tseslint.configs.recommended.rules,
72+
...security.configs.recommended.rules,
73+
'security/detect-object-injection': 'off', // Too many false positives with TypeScript strict mode
6774
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
6875
'@typescript-eslint/no-explicit-any': 'warn',
6976
'no-undef': 'off',

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"lint": "eslint src tests --max-warnings 0",
1212
"lint:fix": "eslint src tests --fix",
1313
"format": "prettier --write .",
14-
"format:check": "prettier --check ."
14+
"format:check": "prettier --check .",
15+
"audit:check": "pnpm audit --audit-level=moderate"
1516
},
1617
"dependencies": {
1718
"rss-parser": "^3.13.0",
@@ -25,19 +26,20 @@
2526
"@types/node": "^20.11.30",
2627
"@typescript-eslint/eslint-plugin": "^8.53.0",
2728
"@typescript-eslint/parser": "^8.53.0",
28-
"@vitejs/plugin-vue": "^5.0.4",
29+
"@vitejs/plugin-vue": "^6.0.3",
2930
"autoprefixer": "^10.4.19",
3031
"canvas": "^3.2.1",
3132
"eslint": "^9.39.2",
33+
"eslint-plugin-security": "^3.0.1",
3234
"eslint-plugin-vue": "^10.7.0",
3335
"globals": "^17.0.0",
3436
"happy-dom": "^20.0.0",
3537
"postcss": "^8.4.38",
3638
"prettier": "^3.8.0",
3739
"tailwindcss": "^3.4.1",
3840
"typescript": "^5.4.3",
39-
"vite": "^5.2.6",
40-
"vitest": "^1.4.0",
41+
"vite": "^7.3.1",
42+
"vitest": "^4.0.17",
4143
"vue-eslint-parser": "^10.2.0",
4244
"vue-tsc": "^2.0.7"
4345
}

0 commit comments

Comments
 (0)