Unified security code scanning system with CodeQL and Semgrep
This monorepo provides a reusable security scanning workflow with automatic language detection and parallel execution:
.github/workflows/security-scan.yml- Main reusable workflow (orchestrator)packages/language-detector/- Detects languages and creates scan matrixpackages/codeql-action/- Custom CodeQL analysis with repo-specific configspackages/semgrep-action/- Semgrep pattern-based scanner
Add to your repository's .github/workflows/security.yml:
name: 'Security Scan'
on: [push, pull_request]
jobs:
security-scan:
uses: MetaMask/action-security-code-scanner/.github/workflows/security-scan.yml@v2
with:
scanner-ref: v2
permissions:
actions: read
contents: read
security-events: writeThe workflow will:
- Auto-detect languages in your repository
- Load repo-specific config from
repo-configs/(or use defaults) - Run CodeQL and Semgrep scans in parallel
- Upload SARIF results to GitHub Security tab
Option 1: File-based config (recommended)
Create repo-configs/<your-repo-name>.js in this monorepo:
const config = {
pathsIgnored: ['test', 'docs'],
rulesExcluded: ['js/log-injection'],
languages_config: [
{
language: 'java-kotlin',
build_mode: 'manual',
build_command: './gradlew build',
version: '21',
distribution: 'temurin',
},
],
queries: [
{ name: 'Security queries', uses: './query-suites/base.qls' },
{
name: 'Custom queries',
uses: './custom-queries/query-suites/custom-queries.qls',
},
],
};
export default config;Option 2: Workflow input (overrides file config)
jobs:
security-scan:
uses: metamask/security-codescanner-monorepo/.github/workflows/security-scan.yml@main
with:
repo: ${{ github.repository }}
languages_config: |
[
{
"language": "java-kotlin",
"build_mode": "manual",
"build_command": "./gradlew build",
"version": "21"
}
]
paths_ignored: 'test,docs'
rules_excluded: 'js/log-injection,py/sql-injection'When testing changes to the security scanner itself from a dev branch, you must explicitly pass the ref input:
jobs:
security-scan:
uses: metamask/security-codescanner-monorepo/.github/workflows/security-scan.yml@dev-branch
with:
repo: ${{ github.repository }}
ref: dev-branch # Must explicitly pass the branch nameNote: The @branch in the uses: statement only affects which workflow file is used. The ref input ensures all internal monorepo checkouts use the same branch.
security-scanner-monorepo/
βββ .github/workflows/
β βββ security-scan.yml # Main reusable workflow
βββ packages/
β βββ language-detector/ # Language detection & matrix creation
β βββ codeql-action/ # CodeQL scanner
β β βββ repo-configs/ # Repository-specific configs
β β βββ query-suites/ # CodeQL query suites
β β βββ scripts/ # Config generation scripts
β β βββ src/ # Shared utilities
β βββ semgrep-action/ # Semgrep scanner
βββ SECURITY.md # Security model documentation
# Install dependencies
yarn install
# Run linting
yarn lint
# Fix formatting
yarn lint:fix# Test language detector
yarn workspace @metamask/language-detector test
# Test with integration tests
yarn workspace @metamask/language-detector test:integration# Run command in specific package
yarn workspace @metamask/language-detector <command>
# Run command in all packages
yarn workspaces foreach run <command>{
// Paths to ignore during scan
pathsIgnored: ['test', 'vendor'],
// Rule IDs to exclude
rulesExcluded: ['js/log-injection'],
// Per-language configuration
languages_config: [
{
language: 'java-kotlin', // CodeQL language
ignore: false, // Skip this language (optional)
build_mode: 'manual', // 'none', 'autobuild', or 'manual'
build_command: './gradlew build',
version: '21', // Language/runtime version
distribution: 'temurin' // Distribution (Java/Node.js)
}
],
// CodeQL query suites
queries: [
{ name: 'Base queries', uses: './query-suites/base.qls' }
]
}CodeQL:
- JavaScript/TypeScript β
javascript-typescript - Python β
python - Java/Kotlin β
java-kotlin - Go β
go - C/C++ β
cpp - C# β
csharp - Ruby β
ruby
Semgrep: All languages (language-agnostic pattern matching)
- Detects languages via GitHub API
- Maps to appropriate scanners
- Configurable per-repository
- Parallel scanning per language
- Matrix-based job strategy
- Fail-fast for ignored languages
- File-based configs (single source of truth)
- Workflow input overrides
- Per-language build settings
- Minimal token permissions (
contents: read,security-events: write) - Input validation and sanitization
- See SECURITY.md for threat model
- Check GitHub's language detection (repo insights β languages)
- Ensure language is in
LANGUAGE_MAPPINGinlanguage-detector/src/job-configurator.js - Add manual
languages_configin workflow input
- Verify
build_commandin repo config - Check if correct
versionanddistributionare specified - Review CodeQL build logs in Actions
- Repo config filename must match repo name:
owner/repoβrepo.js - Ensure config file exports with
export default config - Check config-loader logs in workflow output
- Add required permissions to calling workflow:
permissions: actions: read contents: read security-events: write
ISC
See SECURITY.md for security model and REVIEW_TRACKING.md for current development status.