diff --git a/.github/semgrep/llm-owasp.yaml b/.github/semgrep/llm-owasp.yaml index 12672ad..cc0ed6a 100644 --- a/.github/semgrep/llm-owasp.yaml +++ b/.github/semgrep/llm-owasp.yaml @@ -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 @@ -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: diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 8e7823b..df94907 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -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 # ────────────────────────────────────────────────────────────── @@ -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 \ @@ -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 @@ -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