Update codeql.yml #4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "CodeQL Advanced Security Analysis" | |
| on: | |
| push: | |
| branches: [ "main", "develop", "release/**" ] | |
| pull_request: | |
| branches: [ "main", "develop" ] | |
| schedule: | |
| # Run at 2 AM UTC every day for continuous monitoring | |
| - cron: '0 2 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| scan_level: | |
| description: 'Security scan level' | |
| required: true | |
| default: 'standard' | |
| type: choice | |
| options: | |
| - standard | |
| - aggressive | |
| - maximum | |
| # Prevent multiple scans running simultaneously | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| analyze: | |
| name: Analyze TypeScript/Next.js/tRPC | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 360 | |
| permissions: | |
| security-events: write | |
| packages: read | |
| actions: read | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@v2 | |
| with: | |
| egress-policy: audit | |
| disable-sudo: true | |
| disable-file-monitoring: false | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: latest | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'pnpm' | |
| - name: Install Dependencies | |
| run: | | |
| pnpm install --frozen-lockfile --ignore-scripts | |
| env: | |
| NODE_ENV: development | |
| - name: Configure CodeQL Query Packs | |
| id: query-config | |
| run: | | |
| echo "Creating advanced CodeQL configuration for Next.js/tRPC..." | |
| cat > codeql-config.yml << 'EOF' | |
| name: "Next.js tRPC Security Configuration" | |
| disable-default-queries: false | |
| queries: | |
| - uses: security-extended | |
| - uses: security-and-quality | |
| query-filters: | |
| - exclude: | |
| id: js/unused-local-variable | |
| - exclude: | |
| tags: documentation | |
| paths-ignore: | |
| - node_modules | |
| - .next | |
| - out | |
| - dist | |
| - build | |
| - coverage | |
| - public | |
| - '**/*.test.ts' | |
| - '**/*.test.tsx' | |
| - '**/*.spec.ts' | |
| - '**/*.spec.tsx' | |
| - '**/test/**' | |
| - '**/tests/**' | |
| - '**/__tests__/**' | |
| - '**/__mocks__/**' | |
| paths: | |
| - src | |
| - app | |
| - pages | |
| - server | |
| - lib | |
| - utils | |
| - api | |
| EOF | |
| # Create custom queries directory | |
| mkdir -p .github/codeql/custom-queries | |
| # tRPC Input Validation Query | |
| cat > .github/codeql/custom-queries/trpc-input-validation.ql << 'TRPCEOF' | |
| /** | |
| * @name tRPC Missing Input Validation | |
| * @description Detects tRPC procedures without proper input validation using Zod | |
| * @kind problem | |
| * @problem.severity error | |
| * @security-severity 8.5 | |
| * @precision high | |
| * @id custom/trpc-input-validation | |
| * @tags security | |
| * trpc | |
| * input-validation | |
| */ | |
| import javascript | |
| from DataFlow::CallNode procedureCall | |
| where | |
| procedureCall.getCalleeName().regexpMatch(".*(query|mutation)") and | |
| procedureCall.getReceiver().toString().matches("%router%") and | |
| not exists(DataFlow::MethodCallNode inputCall | | |
| inputCall.getMethodName() = "input" and | |
| inputCall.flowsTo(procedureCall) | |
| ) | |
| select procedureCall, "tRPC procedure without input validation schema" | |
| TRPCEOF | |
| # SQL Injection Detection for Prisma/TypeORM | |
| cat > .github/codeql/custom-queries/sql-injection.ql << 'SQLEOF' | |
| /** | |
| * @name SQL Injection via Raw Queries | |
| * @description Detects potential SQL injection in raw database queries | |
| * @kind path-problem | |
| * @problem.severity error | |
| * @security-severity 9.8 | |
| * @precision high | |
| * @id custom/sql-injection | |
| * @tags security | |
| * external/cwe/cwe-089 | |
| */ | |
| import javascript | |
| import DataFlow::PathGraph | |
| class SqlInjectionConfig extends TaintTracking::Configuration { | |
| SqlInjectionConfig() { this = "SqlInjectionConfig" } | |
| override predicate isSource(DataFlow::Node source) { | |
| source instanceof RemoteFlowSource | |
| } | |
| override predicate isSink(DataFlow::Node sink) { | |
| exists(DataFlow::MethodCallNode call | | |
| call.getMethodName().regexpMatch("(\\$queryRaw|\\$executeRaw|query|raw)") and | |
| sink = call.getArgument(0) | |
| ) | |
| } | |
| } | |
| from SqlInjectionConfig config, DataFlow::PathNode source, DataFlow::PathNode sink | |
| where config.hasFlowPath(source, sink) | |
| select sink.getNode(), source, sink, | |
| "Potential SQL injection from $@", source.getNode(), "user input" | |
| SQLEOF | |
| # NoSQL Injection Detection | |
| cat > .github/codeql/custom-queries/nosql-injection.ql << 'NOSQLEOF' | |
| /** | |
| * @name NoSQL Injection Detection | |
| * @description Detects potential NoSQL injection in MongoDB queries | |
| * @kind path-problem | |
| * @problem.severity error | |
| * @security-severity 9.8 | |
| * @precision high | |
| * @id custom/nosql-injection | |
| * @tags security | |
| * external/cwe/cwe-943 | |
| */ | |
| import javascript | |
| import DataFlow::PathGraph | |
| class NoSqlInjectionConfig extends TaintTracking::Configuration { | |
| NoSqlInjectionConfig() { this = "NoSqlInjectionConfig" } | |
| override predicate isSource(DataFlow::Node source) { | |
| source instanceof RemoteFlowSource | |
| } | |
| override predicate isSink(DataFlow::Node sink) { | |
| exists(DataFlow::MethodCallNode call | | |
| call.getMethodName().regexpMatch("(find|findOne|findMany|update|delete|aggregate)") and | |
| sink = call.getArgument(0) | |
| ) | |
| } | |
| } | |
| from NoSqlInjectionConfig config, DataFlow::PathNode source, DataFlow::PathNode sink | |
| where config.hasFlowPath(source, sink) | |
| select sink.getNode(), source, sink, | |
| "Potential NoSQL injection from $@", source.getNode(), "user input" | |
| NOSQLEOF | |
| # API Over-fetching Detection | |
| cat > .github/codeql/custom-queries/api-overfetching.ql << 'APIEOF' | |
| /** | |
| * @name Database Query Over-fetching | |
| * @description Detects database queries without pagination or field selection | |
| * @kind problem | |
| * @problem.severity warning | |
| * @security-severity 6.0 | |
| * @precision medium | |
| * @id custom/api-overfetching | |
| * @tags security | |
| * performance | |
| * database | |
| */ | |
| import javascript | |
| from DataFlow::MethodCallNode call | |
| where | |
| call.getMethodName().regexpMatch("(findMany|findAll)") and | |
| not exists(DataFlow::Node arg | | |
| arg = call.getArgument(0) and | |
| arg.asExpr().(ObjectExpr).getAProperty().getName().matches("take") | |
| ) and | |
| not exists(DataFlow::Node arg | | |
| arg = call.getArgument(0) and | |
| arg.asExpr().(ObjectExpr).getAProperty().getName().matches("select") | |
| ) | |
| select call, "Database query without pagination (take/skip) or field selection (select)" | |
| APIEOF | |
| # Next.js Server-Side Security | |
| cat > .github/codeql/custom-queries/nextjs-server-security.ql << 'NEXTEOF' | |
| /** | |
| * @name Next.js Exposed Server-Side Data | |
| * @description Detects sensitive data potentially exposed to client | |
| * @kind problem | |
| * @problem.severity error | |
| * @security-severity 7.5 | |
| * @precision medium | |
| * @id custom/nextjs-server-security | |
| * @tags security | |
| * nextjs | |
| * data-exposure | |
| */ | |
| import javascript | |
| from DataFlow::Node returnNode | |
| where | |
| exists(Function f | | |
| f.getName().regexpMatch("(getServerSideProps|getStaticProps)") and | |
| returnNode.asExpr().getEnclosingFunction() = f | |
| ) and | |
| returnNode.toString().regexpMatch(".*(password|secret|token|key|credential).*") | |
| select returnNode, "Potential exposure of sensitive data in server-side props" | |
| NEXTEOF | |
| # Authentication & Authorization Checks | |
| cat > .github/codeql/custom-queries/missing-auth.ql << 'AUTHEOF' | |
| /** | |
| * @name Missing Authentication Check | |
| * @description Detects API routes or tRPC procedures without authentication | |
| * @kind problem | |
| * @problem.severity error | |
| * @security-severity 8.0 | |
| * @precision medium | |
| * @id custom/missing-auth | |
| * @tags security | |
| * authentication | |
| * authorization | |
| */ | |
| import javascript | |
| from DataFlow::FunctionNode handler | |
| where | |
| ( | |
| // Next.js API routes | |
| exists(ExportAssignment exp | | |
| exp.getExpression() = handler.asExpr() | |
| ) or | |
| // tRPC procedures | |
| exists(DataFlow::MethodCallNode call | | |
| call.getMethodName().regexpMatch("(query|mutation)") and | |
| handler = call.getArgument(0) | |
| ) | |
| ) and | |
| not exists(DataFlow::Node authCheck | | |
| authCheck.toString().regexpMatch(".*(auth|session|token|user).*") and | |
| authCheck.asExpr().getEnclosingFunction() = handler.getFunction() | |
| ) | |
| select handler, "Handler without apparent authentication check" | |
| AUTHEOF | |
| # XSS Detection | |
| cat > .github/codeql/custom-queries/xss-detection.ql << 'XSSEOF' | |
| /** | |
| * @name Cross-Site Scripting (XSS) Vulnerability | |
| * @description Detects potential XSS vulnerabilities in React components | |
| * @kind path-problem | |
| * @problem.severity error | |
| * @security-severity 9.0 | |
| * @precision high | |
| * @id custom/xss-detection | |
| * @tags security | |
| * external/cwe/cwe-079 | |
| */ | |
| import javascript | |
| import DataFlow::PathGraph | |
| class XssConfig extends TaintTracking::Configuration { | |
| XssConfig() { this = "XssConfig" } | |
| override predicate isSource(DataFlow::Node source) { | |
| source instanceof RemoteFlowSource | |
| } | |
| override predicate isSink(DataFlow::Node sink) { | |
| exists(JSXElement jsx | | |
| sink.asExpr() = jsx.getAttributeByName("dangerouslySetInnerHTML").getValue() | |
| ) | |
| } | |
| } | |
| from XssConfig config, DataFlow::PathNode source, DataFlow::PathNode sink | |
| where config.hasFlowPath(source, sink) | |
| select sink.getNode(), source, sink, | |
| "Potential XSS vulnerability from $@", source.getNode(), "user input" | |
| XSSEOF | |
| # Environment Variable Exposure | |
| cat > .github/codeql/custom-queries/env-exposure.ql << 'ENVEOF' | |
| /** | |
| * @name Environment Variable Client Exposure | |
| * @description Detects server-side environment variables exposed to client | |
| * @kind problem | |
| * @problem.severity error | |
| * @security-severity 8.0 | |
| * @precision high | |
| * @id custom/env-exposure | |
| * @tags security | |
| * configuration | |
| */ | |
| import javascript | |
| from DataFlow::PropRead envAccess | |
| where | |
| envAccess.getBase().toString().matches("%process.env%") and | |
| not envAccess.getPropertyName().matches("NEXT_PUBLIC_%") and | |
| exists(File f | | |
| f = envAccess.getFile() and | |
| not f.getRelativePath().matches("%/server/%") and | |
| not f.getRelativePath().matches("%/api/%") | |
| ) | |
| select envAccess, "Server-side environment variable potentially exposed to client" | |
| ENVEOF | |
| - name: Initialize CodeQL | |
| uses: github/codeql-action/init@v4 | |
| with: | |
| languages: javascript-typescript | |
| build-mode: none | |
| queries: +security-extended,+security-and-quality,+.github/codeql/custom-queries | |
| config-file: ./codeql-config.yml | |
| db-location: '${{ runner.temp }}/codeql_databases' | |
| setup-python-dependencies: false | |
| tools: latest | |
| ram: 8192 | |
| threads: 0 | |
| - name: Perform CodeQL Analysis | |
| uses: github/codeql-action/analyze@v4 | |
| with: | |
| category: "/language:typescript" | |
| output: sarif-results | |
| upload: true | |
| checkout_path: ${{ github.workspace }} | |
| add-snippets: true | |
| - name: Upload SARIF Results | |
| uses: github/codeql-action/upload-sarif@v4 | |
| if: always() | |
| with: | |
| sarif_file: sarif-results/javascript-typescript.sarif | |
| category: typescript | |
| wait-for-processing: true | |
| - name: Generate Security Report | |
| if: always() | |
| run: | | |
| echo "## Security Analysis Report" > security-report.md | |
| echo "**Language:** TypeScript (Next.js/tRPC)" >> security-report.md | |
| echo "**Timestamp:** $(date -u)" >> security-report.md | |
| echo "" >> security-report.md | |
| if [ -d "sarif-results" ]; then | |
| echo "### Analysis Complete" >> security-report.md | |
| echo "Results have been uploaded to GitHub Security tab." >> security-report.md | |
| if command -v jq &> /dev/null && [ -f "sarif-results/javascript-typescript.sarif" ]; then | |
| CRITICAL=$(jq '[.runs[].results[] | select(.level=="error")] | length' sarif-results/javascript-typescript.sarif 2>/dev/null || echo "0") | |
| WARNING=$(jq '[.runs[].results[] | select(.level=="warning")] | length' sarif-results/javascript-typescript.sarif 2>/dev/null || echo "0") | |
| NOTE=$(jq '[.runs[].results[] | select(.level=="note")] | length' sarif-results/javascript-typescript.sarif 2>/dev/null || echo "0") | |
| echo "" >> security-report.md | |
| echo "**Findings Summary:**" >> security-report.md | |
| echo "- Critical Issues: $CRITICAL" >> security-report.md | |
| echo "- Warnings: $WARNING" >> security-report.md | |
| echo "- Notes: $NOTE" >> security-report.md | |
| if [ "$CRITICAL" -gt 0 ]; then | |
| echo "" >> security-report.md | |
| echo "**Action Required:** Critical security issues detected. Review immediately." >> security-report.md | |
| fi | |
| fi | |
| fi | |
| - name: Comment PR with Results | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| if (fs.existsSync('security-report.md')) { | |
| const report = fs.readFileSync('security-report.md', 'utf8'); | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: report | |
| }); | |
| } | |
| - name: Check for Critical Vulnerabilities | |
| if: always() | |
| run: | | |
| if [ -d "sarif-results" ] && command -v jq &> /dev/null && [ -f "sarif-results/javascript-typescript.sarif" ]; then | |
| CRITICAL=$(jq '[.runs[].results[] | select(.level=="error")] | length' sarif-results/javascript-typescript.sarif 2>/dev/null || echo "0") | |
| if [ "$CRITICAL" -gt 0 ]; then | |
| echo "::error::Found $CRITICAL critical security issues!" | |
| echo "Please review the Security tab for details." | |
| echo "" | |
| echo "Critical issues found in:" | |
| jq -r '.runs[].results[] | select(.level=="error") | "- \(.ruleId): \(.message.text)"' sarif-results/javascript-typescript.sarif 2>/dev/null || true | |
| exit 1 | |
| else | |
| echo "::notice::No critical security issues found." | |
| fi | |
| fi | |
| dependency-review: | |
| name: Dependency Security Review | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Dependency Review | |
| uses: actions/dependency-review-action@v4 | |
| with: | |
| fail-on-severity: moderate | |
| deny-licenses: GPL-3.0, AGPL-3.0 | |
| comment-summary-in-pr: always | |
| vulnerability-check: true | |
| license-check: true | |
| security-summary: | |
| name: Security Analysis Summary | |
| needs: [analyze, dependency-review] | |
| runs-on: ubuntu-latest | |
| if: always() | |
| permissions: | |
| security-events: read | |
| contents: read | |
| steps: | |
| - name: Generate Summary | |
| run: | | |
| echo "# Security Analysis Complete" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "## Analysis Results" >> $GITHUB_STEP_SUMMARY | |
| echo "- CodeQL Analysis: ${{ needs.analyze.result }}" >> $GITHUB_STEP_SUMMARY | |
| if [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| echo "- Dependency Review: ${{ needs.dependency-review.result }}" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "## Checks Performed" >> $GITHUB_STEP_SUMMARY | |
| echo "- SQL Injection (Prisma raw queries)" >> $GITHUB_STEP_SUMMARY | |
| echo "- NoSQL Injection (MongoDB queries)" >> $GITHUB_STEP_SUMMARY | |
| echo "- tRPC input validation" >> $GITHUB_STEP_SUMMARY | |
| echo "- Database over-fetching" >> $GITHUB_STEP_SUMMARY | |
| echo "- XSS vulnerabilities" >> $GITHUB_STEP_SUMMARY | |
| echo "- Authentication checks" >> $GITHUB_STEP_SUMMARY | |
| echo "- Environment variable exposure" >> $GITHUB_STEP_SUMMARY | |
| echo "- Next.js server-side data leaks" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "View detailed results in the [Security tab](https://github.com/${{ github.repository }}/security/code-scanning)" >> $GITHUB_STEP_SUMMARY |