Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 7 additions & 12 deletions .github/semgrep/llm-owasp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ rules:
cwe: CWE-79

- id: llm02-dangerous-set-inner-html
pattern: dangerouslySetInnerHTML={{ __html: $VAL }}
pattern-regex: 'dangerouslySetInnerHTML\s*=\s*\{\{'
message: >
LLM02 (Insecure Output Handling): dangerouslySetInnerHTML renders raw HTML,
bypassing React's XSS protection. Sanitize AI-generated content with
DOMPurify before passing it here, or restructure to avoid raw HTML rendering.
languages: [typescript, javascript]
languages: [generic]
severity: WARNING
metadata:
owasp-llm: LLM02
Expand Down Expand Up @@ -71,17 +71,12 @@ rules:
owasp-llm: LLM08
cwe: CWE-95

- id: llm08-child-process-exec-dynamic
patterns:
- pattern: |
require('child_process').exec($CMD, ...)
- pattern-not: |
require('child_process').exec("...", ...)
- id: llm08-child-process-exec
pattern: require('child_process').exec($CMD, ...)
message: >
LLM08 (Excessive Agency): child_process.exec() with a dynamic command
string can enable OS command injection if $CMD contains AI-generated or
user-controlled content. Use execFile() with a fixed binary and argument
array instead.
LLM08 (Excessive Agency): child_process.exec() passes the command to a
shell, enabling command injection if $CMD is AI-generated or user-controlled.
Use execFile() with a fixed binary and an argument array instead.
languages: [typescript, javascript]
severity: ERROR
metadata:
Expand Down
21 changes: 15 additions & 6 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,30 @@ jobs:
npm-audit:
name: npm Dependency Audit
runs-on: ubuntu-latest
# Skip gracefully if the web-builder hasn't been committed yet
if: ${{ hashFiles('resumes/web-builder/package.json') != '' }}
steps:
- uses: actions/checkout@v4
- name: Check if web-builder exists
id: check-webbuilder
run: |
if [ -f "resumes/web-builder/package.json" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "::notice::resumes/web-builder/package.json not found — skipping npm audit"
fi
- uses: actions/setup-node@v4
if: steps.check-webbuilder.outputs.exists == 'true'
with:
node-version: '20'
cache: npm
cache-dependency-path: resumes/web-builder/package-lock.json
- name: Install dependencies
if: steps.check-webbuilder.outputs.exists == 'true'
working-directory: resumes/web-builder
run: npm ci
- name: Audit for vulnerabilities (moderate+)
if: steps.check-webbuilder.outputs.exists == 'true'
working-directory: resumes/web-builder
# --audit-level=moderate fails on moderate, high, and critical issues
run: npm audit --audit-level=moderate

# ──────────────────────────────────────────────────────────────
Expand All @@ -68,7 +77,7 @@ jobs:

# --- TypeScript / React (standard auto rules) ---
- name: Semgrep – TypeScript/React auto rules
if: ${{ hashFiles('resumes/web-builder/src') != '' }}
if: ${{ hashFiles('resumes/web-builder/src/**') != '' }}
# Exit 0 so we always upload SARIF; pipeline gating is done via SARIF review
run: |
semgrep scan \
Expand All @@ -80,7 +89,7 @@ jobs:

- name: Upload TypeScript SARIF
if: always() && hashFiles('semgrep-ts.sarif') != ''
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: semgrep-ts.sarif
category: semgrep-typescript
Expand All @@ -97,7 +106,7 @@ jobs:

- name: Upload LLM OWASP SARIF
if: always() && hashFiles('semgrep-llm.sarif') != ''
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: semgrep-llm.sarif
category: semgrep-llm-owasp
Expand Down
Loading