├── .gitallowed ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── features-request-------.md │ └── issue-with-ruleset------.md ├── renovate.json └── workflows │ ├── full-loop.yml │ ├── lint.yml │ ├── loop.yml │ └── semgrep-self-test.yml ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── SECURITY.md ├── actions ├── add-maintainer-custom-property │ ├── action.cjs │ └── action.yml ├── add-runtime-custom-property │ ├── action.cjs │ └── action.yml ├── check-new-repos │ ├── action.cjs │ └── action.yml ├── dependabot-auto-dismiss │ ├── action.cjs │ ├── action.yml │ └── dismiss.txt ├── dependabot-nudge │ ├── action.cjs │ └── action.yml ├── main │ ├── action.cjs │ └── action.yml ├── older-than-2y │ ├── action.cjs │ └── action.yml └── renovate-sanity-check │ ├── action.cjs │ └── action.yml ├── assets ├── cleaner.rb ├── debug.sh ├── dtd │ ├── blocklist.txt │ └── svg11-secure-flat.dtd ├── npm-audit.py ├── pip-audit.py ├── reviewdog.sh ├── reviewdog │ └── reviewdog.yml ├── scripttagextractor.py ├── semgrep_rules │ ├── blocklist-brave-core.txt │ ├── blocklist-bsg.txt │ ├── blocklist-internal.txt │ ├── blocklist-static.txt │ ├── blocklist.txt │ ├── client │ │ ├── android-profile-original.java │ │ ├── android-profile-original.yaml │ │ ├── android-profile-static.java │ │ ├── android-profile-static.yaml │ │ ├── android-resolve-intent.java │ │ ├── android-resolve-intent.kt │ │ ├── android-resolve-intent.yaml │ │ ├── brave-execute-script-ios.swift │ │ ├── brave-execute-script-ios.yaml │ │ ├── brave-execute-script.cpp │ │ ├── brave-execute-script.yaml │ │ ├── brave-isolated-world.cpp │ │ ├── brave-isolated-world.yaml │ │ ├── brave-missing-break-in-switch.c │ │ ├── brave-missing-break-in-switch.yaml │ │ ├── browser-dependency-inversion.cc │ │ ├── browser-dependency-inversion.yaml │ │ ├── cast-signed-to-unsigned.c │ │ ├── cast-signed-to-unsigned.yaml │ │ ├── check_includes.gni │ │ ├── check_includes.yaml │ │ ├── chromium-insecure-gurl.cpp │ │ ├── chromium-insecure-gurl.yaml │ │ ├── chromium-uaf.cpp │ │ ├── chromium-uaf.yaml │ │ ├── const_cast.cpp │ │ ├── const_cast.yaml │ │ ├── dangling-pointer-trait.cc │ │ ├── dangling-pointer-trait.yaml │ │ ├── get-visible-entry.cc │ │ ├── get-visible-entry.yaml │ │ ├── glide-library.java │ │ ├── glide-library.yaml │ │ ├── in-process-browser-test.cc │ │ ├── in-process-browser-test.yaml │ │ ├── interesting-api-calls.cc │ │ ├── interesting-api-calls.yaml │ │ ├── licensing.html │ │ ├── licensing.txt │ │ ├── licensing.yaml │ │ ├── mismatched-memory-management-cpp.cpp │ │ ├── mismatched-memory-management-cpp.yaml │ │ ├── privacy.en.md │ │ ├── privacy.grd │ │ ├── privacy.md │ │ ├── privacy.yaml │ │ ├── refcounted-usage.cpp │ │ ├── refcounted-usage.yaml │ │ ├── reinterpret_cast.cpp │ │ ├── reinterpret_cast.yaml │ │ ├── signed-unsigned-conversion.yaml │ │ ├── typos.c │ │ ├── typos.cc │ │ ├── typos.yaml │ │ ├── unsafe-cpp-constructs.cpp │ │ ├── unsafe-cpp-constructs.yaml │ │ ├── unsafejs-in-cpp.cc │ │ ├── unsafejs-in-cpp.yaml │ │ ├── web-contents-user-data.cc │ │ ├── web-contents-user-data.yaml │ │ ├── web-ui-origin-encoding.ts │ │ └── web-ui-origin-encoding.yaml │ ├── frozen │ │ └── nonfree │ │ │ ├── audit.yaml │ │ │ ├── others.yaml │ │ │ ├── security_noaudit_novuln.yaml │ │ │ └── vulns.yaml │ ├── generate-compound.rb │ ├── generated │ │ └── oss │ │ │ ├── audit.yaml │ │ │ ├── others.yaml │ │ │ ├── security_noaudit_novuln.yaml │ │ │ └── vulns.yaml │ ├── services │ │ ├── activerecord-sanitize-sql-noop.rb │ │ ├── activerecord-sanitize-sql-noop.yaml │ │ ├── brave-third-party-action-not-pinned-to-commit-sha.test.yaml │ │ ├── brave-third-party-action-not-pinned-to-commit-sha.yaml │ │ ├── detected-aws-access-key-id-value.js │ │ ├── detected-aws-access-key-id-value.yaml │ │ ├── docker-compose-no-new-privileges.test.yaml │ │ ├── docker-compose-no-new-privileges.yaml │ │ ├── find-links-without-no-index.txt │ │ ├── find-links-without-no-index.yaml │ │ ├── http-parse-multipart-dos.go │ │ ├── http-parse-multipart-dos.yaml │ │ ├── insecure-types.go │ │ ├── insecure-types.yaml │ │ ├── internal-digest-call.py │ │ ├── internal-digest-call.yaml │ │ ├── io-readall-dos.go │ │ ├── io-readall-dos.yaml │ │ ├── jinja-safe-usages.html │ │ ├── jinja-safe-usages.yaml │ │ ├── missing-integrity.html │ │ ├── missing-integrity.yaml │ │ ├── missing-noopener-window-open-native.js │ │ ├── missing-noopener-window-open-native.yaml │ │ ├── missing-noopener-window-open.html │ │ ├── missing-noopener-window-open.yaml │ │ ├── no-backticks-in-js-handlers.html │ │ ├── no-backticks-in-js-handlers.yaml │ │ ├── nodejs-insecure-url-parse.js │ │ ├── nodejs-insecure-url-parse.yaml │ │ ├── not-using-hasownproperty-proto-access.js │ │ ├── not-using-hasownproperty-proto-access.yaml │ │ ├── path-travesal-by-string-interpolation.ts │ │ ├── path-travesal-by-string-interpolation.yaml │ │ ├── pip-extra-index-url.Makefile │ │ ├── pip-extra-index-url.yaml │ │ ├── react-href-var.tsx │ │ ├── react-href-var.yaml │ │ ├── ssti.go │ │ ├── ssti.yaml │ │ ├── starts-with-partial-host-py.py │ │ ├── starts-with-partial-host-py.yaml │ │ ├── svelte-html-usages.svelte │ │ ├── svelte-html-usages.yaml │ │ ├── svelte-purifyConfig-usage.svelte │ │ ├── svelte-purifyConfig-usage.yaml │ │ ├── url-constructor-base.js │ │ ├── url-constructor-base.yaml │ │ ├── var-in-href.html │ │ ├── var-in-href.yaml │ │ ├── var-in-script-tag.html │ │ └── var-in-script-tag.yaml │ └── update-ruleset.rb ├── tfsec.jq ├── tfsec.sh └── xmllint.sh ├── package-lock.json ├── package.json ├── requirements.txt ├── run.js ├── socket.yml ├── src ├── addMaintainerCustomProperty.js ├── dependabotDismiss.js ├── dependabotNudge.js ├── getConfig.js ├── getMaintainers.js ├── getProperties.js ├── kubeGetRepositories.js ├── pullRequestChangedFiles.js ├── renovateSanityCheck.js ├── sendSlackMessage.js ├── steps │ ├── assigneeRemoved.js │ ├── assigneesAfter.js │ ├── cleanupComments.js │ ├── commentsNumber.js │ ├── hotwords.js │ └── unverifiedCommits.js └── updateRuntimeProperty.js └── t3sts ├── brakeman └── app │ └── models │ └── thing.rb ├── dtd ├── $pac-color.svg ├── script.svg └── theme=🌚 dark.svg ├── npmaudit └── package-lock.json ├── pipaudit ├── pyproject.toml └── requirements-flask.txt └── tf ├── example.tf └── main.tf /.gitallowed: -------------------------------------------------------------------------------- 1 | AKIABBBBBBBBBBBBBB1B 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @brave/sec-team 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/features-request-------.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Features request: `...`' 3 | about: Include a new feature in the `security-action` 4 | title: '' 5 | labels: enhancement 6 | assignees: thypon, kdenhartog 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-with-ruleset------.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue with ruleset `...` 3 | about: Describe this issue with the ruleset 4 | title: '' 5 | labels: bug 6 | assignees: thypon, kdenhartog 7 | 8 | --- 9 | 10 | Reference: ... 11 | 12 | # Proposed Solution 13 | 14 | - [ ] Remove the rule 15 | - [ ] Fork and improve the rule 16 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>brave/renovate-config", 5 | "helpers:pinGitHubActionDigests" 6 | ], 7 | "ignorePaths": [ 8 | "t3sts/", 9 | "assets/semgrep_rules/" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/full-loop.yml: -------------------------------------------------------------------------------- 1 | name: full-loop 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | types: [opened, synchronize, reopened, ready_for_review] 8 | branches: [main] 9 | permissions: 10 | contents: read 11 | jobs: 12 | full-loop: 13 | name: full-loop 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 17 | with: 18 | fetch-depth: 0 19 | - uses: ./actions/main 20 | name: Run action on full security-action repo 21 | id: action 22 | with: 23 | debug: true 24 | github_token: ${{ secrets.GITHUB_TOKEN }} 25 | baseline_scan_only: false 26 | gh_to_slack_user_map: ${{ secrets.GH_TO_SLACK_USER_MAP }} 27 | - run: | 28 | set -e 29 | if ((${{ fromJson(steps.action.outputs.reviewdog-findings) }} < 106)); then 30 | echo "Too few reviewdog findings" 31 | exit 1 32 | fi 33 | if ((${{ fromJson(steps.action.outputs.safesvg-count) }} < 2)); then 34 | echo "Too few safesvg findings" 35 | exit 1 36 | fi 37 | if ((${{ fromJson(steps.action.outputs.tfsec-count) }} < 4)); then 38 | echo "Too few tfsec findings" 39 | exit 1 40 | fi 41 | if ((${{ fromJson(steps.action.outputs.semgrep-count) }} < 97)); then 42 | echo "Too few semgrep findings" 43 | exit 1 44 | fi 45 | if ((${{ fromJson(steps.action.outputs.sveltegrep-count) }} < 3)); then 46 | echo "Too few sveltegrep findings" 47 | exit 1 48 | fi 49 | if ((${{ fromJson(steps.action.outputs.npm-audit-count) }} < 3)); then 50 | echo "Too few npm-audit findings" 51 | exit 1 52 | fi 53 | if ((${{ fromJson(steps.action.outputs.pip-audit-count) }} < 2)); then 54 | echo "Too few pip-audit findings" 55 | exit 1 56 | fi 57 | shell: bash 58 | name: Check number of findings 59 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run lint and npm audit. 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: lint and audit 5 | on: 6 | push: 7 | branches: [main] 8 | pull_request: 9 | branches: [main] 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | node-version: [18.x] 16 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 17 | steps: 18 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | cache: 'npm' 24 | - run: npm ci 25 | - if: ${{ github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]' }} 26 | run: npm audit 27 | - run: npm run lint 28 | -------------------------------------------------------------------------------- /.github/workflows/loop.yml: -------------------------------------------------------------------------------- 1 | name: loop 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | types: [opened, synchronize, reopened, ready_for_review] 8 | branches: [main] 9 | permissions: 10 | actions: read 11 | contents: read 12 | pull-requests: write 13 | security-events: write 14 | jobs: 15 | loop: 16 | name: loop 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | steps: 21 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 22 | with: 23 | fetch-depth: 0 24 | - run: | 25 | pwd 26 | tree -a 27 | shell: bash 28 | - uses: ./actions/main 29 | with: 30 | debug: true 31 | github_token: ${{ secrets.GITHUB_TOKEN }} 32 | gh_to_slack_user_map: ${{ secrets.GH_TO_SLACK_USER_MAP }} 33 | -------------------------------------------------------------------------------- /.github/workflows/semgrep-self-test.yml: -------------------------------------------------------------------------------- 1 | name: semgrep-self-test 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | types: [opened, synchronize, reopened, ready_for_review] 8 | branches: [main] 9 | permissions: 10 | # This is a public repo, no permissions required to clone 11 | contents: none 12 | jobs: 13 | semgrep-self-test: 14 | name: semgrep-self-test 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 18 | - run: | 19 | python3 -m pip --disable-pip-version-check install -r requirements.txt 20 | shell: bash 21 | - run: | 22 | cd assets/semgrep_rules/; semgrep --test --disable-version-check --strict --metrics=off 23 | shell: bash 24 | - run: | 25 | JSON=$(semgrep \ 26 | --disable-version-check --strict --metrics=off --json \ 27 | $(find assets/semgrep_rules -name '*.yml' -or -name '*.yaml' -not -name '*.test.yml' -not -name '*.test.yaml' -not -path "assets/semgrep_rules/generated/*" | sed 's/^/-c /g') \ 28 | assets/semgrep_rules/{client,services} || true) 29 | ERRORS=$(echo "$JSON" | jq '.errors' || true) 30 | BADERRS=$(echo "$ERRORS" | jq '.[] | select(.level == "error")' || true) 31 | if [[ -n "$BADERRS" ]]; then 32 | echo "Semgrep rule / version issue: $BADERRS" 33 | exit 123 34 | fi 35 | RESULTCOUNT=$(echo "$JSON" | tr '\n' ' ' | tr '\t' ' ' | jq '.results | length') 36 | NUMRESULTS=$(grep -R ruleid: | wc -l) 37 | if [[ "$RESULTCOUNT" -lt "$NUMRESULTS" ]]; then 38 | echo "Found fewer than $NUMRESULTS semgrep results" 39 | exit 122 40 | fi 41 | shell: bash 42 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gem 'brakeman', '6.2.2' 3 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | brakeman (6.2.2) 5 | racc 6 | racc (1.8.1) 7 | 8 | PLATFORMS 9 | arm64-darwin-22 10 | arm64-darwin-23 11 | x86_64-linux 12 | 13 | DEPENDENCIES 14 | brakeman (= 6.2.2) 15 | 16 | BUNDLED WITH 17 | 2.4.10 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # security-action 2 | 3 | Composite GitHub CI Action[^1] containing the minimal viable security lint for brave repositories 4 | 5 | ## Usage 6 | 7 | Add an action under `.github/workflow/security-action.yml` with the following content: 8 | 9 | ```yml 10 | name: security 11 | on: 12 | workflow_dispatch: 13 | push: 14 | branches: [main] 15 | pull_request: 16 | types: [opened, synchronize, reopened, ready_for_review] 17 | branches: [main] 18 | 19 | jobs: 20 | security: 21 | name: security 22 | runs-on: ubuntu-latest 23 | strategy: 24 | fail-fast: false 25 | steps: 26 | - uses: actions/checkout@v3 27 | with: 28 | fetch-depth: 0 29 | - uses: brave/security-action/actions/main@main 30 | with: 31 | github_token: ${{ secrets.GITHUB_TOKEN }} 32 | slack_token: ${{ secrets.HOTSPOTS_SLACK_TOKEN }} # optional 33 | # by default assignees will be thypon, modify accordingly 34 | assignees: | 35 | yoursecuritycontact 36 | yoursecondsecuritycontact 37 | ``` 38 | 39 | ## Branching Strategy 40 | 41 | - main branch, this should be tracked and included by all the repositories, without versioning. It should be always "stable" and contain the latest and greatest security checks 42 | - feature/*, feature branches including new security checkers 43 | - bugfix/*, fixes for specific bugs in the action 44 | 45 | ## References 46 | 47 | [^1]: https://docs.github.com/en/actions/creating-actions/creating-a-composite-action 48 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | All versions including and above the current stable release version number (the version downloadable on https://brave.com/download). 6 | 7 | ## Reporting a Vulnerability 8 | 9 | See https://hackerone.com/brave for details. 10 | -------------------------------------------------------------------------------- /actions/add-maintainer-custom-property/action.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async ({ github, context, inputs, actionPath, core, debug = false }) => { 2 | const { default: addMaintainerCustomProperty } = await import(`${actionPath}/src/addMaintainerCustomProperty.js`) 3 | const { default: sendSlackMessage } = await import(`${actionPath}/src/sendSlackMessage.js`) 4 | 5 | const message = await addMaintainerCustomProperty({ 6 | org: context.repo.owner, 7 | github, 8 | ignoreMaintainers: inputs.ignore_maintainers, 9 | debug 10 | }) 11 | 12 | if (message.trim().length > 0) { 13 | await sendSlackMessage({ 14 | token: inputs.slack_token, 15 | message, 16 | channel: '#secops-hotspots', 17 | color: 'yellow', 18 | username: 'add-maintainer-custom-property' 19 | }) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /actions/add-maintainer-custom-property/action.yml: -------------------------------------------------------------------------------- 1 | # action that add maintainer as a custom property 2 | # to all repositories in this organization 3 | name: add-maintainer-custom-property 4 | description: Add Maintainer as Custom Property to Repositories 5 | inputs: 6 | github_token: 7 | description: 'GitHub Token' 8 | required: true 9 | slack_token: 10 | description: 'Slack Token' 11 | required: true 12 | ignore_maintainers: 13 | description: 'Comma separated list of maintainers to ignore' 14 | default: brave-builds,brave-browser-releases,brave-support-admin 15 | debug: 16 | description: 'Debug mode' 17 | required: false 18 | runs: 19 | using: 'composite' 20 | steps: 21 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 22 | with: 23 | node-version: '22.x' 24 | - id: npm 25 | run: cd ${{ github.action_path }}/../..; npm ci 26 | shell: bash 27 | - name: run 28 | id: run 29 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 30 | env: 31 | DEBUG: ${{ (inputs.debug == 'true' || runner.debug) && 'true' || 'false'}} 32 | with: 33 | github-token: ${{ inputs.github_token }} 34 | script: | 35 | const actionPath = '${{ github.action_path }}/../../' 36 | const inputs = ${{ toJson(inputs) }} 37 | 38 | const script = require('${{ github.action_path }}/action.cjs') 39 | await script({github, context, inputs, actionPath, core, 40 | debug: process.env.DEBUG === 'true'}) 41 | -------------------------------------------------------------------------------- /actions/add-runtime-custom-property/action.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async ({ github, context, inputs, actionPath, core, debug = false }) => { 2 | const { default: kubeGetRepositories } = await import(`${actionPath}/src/kubeGetRepositories.js`) 3 | const { default: updateRuntimeProperty } = await import(`${actionPath}/src/updateRuntimeProperty.js`) 4 | const org = context.repo.owner 5 | 6 | if (inputs.static_repositories) { 7 | await updateRuntimeProperty({ 8 | github, 9 | org, 10 | runtime: 'static', 11 | repositories: inputs.static_repositories, 12 | debug 13 | }) 14 | } 15 | 16 | if (inputs.runtime_directory) { 17 | const repositories = await kubeGetRepositories({ 18 | directory: inputs.runtime_directory, 19 | orgFilter: new RegExp(`^${org}$`), 20 | debug 21 | }) 22 | 23 | await updateRuntimeProperty({ 24 | github, 25 | org, 26 | core, 27 | runtime: 'bsg', 28 | repositories, 29 | debug 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /actions/add-runtime-custom-property/action.yml: -------------------------------------------------------------------------------- 1 | # action that add runtime as a custom property 2 | # to all repositories in this organization 3 | name: add-runtime-custom-property 4 | description: Add Runtime as Custom Property to Repositories 5 | inputs: 6 | github_token: 7 | description: 'GitHub Token' 8 | required: true 9 | static_repositories: 10 | description: 'Repositories that will have a `static` runtime' 11 | required: true 12 | runtime_directory: 13 | description: 'Directory where runtime files are stored' 14 | required: true 15 | debug: 16 | description: 'Debug mode' 17 | required: false 18 | runs: 19 | using: 'composite' 20 | steps: 21 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 22 | with: 23 | node-version: '22.x' 24 | - id: npm 25 | run: cd ${{ github.action_path }}/../..; npm ci 26 | shell: bash 27 | - name: run 28 | id: run 29 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 30 | env: 31 | DEBUG: ${{ (inputs.debug == 'true' || runner.debug) && 'true' || 'false'}} 32 | with: 33 | github-token: ${{ inputs.github_token }} 34 | script: | 35 | const actionPath = '${{ github.action_path }}/../../' 36 | const inputs = ${{ toJson(inputs) }} 37 | 38 | const script = require('${{ github.action_path }}/action.cjs') 39 | await script({github, context, inputs, actionPath, core, 40 | debug: process.env.DEBUG === 'true'}) 41 | -------------------------------------------------------------------------------- /actions/check-new-repos/action.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async ({ github, context, inputs, actionPath, core, debug = false }) => { 2 | const { default: sendSlackMessage } = await import(`${actionPath}/src/sendSlackMessage.js`) 3 | 4 | const query = `query ($owner: String!) { 5 | repositoryOwner(login: $owner) { 6 | repositories(last: 100) { 7 | totalCount 8 | nodes { 9 | name 10 | createdAt 11 | } 12 | } 13 | } 14 | }` 15 | const variables = { 16 | owner: context.repo.owner 17 | } 18 | const result = await github.graphql(query, variables) 19 | const totalCount = result.repositoryOwner.repositories.totalCount 20 | 21 | // DEBUG: console.log("totalCount: %s", totalCount) 22 | const repositories = result.repositoryOwner.repositories 23 | const yesterday = ((d) => d.setDate(d.getDate() - 1))(new Date()) 24 | const newerThanADay = repositories.nodes.filter( 25 | repo => new Date(repo.createdAt) > yesterday 26 | ) 27 | // DEBUG: console.log("NewerThanADay: %o", newerThanADay); 28 | let message = '' 29 | if (newerThanADay.length > 0) { 30 | message += `${newerThanADay.length} new repos in ${variables.owner}:\n\n` 31 | for (let i = 0; i < newerThanADay.length; i++) { 32 | message += `- ${newerThanADay[i].name}\n` 33 | } 34 | message += `\nTotal repositories in ${variables.owner}: ${totalCount}` 35 | 36 | core.setSecret(message) 37 | } 38 | 39 | if (message.trim().length > 0) { 40 | await sendSlackMessage({ 41 | token: inputs.slack_token, 42 | message, 43 | channel: '#secops-hotspots', 44 | color: 'yellow', 45 | username: 'check-new-repos' 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /actions/check-new-repos/action.yml: -------------------------------------------------------------------------------- 1 | name: check-new-repositories 2 | description: Check New Repositories 3 | inputs: 4 | github_token: 5 | description: 'GitHub Token' 6 | required: true 7 | slack_token: 8 | description: 'Slack Token' 9 | required: true 10 | debug: 11 | description: 'Debug mode' 12 | required: false 13 | runs: 14 | using: 'composite' 15 | steps: 16 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 17 | with: 18 | node-version: '22.x' 19 | - id: npm 20 | run: cd ${{ github.action_path }}/../..; npm ci 21 | shell: bash 22 | - name: Check New Repos 23 | id: check-new-repos 24 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 25 | env: 26 | DEBUG: ${{ (inputs.debug == 'true' || runner.debug) && 'true' || 'false'}} 27 | with: 28 | github-token: ${{ inputs.github_token }} 29 | script: | 30 | const actionPath = '${{ github.action_path }}/../../' 31 | const inputs = ${{ toJson(inputs) }} 32 | 33 | const script = require('${{ github.action_path }}/action.cjs') 34 | await script({github, context, inputs, actionPath, core, 35 | debug: process.env.DEBUG === 'true'}) 36 | -------------------------------------------------------------------------------- /actions/dependabot-auto-dismiss/action.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async ({ github, context, inputs, actionPath, core, debug = false }) => { 2 | const { default: sendSlackMessage } = await import(`${actionPath}/src/sendSlackMessage.js`) 3 | const { default: dependabotDismiss } = await import(`${actionPath}/src/dependabotDismiss.js`) 4 | const message = await dependabotDismiss({ debug, org: context.repo.owner, github, dependabotDismissConfig: `${actionPath}/actions/dependabot-auto-dismiss/dismiss.txt` }) 5 | if (message.length > 0) { await sendSlackMessage({ debug, username: 'dependabot-auto-dismiss', message, channel: '#secops-hotspots', token: inputs.slack_token }) } 6 | } 7 | -------------------------------------------------------------------------------- /actions/dependabot-auto-dismiss/action.yml: -------------------------------------------------------------------------------- 1 | name: weekly-dependabot-auto-dismiss 2 | description: Weekly Dependabot Auto Dismiss 3 | inputs: 4 | github_token: 5 | description: 'GitHub Token' 6 | required: true 7 | slack_token: 8 | description: 'Slack Token' 9 | required: true 10 | debug: 11 | description: 'Debug mode' 12 | required: false 13 | runs: 14 | using: 'composite' 15 | steps: 16 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 17 | with: 18 | node-version: '22.x' 19 | - id: npm 20 | run: cd ${{ github.action_path }}/../..; npm ci 21 | shell: bash 22 | - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 23 | env: 24 | DEBUG: ${{ (inputs.debug == 'true' || runner.debug) && 'true' || 'false'}} 25 | with: 26 | github-token: ${{ inputs.github_token }} 27 | script: | 28 | const actionPath = '${{ github.action_path }}/../../' 29 | const inputs = ${{ toJson(inputs) }} 30 | 31 | const script = require('${{ github.action_path }}/action.cjs') 32 | await script({github, context, inputs, actionPath, core, 33 | debug: process.env.DEBUG === 'true'}) -------------------------------------------------------------------------------- /actions/dependabot-auto-dismiss/dismiss.txt: -------------------------------------------------------------------------------- 1 | CVE-2023-44270 2 | CVE-2023-39325 3 | CVE-2022-41723 4 | CVE-2023-44487 5 | GHSA-255r-3prx-mf99 6 | CVE-2024-23331 7 | GHSA-2qv5-7mw5-j3cg 8 | CVE-2024-4068 9 | CVE-2024-45296 10 | CVE-2024-45338 11 | GHSA-wwq9-3cpr-mm53 12 | CVE-2023-45288 13 | CVE-2024-24786 14 | CVE-2025-27789 15 | GHSA-4p46-pwfr-66x6 16 | CVE-2025-4432 17 | CVE-2025-3730 18 | -------------------------------------------------------------------------------- /actions/dependabot-nudge/action.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async ({ github, context, inputs, actionPath, core, debug = false }) => { 2 | const { default: sendSlackMessage } = await import(`${actionPath}/src/sendSlackMessage.js`) 3 | const { default: dependabotNudge } = await import(`${actionPath}/src/dependabotNudge.js`) 4 | 5 | let githubToSlack = {} 6 | try { 7 | githubToSlack = JSON.parse(inputs.gh_to_slack_user_map) 8 | } catch (e) { 9 | if (debug) console.log('GH_TO_SLACK_USER_MAP is not valid JSON') 10 | } 11 | 12 | // set minlevel to 'medium' if it's the first Monday of the month, otherwise stick to high or critical issues 13 | let minlevel = 'medium' 14 | const today = new Date() 15 | if (today.getDate() > 7) { 16 | if (debug) { console.log('Not the first Monday of the month!') } 17 | minlevel = 'high' 18 | } 19 | 20 | const messages = await dependabotNudge({ debug, org: context.repo.owner, github, minlevel, githubToSlack, actionPath }) 21 | 22 | for (const message of messages) { 23 | try { 24 | await sendSlackMessage({ debug, username: 'dependabot', message, channel: '#secops-hotspots', token: inputs.slack_token }) 25 | } catch (error) { 26 | if (debug) { console.log(error) } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /actions/dependabot-nudge/action.yml: -------------------------------------------------------------------------------- 1 | name: weekly-dependabot-nudge 2 | description: Weekly Dependabot Nudge 3 | inputs: 4 | github_token: 5 | description: 'GitHub Token' 6 | required: true 7 | slack_token: 8 | description: 'Slack Token' 9 | required: true 10 | gh_to_slack_user_map: 11 | description: 'JSON map of github usernames to slack usernames' 12 | required: false 13 | debug: 14 | description: 'Debug mode' 15 | required: false 16 | runs: 17 | using: 'composite' 18 | steps: 19 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 20 | with: 21 | node-version: '22.x' 22 | - id: npm 23 | run: cd ${{ github.action_path }}/../..; npm ci 24 | shell: bash 25 | - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 26 | env: 27 | DEBUG: ${{ (inputs.debug == 'true' || runner.debug) && 'true' || 'false'}} 28 | with: 29 | github-token: ${{ inputs.github_token }} 30 | script: |- 31 | const actionPath = '${{ github.action_path }}/../../' 32 | const inputs = ${{ toJson(inputs) }} 33 | 34 | const script = require('${{ github.action_path }}/action.cjs') 35 | await script({github, context, inputs, actionPath, core, 36 | debug: process.env.DEBUG === 'true'}) 37 | -------------------------------------------------------------------------------- /actions/older-than-2y/action.cjs: -------------------------------------------------------------------------------- 1 | function formatInMessage (r) { 2 | const pushedAt = new Date(r.pushed_at) 3 | return `- ${r.private ? '😎 ' : ''} ${r.full_name} ${r.html_url}\t🌟 ${r.stargazers_count}🍴${r.forks} - Last pushed ${pushedAt.getFullYear()}/${pushedAt.getMonth()}/${pushedAt.getDay() + 1}\n` 4 | } 5 | 6 | module.exports = async ({ github, context, inputs, actionPath, core, debug = false }) => { 7 | const { default: sendSlackMessage } = await import(`${actionPath}/src/sendSlackMessage.js`) 8 | 9 | const org = context.repo.owner 10 | 11 | const v = await github.paginate('GET /orgs/{org}/repos', { 12 | org, 13 | headers: { 14 | 'X-GitHub-Api-Version': '2022-11-28' 15 | } 16 | }) 17 | const maxOlderDate = ((d) => d.setDate(d.getDate() - 2 * 365))(new Date()) // 2 years 18 | const reposOlderThanDate = v.filter(r => r.archived === false).filter(r => r.disabled === false).filter(r => new Date(r.pushed_at) < maxOlderDate) 19 | const forks = reposOlderThanDate.filter(r => r.fork === true) 20 | const nonForks = reposOlderThanDate.filter(r => r.fork === false) 21 | // console.log(reposOlderThanDate[0]) // DEBUG 22 | 23 | if (reposOlderThanDate.length === 0) return '' 24 | 25 | let message = `${org} has ${reposOlderThanDate.length} outdated repositories.\nConsider archiving them.` 26 | 27 | if (nonForks.length !== 0) message += '\n\nRepositories:\n' 28 | for (let i = 0; i < nonForks.length; i++) { 29 | const r = nonForks[i] 30 | message += formatInMessage(r) 31 | } 32 | 33 | if (forks.length !== 0) message += '\n\nForks:\n' 34 | for (let i = 0; i < forks.length; i++) { 35 | const r = forks[i] 36 | message += formatInMessage(r) 37 | } 38 | 39 | core.setSecret(message) 40 | 41 | if (message.length > 0) { await sendSlackMessage({ debug, username: 'older-than-2y', message, color: 'blue', channel: '#security-hotspots', token: inputs.slack_token }) } 42 | } 43 | -------------------------------------------------------------------------------- /actions/older-than-2y/action.yml: -------------------------------------------------------------------------------- 1 | name: older-than-2y 2 | description: Older Than 2 Years Informer 3 | inputs: 4 | github_token: 5 | description: 'GitHub Token' 6 | required: true 7 | slack_token: 8 | description: 'Slack Token' 9 | required: true 10 | debug: 11 | description: 'Debug mode' 12 | required: false 13 | runs: 14 | using: 'composite' 15 | steps: 16 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 17 | with: 18 | node-version: '22.x' 19 | - id: npm 20 | run: cd ${{ github.action_path }}/../..; npm ci 21 | shell: bash 22 | - name: Older Than 2 Years Informer 23 | id: older-than-2y 24 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 25 | env: 26 | DEBUG: ${{ (inputs.debug == 'true' || runner.debug) && 'true' || 'false'}} 27 | with: 28 | github-token: ${{ inputs.github_token }} 29 | script: | 30 | const actionPath = '${{ github.action_path }}/../../' 31 | const inputs = ${{ toJson(inputs) }} 32 | 33 | const script = require('${{ github.action_path }}/action.cjs') 34 | await script({github, context, inputs, actionPath, core, 35 | debug: process.env.DEBUG === 'true'}) 36 | -------------------------------------------------------------------------------- /actions/renovate-sanity-check/action.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async ({ github, context, inputs, actionPath, core, debug = false }) => { 2 | console.log(`${actionPath}/src/renovateSanityCheck.js`) 3 | const { default: renovateSanityCheck } = await import(`${actionPath}/src/renovateSanityCheck.js`) 4 | const { default: sendSlackMessage } = await import(`${actionPath}/src/sendSlackMessage.js`) 5 | 6 | const message = await renovateSanityCheck({ 7 | org: context.repo.owner, 8 | github, 9 | debug 10 | }) 11 | 12 | if (message.length > 0) { await sendSlackMessage({ debug, username: 'renovate-sanity-check', message, color: 'yellow', channel: '#secops-hotspots', token: inputs.slack_token }) } 13 | } 14 | -------------------------------------------------------------------------------- /actions/renovate-sanity-check/action.yml: -------------------------------------------------------------------------------- 1 | # action that runs monthly and check if all repositories in the organization are following the renovate central configuration 2 | # to all repositories in this organization 3 | name: renovate-sanity-check 4 | description: Renovate Sanity Check 5 | inputs: 6 | github_token: 7 | description: 'GitHub token' 8 | required: true 9 | slack_token: 10 | description: 'Slack token' 11 | required: true 12 | debug: 13 | description: 'Debug mode' 14 | default: "false" 15 | runs: 16 | using: 'composite' 17 | steps: 18 | - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 19 | with: 20 | node-version: '22.x' 21 | - id: npm 22 | run: cd ${{ github.action_path }}/../..; npm ci 23 | shell: bash 24 | - name: run 25 | id: run 26 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 27 | env: 28 | DEBUG: ${{ (inputs.debug == 'true' || runner.debug) && 'true' || 'false'}} 29 | with: 30 | github-token: ${{ inputs.github_token }} 31 | script: | 32 | const actionPath = '${{ github.action_path }}/../../' 33 | const inputs = ${{ toJson(inputs) }} 34 | 35 | const script = require('${{ github.action_path }}/action.cjs') 36 | await script({github, context, inputs, actionPath, core, 37 | debug: process.env.DEBUG === 'true'}) 38 | -------------------------------------------------------------------------------- /assets/cleaner.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'optparse' 3 | 4 | class Matcher 5 | def initialize() 6 | @files = [] 7 | @blocklist = [] 8 | end 9 | 10 | def push(blocklist) 11 | @files.push blocklist 12 | end 13 | 14 | def init() 15 | @files.each do |blf| 16 | next unless File.exist?(blf) 17 | 18 | blocklist = File.read(blf).split("\n").map(&:strip).reject(&:empty?) 19 | # remove empty lines and comments 20 | blocklist.reject! { |r| r.empty? || r.start_with?('#') } 21 | 22 | # remove all matching lines and report 23 | blocklist.reject! do |r| 24 | ret = r =~ /^[*@]+$/ 25 | STDERR.puts "Warning: #{blf} contains a line with only asterisks/at, which will match everything" if ret 26 | ret 27 | end 28 | 29 | @blocklist += blocklist 30 | end 31 | end 32 | 33 | def match?(line) 34 | @blocklist.each do |r| 35 | return true if File.fnmatch?("*#{r}*", line) 36 | end 37 | false 38 | end 39 | end 40 | 41 | options = { 42 | matcher: Matcher.new() 43 | } 44 | OptionParser.new do |opts| 45 | opts.banner = "Usage: reviewdog-adapter.rb [options]" 46 | 47 | opts.on("--svgo", "Add SVGO String") do |v| 48 | options[:svgo] = true 49 | options[:matcher].push "#{ENV["SCRIPTPATH"]}/dtd/blocklist.txt" 50 | end 51 | 52 | opts.on("--assignees", "Add Assignees String") do |v| 53 | options[:assignees] = true 54 | end 55 | 56 | opts.on("--sveltegrep", "Remove Extracted Script Extension, and use semgrep blocklist") do |v| 57 | options[:sveltegrep] = true 58 | options[:matcher].push "#{ENV["SCRIPTPATH"]}/semgrep_rules/blocklist.txt" 59 | end 60 | 61 | opts.on("--semgrep", "Use semgrep blocklist") do |v| 62 | options[:semgrep] = true 63 | options[:matcher].push "#{ENV["SCRIPTPATH"]}/semgrep_rules/blocklist.txt" 64 | end 65 | end.parse! 66 | 67 | if ENV['REMOTE_RUNTIME'] 68 | options[:matcher].push "#{ENV["SCRIPTPATH"]}/semgrep_rules/blocklist-#{ENV['REMOTE_RUNTIME']}.txt" 69 | end 70 | 71 | options[:matcher].init() 72 | 73 | STDIN.each_line(chomp: true).sort.uniq.to_a.each do |l| 74 | l.gsub!(/#{ENV['PWD']}/, '') 75 | 76 | l.gsub!(/\.extractedscript\.[hjlmst]*:/, ':') if options[:sveltegrep] 77 | 78 | l.gsub!('Source: null', 'Source: https://github.com/brave/security-action') 79 | 80 | if options[:svgo] 81 | l.gsub!(/$/, '

Run SVGO on your assets

') 82 | end 83 | 84 | if options[:assignees] 85 | if l =~ /,null$/ 86 | l.gsub!(/,null$/, "
Cc #{ENV['ASSIGNEES']}") 87 | else 88 | l.gsub!(/,([^,]+)$/) { |_| "
Cc #{$1.split.map { |s| "@"+s }.join(' ')}" } 89 | end 90 | else 91 | l.gsub!(/$/, "
Cc #{ENV['ASSIGNEES']}") 92 | end 93 | 94 | puts l unless options[:matcher].match?(l) 95 | end -------------------------------------------------------------------------------- /assets/debug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | pwd 3 | tree -a 4 | echo $RUNNER_TEMP 5 | tree -a $RUNNER_TEMP 6 | realpath "$0" 7 | realpath -s "$0" 8 | echo $ASSIGNEES -------------------------------------------------------------------------------- /assets/dtd/blocklist.txt: -------------------------------------------------------------------------------- 1 | element *: validity error : ID * already defined 2 | element *: validity error : No declaration for attribute data-* of element * 3 | element style: validity error : Element style does not carry attribute type 4 | element svg: validity error : Value for attribute version of svg must be * 5 | third_party/rust/**/*.svg 6 | element *: validity error : No declaration for attribute gid of element * 7 | element *: validity error : No declaration for attribute cache-id of element * 8 | element *: validity error : No declaration for attribute mask-type of element * 9 | -------------------------------------------------------------------------------- /assets/npm-audit.py: -------------------------------------------------------------------------------- 1 | import json 2 | import shutil 3 | import subprocess 4 | import sys 5 | import tempfile 6 | 7 | from os import environ, path 8 | 9 | 10 | def main(): 11 | with open(path.join(environ["SCRIPTPATH"], "all_changed_files.txt")) as all_changed_files: 12 | files = all_changed_files.read() 13 | changed_lock_files = [ 14 | f for f in files.split("\x00") 15 | if f.endswith("package-lock.json") 16 | ] 17 | # Create temporary directory just for the package-lock.json file 18 | # without a parent directory containing package.json files 19 | with tempfile.TemporaryDirectory(dir="../") as temp_dir: 20 | temp_file_path = path.join(temp_dir, 'package-lock.json') 21 | for lock_path in changed_lock_files: 22 | with open(lock_path) as lock_file: 23 | lock_file_lines = lock_file.readlines() 24 | shutil.copy(lock_path, temp_file_path) 25 | process_output = subprocess.run( 26 | [shutil.which("npm"), "audit", "--package-lock-only", "--json"], 27 | cwd=temp_dir, 28 | capture_output=True, 29 | ) 30 | output = json.loads(process_output.stdout) 31 | for vulnerability in output["vulnerabilities"].values(): 32 | if not isinstance(vulnerability["via"][0], str): 33 | severity = vulnerability["severity"][0].upper() 34 | via = vulnerability["via"][0] 35 | source = via.get("url", "") 36 | node_name = vulnerability["nodes"][0] 37 | search_for = f'"{node_name}": {{' 38 | try: 39 | line = next( 40 | lineno for lineno, line in enumerate(lock_file_lines) 41 | if line.strip() == search_for 42 | ) + 2 43 | except StopIteration: 44 | if node_name.startswith("node_modules/"): 45 | search_for = f'"{node_name[len("node_modules/"):]}": {{' 46 | line = next( 47 | lineno for lineno, line in enumerate(lock_file_lines) 48 | if line.strip() == search_for 49 | ) + 2 50 | else: 51 | print("Cannot find", search_for, vulnerability, file=sys.stderr) 52 | raise 53 | if source: 54 | source = f"

See {source}" 55 | print(f"{severity}:{lock_path}:{line} {via.get('title')}{source}") 56 | 57 | 58 | if __name__ == "__main__": 59 | main() 60 | -------------------------------------------------------------------------------- /assets/reviewdog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | # Absolute path to this script. /home/user/bin/foo.sh 3 | SCRIPT=$(readlink -f $0) 4 | # Absolute path this script is in. /home/user/bin 5 | export SCRIPTPATH=`dirname $SCRIPT` 6 | export GOPATH=$HOME/go 7 | export PATH=$PATH:$GOROOT/bin:$GOPATH/bin 8 | export SEC_ACTION_DEBUG=$SEC_ACTION_DEBUG 9 | export ASSIGNEES=$(echo "$ASSIGNEES" | sed 's|\([^ ]\)|@\1|' | tr -s '\n' ' ') 10 | 11 | RUNNERS="safesvg tfsec semgrep sveltegrep npm-audit pip-audit" # disabled: brakeman 12 | 13 | if [ -n "${GITHUB_BASE_REF+set}" ]; then 14 | for runner in $RUNNERS; do 15 | reviewdog -reporter=local -runners=$runner -conf="$SCRIPTPATH/reviewdog/reviewdog.yml" -diff="git diff origin/$GITHUB_BASE_REF" > $runner.log 2>> reviewdog.log || true 16 | grep -H "" reviewdog.$runner.stderr.log >> reviewdog.fail.log || true 17 | [[ ${SEC_ACTION_DEBUG:-false} == 'true' ]] && grep -H "" reviewdog.$runner.stderr.log || true 18 | done 19 | 20 | for runner in $RUNNERS; do 21 | cat $runner.log | reviewdog -reporter=github-pr-review -efm='%f:%l: %m' \ 22 | || cat $runner.log >> reviewdog.fail.log 23 | grep -H "" $runner.log >> reviewdog.log || true 24 | echo -n "$runner: " 25 | echo "${runner//-/_}_count=$(grep -c "^" $runner.log)" >> $GITHUB_OUTPUT || true 26 | [[ ${SEC_ACTION_DEBUG:-false} == 'true' ]] && grep -H "" $runner.log || true 27 | done 28 | 29 | else 30 | git ls-files | tr '\n' '\0' > $SCRIPTPATH/all_changed_files.txt 31 | reviewdog \ 32 | -runners=$(echo "$RUNNERS" | tr ' ' ',') \ 33 | -conf="$SCRIPTPATH/reviewdog/reviewdog.yml" \ 34 | -filter-mode=nofilter \ 35 | -reporter=local \ 36 | -tee \ 37 | | sed 's/

Cc @brave\/sec-team[ ]*//' \ 38 | | tee reviewdog.log 39 | # TODO: in the future send reviewdog.log to a database and just print out errors with 40 | # [[ ${SEC_ACTION_DEBUG:-false} == 'true' ]] && somethingsomething 41 | # TODO: fix brakeman on full-scan 42 | grep -H "" reviewdog.*.stderr.log | grep -v "reviewdog.brakeman.stderr.log:" >> reviewdog.fail.log || true 43 | for runner in $RUNNERS; do 44 | echo "${runner//-/_}_count=$(grep -c ": \[${runner}\] .*$" reviewdog.log)" >> $GITHUB_OUTPUT || true 45 | done 46 | fi 47 | 48 | cat reviewdog.log | grep 'failed with zero findings: The command itself failed' >> reviewdog.fail.log || true 49 | 50 | echo "findings=$(cat reviewdog.log | grep '^[A-Z]:[^:]*:' | wc -l)" >> $GITHUB_OUTPUT 51 | 52 | sed -i '/^$/d' reviewdog.log reviewdog.fail.log 53 | find reviewdog.log -type f -empty -delete 54 | find reviewdog.fail.log -type f -empty -delete 55 | -------------------------------------------------------------------------------- /assets/semgrep_rules/blocklist-brave-core.txt: -------------------------------------------------------------------------------- 1 | https://semgrep.dev/r/gitlab.bandit.B404 2 | -------------------------------------------------------------------------------- /assets/semgrep_rules/blocklist-bsg.txt: -------------------------------------------------------------------------------- 1 | https://semgrep.dev/r/yaml.kubernetes.security.run-as-non-root.run-as-non-root 2 | https://semgrep.dev/r/yaml.kubernetes.security.writable-filesystem-container.writable-filesystem-container 3 | https://semgrep.dev/r/yaml.kubernetes.security.allow-privilege-escalation-no-securitycontext.allow-privilege-escalation-no-securitycontext 4 | -------------------------------------------------------------------------------- /assets/semgrep_rules/blocklist-internal.txt: -------------------------------------------------------------------------------- 1 | https://semgrep.dev/r/typescript.react.portability.i18next.jsx-label-not-i18n.jsx-label-not-i18n 2 | -------------------------------------------------------------------------------- /assets/semgrep_rules/blocklist-static.txt: -------------------------------------------------------------------------------- 1 | https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/var-in-href.yaml 2 | https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/var-in-script-tag.yaml 3 | -------------------------------------------------------------------------------- /assets/semgrep_rules/blocklist.txt: -------------------------------------------------------------------------------- 1 | https://github.com/0xdea/semgrep-rules/blob/main/c/interesting-api-calls.yaml 2 | https://github.com/0xdea/semgrep-rules/blob/main/c/mismatched-memory-management-cpp.yaml 3 | https://github.com/0xdea/semgrep-rules/blob/main/c/missing-break-in-switch.yaml 4 | https://github.com/0xdea/semgrep-rules/blob/main/c/missing-default-in-switch.yaml 5 | https://github.com/0xdea/semgrep-rules/blob/main/c/signed-unsigned-conversion.yaml 6 | https://github.com/0xdea/semgrep-rules/blob/main/c/typos.yaml 7 | https://semgrep.dev/r/generic.html-templates.security.unquoted-attribute-var.unquoted-attribute-var 8 | https://semgrep.dev/r/generic.html-templates.security.var-in-href.var-in-href 9 | https://semgrep.dev/r/generic.html-templates.security.var-in-script-src.var-in-script-src 10 | https://semgrep.dev/r/generic.html-templates.security.var-in-script-tag.var-in-script-tag 11 | https://semgrep.dev/r/generic.nginx.security.missing-internal.missing-internal 12 | https://semgrep.dev/r/generic.secrets.gitleaks.aws-access-token.aws-access-token 13 | https://semgrep.dev/r/generic.secrets.gitleaks.generic-api-key.generic-api-key 14 | https://semgrep.dev/r/generic.secrets.security.detected-aws-access-key-id-value.detected-aws-access-key-id-value 15 | https://semgrep.dev/r/html.security.audit.missing-integrity.missing-integrity 16 | https://semgrep.dev/r/html.security.audit.missing-integrity.missing-integrity 17 | https://semgrep.dev/r/javascript.express.security.audit.xss.mustache.var-in-href.var-in-href 18 | https://semgrep.dev/r/javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring 19 | https://semgrep.dev/r/python.django.security.audit.xss.template-href-var.template-href-var 20 | https://semgrep.dev/r/python.django.security.audit.xss.var-in-script-tag.var-in-script-tag 21 | https://semgrep.dev/r/python.flask.security.xss.audit.template-href-var.template-href-var 22 | https://semgrep.dev/r/python.flask.security.xss.audit.template-unescaped-with-safe.template-unescaped-with-safe 23 | https://semgrep.dev/r/terraform.aws.security.aws-elb-access-logs-not-enabled.aws-elb-access-logs-not-enabled 24 | https://semgrep.dev/r/trailofbits.yaml.docker-compose.port-all-interfaces.port-all-interfaces 25 | https://semgrep.dev/r/typescript.react.security.audit.react-href-var.react-href-var 26 | https://semgrep.dev/r/typescript.react.security.audit.react-missing-noreferrer.react-missing-noreferrer 27 | https://semgrep.dev/r/yaml.docker-compose.security.no-new-privileges.no-new-privileges 28 | https://semgrep.dev/r/yaml.github-actions.security.third-party-action-not-pinned-to-commit-sha.third-party-action-not-pinned-to-commit-sha 29 | https://semgrep.dev/r/go.lang.security.audit.net.use-tls.use-tls 30 | https://semgrep.dev/r/terraform.aws.security.aws-lambda-x-ray-tracing-not-active.aws-lambda-x-ray-tracing-not-active 31 | https://semgrep.dev/r/gitlab.find_sec_bugs.HARD_CODE_KEY-1 32 | https://semgrep.dev/r/gitlab.find_sec_bugs.HARD_CODE_KEY-2 33 | https://semgrep.dev/r/gitlab.find_sec_bugs.HARD_CODE_KEY-3 34 | https://semgrep.dev/r/gitlab.find_sec_bugs.HARD_CODE_KEY-4 35 | https://semgrep.dev/r/gitlab.bandit.B101 36 | https://semgrep.dev/r/python.django.security.django-no-csrf-token.django-no-csrf-token 37 | https://semgrep.dev/r/typescript.react.portability.i18next.jsx-not-internationalized.jsx-not-internationalized 38 | https://semgrep.dev/r/generic.secrets.gitleaks.hashicorp-tf-password.hashicorp-tf-password 39 | https://semgrep.dev/r/typescript.react.portability.i18next.i18next-key-format.i18next-key-format 40 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/android-profile-original.java: -------------------------------------------------------------------------------- 1 | // ruleid: android-profile-original 2 | mProfile.getOriginalProfile(); 3 | 4 | // ruleid: android-profile-original 5 | getProfileProviderSupplier().get().getOriginalProfile(); 6 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/android-profile-original.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: android-profile-original 3 | patterns: 4 | - pattern-either: 5 | - pattern: '....getOriginalProfile()' 6 | metadata: 7 | author: Brian Johnson 8 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/android-profile-original.yaml 9 | assignees: bridiver 10 | category: correctness 11 | message: Getting the original profile explicitly can lead to security and privacy issues 12 | languages: [java, kotlin] 13 | severity: WARNING 14 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/android-profile-static.java: -------------------------------------------------------------------------------- 1 | // ruleid: android-profile-static 2 | ProfileManager.getLastUsedRegularProfile(); 3 | 4 | // ruleid: android-profile-static 5 | Utils.getProfile(); 6 | 7 | // ruleid: android-profile-static 8 | BraveLeoPrefUtils.getProfile(); 9 | 10 | // ok: android-profile-static 11 | Utils.someOtherMethod(); 12 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/android-profile-static.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: android-profile-static 3 | patterns: 4 | - pattern-either: 5 | - pattern: 'ProfileManager.getLastUsedRegularProfile()' 6 | - pattern: 'Utils.getProfile()' 7 | - pattern: 'BraveLeoPrefUtils.getProfile()' 8 | metadata: 9 | author: Brian Johnson 10 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/android-profile-static.yaml 11 | assignees: bridiver 12 | category: correctness 13 | message: Static methods to access the profile will become a problem when android allows multiple profiles. Also these methods can be dangerous when we need to distinguish between the regular and incognito profile (services factories, etc...) 14 | languages: [java, kotlin] 15 | severity: ERROR 16 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/android-resolve-intent.java: -------------------------------------------------------------------------------- 1 | import android.content.ComponentName; 2 | import android.content.Context; 3 | import android.content.Intent; 4 | import android.content.pm.PackageItemInfo; 5 | import android.content.pm.ResolveInfo; 6 | import android.content.pm.ActivityInfo 7 | import android.os.Bundle; 8 | 9 | public class MainActivity extends AppCompatActivity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(R.layout.activity_main); 15 | 16 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com")); 17 | PackageManager pm = getPackageManager(); 18 | // ruleid: android-resolve-intent 19 | ResolveInfo resolveInfo = pm.resolveService(intent, 0); 20 | // ruleid: android-resolve-intent 21 | resolveInfo = pm.resolveContentProvider(intent, 0); 22 | // ruleid: android-resolve-intent 23 | resolveInfo = pm.resolveActivity(intent, 0); 24 | // ruleid: android-resolve-intent 25 | ComponentName componentName = intent.resolveActivity(pm); 26 | // ruleid: android-resolve-intent 27 | ActivityInfo activityInfo = intent.resolveActivityInfo(pm); 28 | // ruleid: android-resolve-intent 29 | List resolveInfoList = pm.queryBroadcastReceivers(intent,0); 30 | // ruleid: android-resolve-intent 31 | resolveInfoList = pm.queryIntentActivities(intent,0); 32 | // ruleid: android-resolve-intent 33 | resolveInfoList = pm.queryIntentActivityOptions(null,null,intent,0); 34 | // ruleid: android-resolve-intent 35 | resolveInfoList = pm.queryIntentServices(intent,0); 36 | // ruleid: android-resolve-intent 37 | List providerInfoList = pm.queryIntentContentProviders(intent,0); 38 | } 39 | } -------------------------------------------------------------------------------- /assets/semgrep_rules/client/android-resolve-intent.kt: -------------------------------------------------------------------------------- 1 | import android.content.ComponentName 2 | import android.content.Context 3 | import android.content.Intent 4 | import android.content.pm.PackageItemInfo 5 | import android.content.pm.ResolveInfo 6 | import android.content.pm.ActivityInfo 7 | import android.os.Bundle 8 | 9 | class MainActivity : AppCompatActivity() { 10 | 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | setContentView(R.layout.activity_main) 14 | 15 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com")) 16 | val pm = packageManager 17 | // ruleid: android-resolve-intent 18 | val resolveInfo = pm.resolveService(intent, 0) 19 | // ruleid: android-resolve-intent 20 | resolveInfo = pm.resolveContentProvider(intent, 0) 21 | // ruleid: android-resolve-intent 22 | resolveInfo = pm.resolveActivity(intent, 0) 23 | // ruleid: android-resolve-intent 24 | val componentName = intent.resolveActivity(pm) 25 | // ruleid: android-resolve-intent 26 | val activityInfo = intent.resolveActivityInfo(pm) 27 | // ruleid: android-resolve-intent 28 | val resolveInfoList = pm.queryBroadcastReceivers(intent, 0) 29 | // ruleid: android-resolve-intent 30 | resolveInfoList = pm.queryIntentActivities(intent, 0) 31 | // ruleid: android-resolve-intent 32 | resolveInfoList = pm.queryIntentActivityOptions(null, null, intent, 0) 33 | // ruleid: android-resolve-intent 34 | resolveInfoList = pm.queryIntentServices(intent, 0) 35 | // ruleid: android-resolve-intent 36 | val providerInfoList = pm.queryIntentContentProviders(intent, 0) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/android-resolve-intent.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: android-resolve-intent 3 | patterns: 4 | - pattern-either: 5 | - pattern: '....resolveService(...,...)' 6 | - pattern: '....resolveContentProvider(...,...)' 7 | - pattern: '....resolveActivity(...,...)' 8 | - pattern: '....resolveActivity(...)' 9 | - pattern: '....resolveActivityInfo(...,...)' 10 | - pattern: '....queryBroadcastReceivers(...,...)' 11 | - pattern: '....queryIntentActivities(...,...)' 12 | - pattern: '....queryIntentActivityOptions(...,...,...,...)' 13 | - pattern: '....queryIntentServices(...,...)' 14 | - pattern: '....queryIntentContentProviders(...,...)' 15 | metadata: 16 | author: Artem Chaikin 17 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/android-resolve-intent.yaml 18 | assignees: stoletheminerals 19 | category: security 20 | message: Implicit intents in resolveComponent and queryComponent methods for component launch may pose security risks, as other installed apps can register similar components with higher priority. Instead, it is recommended to use hardcoded package names for third-party components launch or getApplicationContext().getPackageName() for local component launch. 21 | languages: [java, kotlin] 22 | severity: WARNING 23 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/brave-execute-script-ios.swift: -------------------------------------------------------------------------------- 1 | import WebKit 2 | 3 | let webView = WKWebView(frame: .zero) 4 | // ruleid: brave-execute-script-ios 5 | webView.evaluateJavaScript("document.title") { (result, error) in 6 | if let result = result { 7 | print("Title: \(result)") 8 | } else if let error = error { 9 | print("Evaluate error: \(error.localizedDescription)") 10 | } 11 | } 12 | // ruleid: brave-execute-script-ios 13 | try await webView.evaluateSafeJavaScriptThrowing( 14 | functionName: "localStorage.setItem", 15 | args: ["storageKey", "receipt"], 16 | contentWorld: .defaultClient 17 | ) 18 | // ruleid: brave-execute-script-ios 19 | webView.evaluateSafeJavaScript( 20 | functionName: "alert(123)", 21 | contentWorld: .page 22 | ) 23 | // ruleid: brave-execute-script-ios 24 | let userScript = WKUserScript(source: "document.title = 'Test';", injectionTime: .atDocumentEnd, forMainFrameOnly: true) 25 | webView.configuration.userContentController.addUserScript(userScript) -------------------------------------------------------------------------------- /assets/semgrep_rules/client/brave-execute-script-ios.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: brave-execute-script-ios 3 | metadata: 4 | author: Artem Chaikin 5 | references: 6 | - https://github.com/brave/brave-browser/wiki/Security-reviews 7 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/brave-execute-script-ios.yaml 8 | assignees: | 9 | stoletheminerals 10 | bridiver 11 | category: security 12 | message: | 13 | $FUNC usages should be vet by the security-team. 14 | 15 | References: 16 | - https://github.com/brave/brave-browser/wiki/Security-reviews (point 13) 17 | severity: INFO 18 | languages: 19 | - swift 20 | patterns: 21 | - pattern-either: 22 | - pattern: $OBJ.$FUNC(...) 23 | - pattern: $FUNC(...) 24 | - metavariable-regex: 25 | metavariable: $FUNC 26 | regex: ^(WKUserScript|evaluateSafeJavaScriptThrowing|evaluateSafeJavaScript|evaluateJavaScript)$ 27 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/brave-execute-script.cpp: -------------------------------------------------------------------------------- 1 | int main() { 2 | // ruleid: brave-execute-script 3 | web_frame->ExecuteScriptInIsolatedWorld( 4 | isolated_world_id_, 5 | blink::WebScriptSource( 6 | blink::WebString::FromUTF16(foobar)), 7 | blink::BackForwardCacheAware::kAllow); 8 | // ruleid: brave-execute-script 9 | web_contents()->GetPrimaryMainFrame()->ExecuteJavaScript(k_script, base::NullCallback()); 10 | } 11 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/brave-execute-script.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: brave-execute-script 3 | metadata: 4 | author: Andrea Brancaleoni 5 | references: 6 | - https://github.com/brave/brave-browser/wiki/Security-reviews 7 | confidence: MEDIUM 8 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/brave-execute-script.yaml 9 | assignees: | 10 | stoletheminerals 11 | diracdeltas 12 | bridiver 13 | category: security 14 | message: | 15 | $FUNC usages should be vet by the security-team. 16 | 17 | References: 18 | - https://github.com/brave/brave-browser/wiki/Security-reviews (point 12) 19 | severity: INFO 20 | languages: 21 | - cpp 22 | patterns: 23 | - pattern: $OBJ.$FUNC(...) 24 | - metavariable-regex: 25 | metavariable: $FUNC 26 | regex: ^(.*ExecuteScript.*|ExecuteMethodAndReturnValue|CallFunctionEvenIfScriptDisabled|ExecuteJavaScript)$ 27 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/brave-isolated-world.cpp: -------------------------------------------------------------------------------- 1 | constexpr int kBraveAdsIsolatedWorldId = 2 | // ruleid: brave-isolated-world-id-content-end 3 | content::ISOLATED_WORLD_ID_CONTENT_END + 4; 4 | 5 | int main() { 6 | // ruleid: brave-isolated-world-id-content-end 7 | int a = content::ISOLATED_WORLD_ID_CONTENT_END; 8 | // ruleid: brave-isolated-world-id-content-end 9 | int a = content::ISOLATED_WORLD_ID_BRAVE_INTERNAL; 10 | } 11 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/brave-isolated-world.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: brave-isolated-world-id-content-end 3 | metadata: 4 | author: Andrea Brancaleoni 5 | confidence: LOW 6 | assignees: | 7 | thypon 8 | diracdeltas 9 | bridiver 10 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/brave-isolated-world.yaml 11 | category: security 12 | message: Security hotspot found (`ISOLATED_WORLD`). A security-team member should analyze the code security for possible vulnerabilities. 13 | severity: WARNING 14 | languages: 15 | - generic 16 | paths: 17 | include: 18 | - "*.c" 19 | - "*.cpp" 20 | - "*.cc" 21 | - "*.h" 22 | - "*.hh" 23 | - "*.hcc" 24 | pattern-either: 25 | - pattern-regex: ISOLATED_WORLD_ID_CONTENT_END 26 | - pattern-regex: ISOLATED_WORLD_ID_BRAVE_INTERNAL 27 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/brave-missing-break-in-switch.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: brave-missing-break-in-switch 3 | metadata: 4 | author: Andrea Brancaleoni 5 | original_author: Marco Ivaldi 6 | references: 7 | - https://cwe.mitre.org/data/definitions/484 8 | - https://g.co/kgs/PCHQjJ 9 | - https://github.com/struct/mms 10 | - https://github.com/returntocorp/semgrep/issues/4939 11 | confidence: MEDIUM 12 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/brave-missing-break-in-switch.yaml 13 | category: security 14 | # NOTE: we should also check for default blocks that miss the 15 | # break/return/exit or equivalent statement/function call. 16 | message: >- 17 | The software omits a break statement within a switch or similar construct, causing code associated with multiple conditions to execute. This can cause problems when the programmer only intended to execute code associated with one condition. 18 | severity: INFO 19 | languages: 20 | - c 21 | - cpp 22 | patterns: 23 | - pattern: | 24 | switch ($VAR) { case $VAL1: ... case $VAL2: ... } 25 | # break 26 | - pattern-not: | 27 | switch ($VAR) { case $VAL1: ... break; case $VAL2: ... } 28 | - pattern-not: | 29 | switch ($VAR) { case $VAL1: break; case $VAL2: ... } 30 | # exit 31 | - pattern-not: | 32 | switch ($VAR) { case $VAL1: ... exit($RET); case $VAL2: ... } 33 | - pattern-not: | 34 | switch ($VAR) { case $VAL1: exit($RET); case $VAL2: ... } 35 | # return 36 | - pattern-not: | 37 | switch ($VAR) { case $VAL1: ... return; case $VAL2: ... } 38 | - pattern-not: | 39 | switch ($VAR) { case $VAL1: return; case $VAL2: ... } 40 | - pattern-not: | 41 | switch ($VAR) { case $VAL1: ... return $RET; case $VAL2: ... } 42 | - pattern-not: | 43 | switch ($VAR) { case $VAL1: return $RET; case $VAL2: ... } 44 | # ABSL_FALLTHROUGH_INTENDED 45 | - pattern-not: | 46 | switch ($VAR) { case $VAL1: ... ABSL_FALLTHROUGH_INTENDED; case $VAL2: ... } 47 | - pattern-not: | 48 | switch ($VAR) { case $VAL1: ABSL_FALLTHROUGH_INTENDED; case $VAL2: ... } 49 | # NOTREACHED_NORETURN 50 | - pattern-not: | 51 | switch ($VAR) { case $VAL1: NOTREACHED_NORETURN(); case $VAL2: ... } 52 | - pattern-not: | 53 | switch ($VAR) { case $VAL1: ... NOTREACHED_NORETURN(); case $VAL2: ... } 54 | # NOTREACHED_IN_MIGRATION 55 | - pattern-not: | 56 | switch ($VAR) { case $VAL1: NOTREACHED_IN_MIGRATION(); case $VAL2: ... } 57 | - pattern-not: | 58 | switch ($VAR) { case $VAL1: ... NOTREACHED_IN_MIGRATION(); case $VAL2: ... } 59 | # NOTREACHED 60 | - pattern-not: | 61 | switch ($VAR) { case $VAL1: NOTREACHED(); case $VAL2: ... } 62 | - pattern-not: | 63 | switch ($VAR) { case $VAL1: ... NOTREACHED(); case $VAL2: ... } 64 | # [[fallthrough]]; 65 | - pattern-not-regex: '\[\[fallthrough\]\];' 66 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/browser-dependency-inversion.cc: -------------------------------------------------------------------------------- 1 | // ruleid: browser-dependency-inversion 2 | chrome::FindBrowserWithTab(web_contents); 3 | // ruleid: browser-dependency-inversion 4 | FindBrowserWithTab(web_contents); 5 | // ruleid: browser-dependency-inversion 6 | BrowserView::GetBrowserViewForNativeWindow(); 7 | // ruleid: browser-dependency-inversion 8 | void MyClass::MyMethod(Browser* browser, bool test) { } 9 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/browser-dependency-inversion.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: browser-dependency-inversion 3 | metadata: 4 | author: Brian Johnson 5 | references: 6 | - https://chromium.googlesource.com/chromium/src/+/main/docs/chrome_browser_design_principles.md#structure_modularity 7 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/browser-dependency-inversion.yaml 8 | assignees: | 9 | goodov 10 | cdesouza-chromium 11 | bridiver 12 | category: correctness 13 | message: | 14 | There are several global functions that facilitate dependency inversion. It will not be possible to call them from modularized features (no dependency cycles), and their usage in non-modularized features is considered a red flag 15 | 16 | Don't use Browser*. This is functionally a container of hundreds of other pointers. It is impossible to specify dependencies, since Browser* functionally depends on everything. Instead, pass in the relevant pointers, e.g. Profile*, FooFeatureController, etc 17 | 18 | References: 19 | - https://chromium.googlesource.com/chromium/src/+/main/docs/chrome_browser_design_principles.md#structure_modularity 20 | severity: INFO 21 | languages: 22 | - cpp 23 | pattern-either: 24 | - patterns: 25 | - pattern: $FUNC(...) 26 | - metavariable-regex: 27 | metavariable: $FUNC 28 | regex: ^(chrome::)?(FindTabbedBrowser|FindAnyBrowser|FindBrowserWithProfile|FindAllTabbedBrowsersWithProfile|FindAllBrowsersWithProfile|FindBrowserWithID|FindBrowserWithWindow|FindBrowserWithActiveWindow|FindBrowserWithTab|FindBrowserWithGroup|FindBrowserWithUiElementContext|FindLastActiveWithProfile|FindLastActive|BrowserView::GetBrowserViewForNativeWindow|BrowserView::FindBrowserWindowWithWebContents)$ 29 | - patterns: 30 | - pattern: $RETURN $FUNC(..., Browser* $BROWSER, ...) { ... } 31 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/cast-signed-to-unsigned.c: -------------------------------------------------------------------------------- 1 | int main() { 2 | // ok: cast-signed-to-unsigned 3 | int y = 42; 4 | // ruleid: cast-signed-to-unsigned 5 | uint x = (uint)y; 6 | 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/cast-signed-to-unsigned.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: cast-signed-to-unsigned 3 | metadata: 4 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/cast-signed-to-unsigned.yaml 5 | assignees: | 6 | fmarier 7 | bridiver 8 | category: security 9 | patterns: 10 | - pattern: ($CAST)($TYPE $X) 11 | - metavariable-regex: 12 | metavariable: $CAST 13 | regex: ^(u.*)$ 14 | - metavariable-regex: 15 | metavariable: $TYPE 16 | regex: ^([^u].*)$ 17 | message: | 18 | Semgrep found a cast from $TYPE (signed) to $CAST. 19 | 20 | For arithmetic use `base::CheckAdd(value1, value2).AssignIfValid(&result)`. 21 | In case casting the value is required, consider using `base::checked_cast`, 22 | or if your want to fail without a check `IsValueInRangeForNumericType`. 23 | 24 | References: 25 | 26 | - https://chromium.googlesource.com/chromium/src/+/main/docs/security/integer-semantics.md#be-aware-of-the-subtleties-of-integer-types 27 | - https://chromium.googlesource.com/chromium/src/+/main/base/numerics/README.md 28 | - https://google.github.io/styleguide/cppguide.html#Casting 29 | - https://chromium.googlesource.com/chromium/src/+/main/styleguide/c++/c++.md 30 | languages: [cpp, c] 31 | severity: WARNING 32 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/check_includes.gni: -------------------------------------------------------------------------------- 1 | source_set("busted_includes") { 2 | # This target's includes are messed up, exclude it from checking. 3 | # ruleid: brave.gni_includes 4 | check_includes = false 5 | } -------------------------------------------------------------------------------- /assets/semgrep_rules/client/check_includes.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: brave.gni_includes 3 | languages: 4 | - generic 5 | severity: ERROR 6 | metadata: 7 | author: Andrea Brancaleoni 8 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/check_includes.yaml 9 | assignees: | 10 | bridiver 11 | thypon 12 | category: security 13 | paths: 14 | include: 15 | - "*.gn" 16 | - "*.gni" 17 | message: > 18 | `check_includes = false` should not be used since it disables gn 19 | dependency checking. 20 | 21 | `check_includes` behaviour should not be explicitly changed. 22 | pattern: check_includes 23 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/chromium-insecure-gurl.cpp: -------------------------------------------------------------------------------- 1 | int main() { 2 | // ok: chromium-insecure-gurl 3 | GURL url = ...; 4 | // ruleid: chromium-insecure-gurl 5 | GURL origin = url.DeprecatedGetOriginAsURL(); 6 | // BUG: `origin` will be incorrect if `url` is an "about:blank" URL 7 | // BUG: `origin` will be incorrect if `url` came from a sandboxed frame. 8 | // BUG: `origin` will be incorrect when `url` (rather than 9 | // `base_url_for_data_url`) is used when working with loadDataWithBaseUrl 10 | // (see also https://crbug.com/1201514). 11 | // BUG: `origin` will be empty if `url` is a blob: URL like 12 | // "blob:http://origin/guid-goes-here". 13 | // NOTE: `GURL origin` is also an anti-pattern; see the "Use correct type to 14 | // represent origins" section below. 15 | 16 | // Blink-specific example: 17 | // ok: chromium-insecure-gurl 18 | KURL url = ...; 19 | // ruleid: chromium-insecure-gurl 20 | scoped_refptr origin = SecurityOrigin::Create(url); 21 | // BUG: `origin` will be incorrect if `url` is an "about:blank" URL 22 | // BUG: `origin` will be incorrect if `url` came from a sandboxed frame. 23 | } -------------------------------------------------------------------------------- /assets/semgrep_rules/client/chromium-insecure-gurl.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: chromium-insecure-gurl 3 | metadata: 4 | author: Andrea Brancaleoni 5 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/chromium-insecure-gurl.yaml 6 | assignees: | 7 | thypon 8 | fmarier 9 | category: security 10 | pattern-either: 11 | - patterns: 12 | - pattern: $TYPE $VAR = ...; 13 | - metavariable-regex: 14 | metavariable: $VAR 15 | regex: origin 16 | - metavariable-regex: 17 | metavariable: $TYPE 18 | regex: ^(G|K)URL$ 19 | - pattern: ((GURL)$VAR).DeprecatedGetOriginAsURL(); 20 | - pattern: SecurityOrigin::Create((KURL $VAR)); 21 | - pattern: SecurityOrigin::Create((GURL $VAR)); 22 | message: | 23 | Use origin (rather than URL) for security decisions. 24 | 25 | URLs are often not sufficient for security decisions, since the origin may not be present in the URL (e.g., about:blank), may be tricky to parse (e.g., blob: or filesystem: URLs), or may be opaque despite a normal-looking URL (e.g., the security context may be sandboxed). Use origins whenever possible. 26 | 27 | https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/security/origin-vs-url.md 28 | languages: 29 | - cpp 30 | - c 31 | severity: WARNING 32 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/chromium-uaf.cpp: -------------------------------------------------------------------------------- 1 | v8::Local uaf(v8::Isolate* isolate) { 2 | if (!EnsureConnected()) { 3 | return v8::Local(); 4 | } 5 | 6 | v8::MaybeLocal resolver = 7 | v8::Promise::Resolver::New(isolate->GetCurrentContext()); 8 | if (resolver.IsEmpty()) { 9 | return v8::Local(); 10 | } 11 | 12 | auto global_context( 13 | v8::Global(isolate, isolate->GetCurrentContext())); 14 | auto promise_resolver( 15 | v8::Global(isolate, resolver.ToLocalChecked())); 16 | // ok: chromium-bind-uaf 17 | DistillPageText( 18 | render_frame(), 19 | base::BindOnce(&PageContentExtractor::OnDistillResult, 20 | weak_ptr_factory_.GetWeakPtr(), std::move(callback))); 21 | // ok: chromium-bind-uaf 22 | DistillPageText( 23 | render_frame(), 24 | base::BindOnce(&PageContentExtractor::OnDistillResult, 25 | // ruleid: chromium-unretained-uaf 26 | base::Unretained(this), std::move(callback))); 27 | 28 | // this is bad and most likely will explode: 29 | auto func = [](MyClass* c, int val) { 30 | if (val > 10) { 31 | c->DoSomething(); 32 | } else { 33 | c->DoSomethingElse(); 34 | } 35 | }; 36 | // ruleid: chromium-bind-uaf 37 | base::BindOnce(&func, this); 38 | 39 | // ok: chromium-bind-uaf 40 | base::BindOnce(&MyClass::DoSomethingConditionally, weak_ptr_factory_.GetWeakPtr()); 41 | 42 | // ok: chromium-unretained-uaf 43 | web_ui()->RegisterMessageCallback( 44 | "initLeoAssistant", 45 | base::BindRepeating(&BraveLeoAssistantHandler::HandleInitLeoAssistant, 46 | base::Unretained(this))); 47 | 48 | // ok: chromium-unretained-uaf 49 | pref_change_registrar_.Add( 50 | prefs::kEnabled, 51 | base::BindRepeating(&AdsServiceImpl::OnEnabledPrefChanged, 52 | base::Unretained(this))); 53 | 54 | // ok: chromium-unretained-uaf 55 | receiver_.set_disconnect_handler( 56 | base::BindOnce(&LoggerImpl::OnError, base::Unretained(this))); 57 | 58 | // ok: chromium-unretained-uaf 59 | remote_.set_disconnect_handler( 60 | base::BindOnce(&LoggerImpl::OnError, base::Unretained(this))); 61 | 62 | // ok: chromium-unretained-uaf 63 | receiver_.set_disconnect_with_reason_handler( 64 | base::BindOnce(&LoggerImpl::OnError, base::Unretained(this))); 65 | 66 | // ok: chromium-unretained-uaf 67 | remote_.set_disconnect_with_reason_handler( 68 | base::BindOnce(&LoggerImpl::OnError, base::Unretained(this))); 69 | 70 | // ok: chromium-unretained-uaf 71 | timer_.Start(FROM_HERE, base::Seconds(1), 72 | base::BindRepeating(base::Unretained(this), 42)); 73 | 74 | } 75 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/chromium-uaf.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: chromium-unretained-uaf 3 | patterns: 4 | - pattern: base::Unretained(...) 5 | - pattern-not-inside: web_ui()->RegisterMessageCallback(...) 6 | - pattern-not-inside: pref_change_registrar_.Add(...) 7 | - pattern-not-inside: receiver_.set_disconnect_handler(...) 8 | - pattern-not-inside: receiver_.set_disconnect_with_reason_handler(...) 9 | - pattern-not-inside: remote_.set_disconnect_handler(...) 10 | - pattern-not-inside: remote_.set_disconnect_with_reason_handler(...) 11 | - pattern-not-inside: timer_.Start(...) 12 | metadata: 13 | author: Andrea Brancaleoni 14 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/chromium-uaf.yaml 15 | assignees: | 16 | thypon 17 | goodov 18 | iefremov 19 | category: security 20 | message: | 21 | base::Unretained is most of the time unrequited, and a weak reference is better suited for secure coding. 22 | Consider swapping Unretained for a weak reference. 23 | base::Unretained usage may be acceptable when a callback owner is guaranteed 24 | to be destroyed with the object base::Unretained is pointing to, for example: 25 | 26 | - PrefChangeRegistrar 27 | - base::*Timer 28 | - mojo::Receiver 29 | - any other class member destroyed when the class is deallocated 30 | languages: [cpp, c] 31 | severity: WARNING 32 | - id: chromium-bind-uaf 33 | metadata: 34 | author: Andrea Brancaleoni 35 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/chromium-uaf.yaml 36 | assignees: | 37 | thypon 38 | goodov 39 | iefremov 40 | patterns: 41 | - patterns: 42 | - pattern-either: 43 | - pattern: base::BindOnce($FIRST_ARG, $...REST_ARGS) 44 | - pattern: base::BindRepeating($FIRST_ARG, $...REST_ARGS) 45 | - metavariable-comparison: 46 | comparison: not re.match("::", str($FIRST_ARG)) and re.match("this", str($...REST_ARGS)) 47 | message: | 48 | BindOnce/BindRepeating may allow callers to access objects which may already be freed in the C++ lifecycle.
Verify the occurrences manually. 49 | languages: [cpp, c] 50 | severity: WARNING 51 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/const_cast.cpp: -------------------------------------------------------------------------------- 1 | // ruleid: const_cast 2 | auto* browser = const_cast(tab->controller()->GetBrowser()); 3 | 4 | // ruleid: const_cast 5 | const_cast(redirect_info).new_referrer = capped_referrer.spec(); 6 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/const_cast.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: const_cast 3 | metadata: 4 | author: Brian Johnson 5 | references: 6 | - https://chromium.googlesource.com/chromium/src/+/main/styleguide/c++/c++-dos-and-donts.md#use-correctly 7 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/const_cast.yaml 8 | assignees: | 9 | bridiver 10 | cdesouza-chromium 11 | languages: [cpp] 12 | message: Avoid const_cast to remove const, except when implementing non-const getters in terms of const getters. 13 | severity: WARNING 14 | patterns: 15 | - pattern: const_cast<$T>($ARG) 16 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/dangling-pointer-trait.cc: -------------------------------------------------------------------------------- 1 | // ruleid: dangling-pointer-trait 2 | raw_ptr browser_view_ = nullptr; 3 | // ruleid: dangling-pointer-trait 4 | raw_ptr actual_ui_web_contents_ = nullptr; 5 | // ruleid: dangling-pointer-trait 6 | const raw_ptr delegate_; 7 | // ruleid: dangling-pointer-trait 8 | raw_ptr context_ = nullptr; 9 | // ruleid: dangling-pointer-trait 10 | raw_ptr mach_ports_header_ = nullptr; 11 | // ruleid: dangling-pointer-trait 12 | raw_ptr test; 13 | // ruleid: dangling-pointer-trait 14 | raw_ptr status_; 15 | // ruleid: dangling-pointer-trait 16 | std::vector> panes; 17 | // ruleid: dangling-pointer-trait 18 | for (std::set>::iterator iter = 19 | removed_windows.begin(); 20 | iter != removed_windows.end(); ++iter) { 21 | WindowState::Get(*iter)->Unminimize(); 22 | RemoveObserverIfUnreferenced(*iter); 23 | } 24 | // ruleid: dangling-pointer-trait 25 | outgoing_queue_ = std::queue>(); 26 | // ruleid: dangling-pointer-trait 27 | const raw_ref app_list_config_; 28 | // ruleid: dangling-pointer-trait 29 | const raw_ref on_destroyed_; 30 | // ruleid: dangling-pointer-trait 31 | const raw_ref ash_; 32 | // ruleid: dangling-pointer-trait 33 | const raw_ptr delegate_; 34 | // ruleid: dangling-pointer-trait 35 | const raw_ptr delegate_; 36 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/dangling-pointer-trait.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: dangling-pointer-trait 3 | metadata: 4 | author: Artem Chaikin 5 | references: 6 | - https://chromium.googlesource.com/chromium/src.git/+/main/docs/dangling_ptr.md 7 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/dangling-pointer-trait.yaml 8 | assignees: | 9 | stoletheminerals 10 | thypon 11 | cdesouza-chromium 12 | category: security 13 | patterns: 14 | - pattern-either: 15 | - pattern-inside: raw_ptr<...> 16 | - pattern-inside: raw_ref<...> 17 | - pattern-either: 18 | - pattern: DanglingUntriaged 19 | - pattern: DisableDanglingPtrDetection 20 | - pattern: FlakyDanglingUntriaged 21 | - pattern: AcrossTasksDanglingUntriaged 22 | - pattern: AllowPtrArithmetic 23 | - pattern: AllowUninitialized 24 | - pattern: LeakedDanglingUntriaged 25 | - pattern: VectorExperimental 26 | - pattern: SetExperimental 27 | - pattern: CtnExperimental 28 | message: "Detected use of a trait that disables dangling pointer checks. This requires security team approval." 29 | severity: WARNING 30 | languages: 31 | - generic 32 | paths: 33 | include: 34 | - "*.c" 35 | - "*.cpp" 36 | - "*.cc" 37 | - "*.h" 38 | - "*.hh" 39 | - "*.hcc" 40 | - "*.mm" 41 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/get-visible-entry.cc: -------------------------------------------------------------------------------- 1 | // ruleid: get-visible-entry 2 | web_contents->GetController().GetVisibleEntry(); 3 | 4 | // ruleid: get-visible-entry 5 | web_contents->GetVisibleURL(); 6 | 7 | // ok: get-visible-entry 8 | web_contents->GetLastCommittedURL(); 9 | 10 | // ok: get-visible-entry 11 | web_contents->GetController().GetLastCommittedEntry(); 12 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/get-visible-entry.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: get-visible-entry 3 | metadata: 4 | author: Brian Johnson 5 | references: 6 | - https://github.com/brave/brave-browser/wiki/Security-reviews 7 | confidence: MEDIUM 8 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/get-visible-entry.yaml 9 | assignees: | 10 | thypon 11 | diracdeltas 12 | bridiver 13 | category: security 14 | message: | 15 | $FUNC usages should be vet by the security-team. Most of the time you want the last committed entry/url 16 | severity: INFO 17 | languages: 18 | - cpp 19 | patterns: 20 | - pattern: $OBJ.$FUNC(...) 21 | - metavariable-regex: 22 | metavariable: $FUNC 23 | regex: ^(GetVisibleEntry|GetVisibleURL)$ 24 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/glide-library.java: -------------------------------------------------------------------------------- 1 | // ruleid: glide-library 2 | import com.bumptech.glide.load.DataSource; 3 | 4 | if (mBraveNewsController != null) { 5 | mBraveNewsController.getImageData(adImageUrl, imageData -> { 6 | if (imageData != null) { 7 | Bitmap decodedByte = 8 | BitmapFactory.decodeByteArray(imageData, 0, imageData.length); 9 | // ruleid: glide-library 10 | Glide.with(mActivity) 11 | .asBitmap() 12 | .load(decodedByte) 13 | .fitCenter() 14 | .priority(Priority.IMMEDIATE) 15 | .diskCacheStrategy(DiskCacheStrategy.ALL) 16 | .into(new CustomTarget() { 17 | @Override 18 | public void onResourceReady(@NonNull Bitmap resource, 19 | @Nullable Transition transition) { 20 | imageView.setImageBitmap(resource); 21 | } 22 | @Override 23 | public void onLoadCleared(@Nullable Drawable placeholder) {} 24 | }); 25 | imageView.setClipToOutline(true); 26 | } 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/glide-library.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: glide-library 3 | metadata: 4 | author: Artem Chaikin 5 | references: 6 | - https://github.com/brave/reviews/issues/1391 7 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/glide-library.yaml 8 | assignees: | 9 | stoletheminerals 10 | bridiver 11 | category: security 12 | message: "The Glide image loading library is not yet approved, new usages should not be implemented until the security team has given their approval." 13 | languages: [java] 14 | severity: WARNING 15 | patterns: 16 | - pattern-either: 17 | - pattern: "import com.bumptech.glide" 18 | - pattern: "Glide.with(...)" 19 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/in-process-browser-test.cc: -------------------------------------------------------------------------------- 1 | // ruleid: in-process-browser-test 2 | class MyTest : public InProcessBrowserTest { 3 | } 4 | 5 | // ok: in-process-browser-test 6 | class MyTest : public PlatformBrowserTest { 7 | } 8 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/in-process-browser-test.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: in-process-browser-test 3 | metadata: 4 | author: Brian Johnson 5 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/in-process-browser-test.yaml 6 | assignees: | 7 | goodov 8 | cdesouza-chromium 9 | bridiver 10 | category: correctness 11 | pattern: | 12 | class $CLASS : public InProcessBrowserTest 13 | message: "Most browser tests should be PlatformBrowserTest so they can run on android. " 14 | languages: 15 | - generic 16 | paths: 17 | include: 18 | - "*.cc" 19 | - "*.h" 20 | severity: WARNING 21 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/interesting-api-calls.cc: -------------------------------------------------------------------------------- 1 | // ruleid:raptor-interesting-api-calls 2 | amazing_scanf(); 3 | 4 | // ruleid:raptor-interesting-api-calls 5 | memset(str, 'x', 10); 6 | 7 | // ok:raptor-interesting-api-calls 8 | memsetnope(); 9 | 10 | // ok:raptor-interesting-api-calls 11 | CalledOnValidThread(); 12 | 13 | // ok:raptor-interesting-api-calls 14 | void MyService::MaybeReconnect(TaskList tasks, int reconnect) { 15 | return; 16 | } 17 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/interesting-api-calls.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: raptor-interesting-api-calls 3 | metadata: 4 | author: Marco Ivaldi 5 | references: 6 | - https://github.com/0xdea/ghidra-scripts/blob/main/Rhabdomancer.java 7 | - https://github.com/x509cert/banned/blob/master/banned.h 8 | - https://g.co/kgs/PCHQjJ 9 | - https://www.sei.cmu.edu/downloads/sei-cert-c-coding-standard-2016-v01.pdf 10 | confidence: MEDIUM 11 | # NOTE: goto, try/catch, kill/sig/jmp, sem/mutex, new/delete, 12 | # static_cast/reinterpret_cast are not covered. 13 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/interesting-api-calls.yaml 14 | category: security 15 | message: >- 16 | Locate all calls to interesting and potentially insecure API functions (candidate points). The auditor can backtrace from these candidate points to find pathways allowing access from untrusted input. 17 | severity: INFO 18 | languages: 19 | - c 20 | - cpp 21 | patterns: 22 | - pattern: $FUNC(...) 23 | - metavariable-regex: 24 | metavariable: $FUNC 25 | regex: \w*(set\w*(u|g)id|(init|set)groups|str\w?cpy|stpn?cpy|str\w?cat|wcs\w?cpy|wcpn?cpy|wcs\w?cat|strtok|wcstok|s\w?printf\w*\(.*|sn\w?printf\w*\(.*|scanf|get(s|c|char|pw|pass|wd|cwd|env|opt|opt_long)|memc?cpy|mem(move|set)|bcopy|alloca|exec(l|v)?(p|e)?e?|system|open(at)?(64)?|pipe|connect|read|recv(from)?|fork|clone|mk\w?temp(64)?|te?mpnam|tmpfile|mkdir|creat|link(at)?|rename(at)?|access(at)?|stat(at)?|ch(own|mod)(at)?|rand|assert)$ 26 | - pattern-not-regex: RunOnUIThread 27 | - pattern-not-regex: CalledOnValidThread 28 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/licensing.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | The following JavaScript library 4 | 5 | by 6 | 7 | John Doe 8 | 9 | is licensed under 10 | 11 | // ruleid: license-nonfree 12 | CC BY-NC 4.0 13 | 14 | 15 | 16 | 17 |

18 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/licensing.txt: -------------------------------------------------------------------------------- 1 | // ruleid: license-nonfree 2 | This logo is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International license. 3 | 4 | See LICENSE.txt for more details. 5 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/licensing.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: license-nonfree 3 | metadata: 4 | author: Francois Marier 5 | confidence: LOW 6 | assignees: | 7 | diracdeltas 8 | fmarier 9 | thypon 10 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/licensing.yaml 11 | category: security 12 | message: "Non-free license detected" 13 | severity: ERROR 14 | languages: 15 | - regex 16 | patterns: 17 | # ruleid: license-nonfree 18 | - pattern-regex: (NonCommercial|NoDerivs|BY-NC|BY-ND) 19 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/mismatched-memory-management-cpp.cpp: -------------------------------------------------------------------------------- 1 | // ok: raptor-mismatched-memory-management-cpp 2 | Blah& operator=(const Blah&) = delete; 3 | 4 | void RedeemOptedOutConfirmation::Destroy() { 5 | // ok: raptor-mismatched-memory-management-cpp 6 | delete this; 7 | } 8 | 9 | void bad1() 10 | { 11 | BarObj *ptr = new BarObj() 12 | 13 | // ruleid: raptor-mismatched-memory-management-cpp 14 | free(ptr); 15 | } 16 | 17 | void good1() 18 | { 19 | BarObj *ptr = new BarObj() 20 | 21 | // ok: raptor-mismatched-memory-management-cpp 22 | delete ptr; 23 | } 24 | 25 | class A { 26 | void bad2(); 27 | void good2(); 28 | }; 29 | 30 | void A::bad2() 31 | { 32 | int *ptr; 33 | ptr = (int*)malloc(sizeof(int)); 34 | 35 | // ruleid: raptor-mismatched-memory-management-cpp 36 | delete ptr; 37 | } 38 | 39 | void A::good2() 40 | { 41 | int *ptr; 42 | ptr = (int*)malloc(sizeof(int)); 43 | 44 | // ok: raptor-mismatched-memory-management-cpp 45 | free(ptr); 46 | } 47 | 48 | class B { 49 | void bad3(bool); 50 | void good3(); 51 | }; 52 | 53 | void B::bad3(bool heap) { 54 | int localArray[2] = { 11,22 }; 55 | int *p = localArray; 56 | 57 | if (heap) { 58 | p = new int[2]; 59 | } 60 | 61 | // ruleid: raptor-mismatched-memory-management-cpp 62 | delete[] p; 63 | } 64 | 65 | void B::good3() { 66 | int localArray[2] = { 11,22 }; 67 | int *p = localArray; 68 | 69 | p = new (std::nothrow) int[2]; 70 | 71 | // ok: raptor-mismatched-memory-management-cpp 72 | delete[] p; 73 | } -------------------------------------------------------------------------------- /assets/semgrep_rules/client/mismatched-memory-management-cpp.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: raptor-mismatched-memory-management-cpp 3 | metadata: 4 | author: Marco Ivaldi 5 | references: 6 | - https://cwe.mitre.org/data/definitions/762 7 | - https://cwe.mitre.org/data/definitions/590 8 | - https://github.com/struct/mms 9 | - https://docs.microsoft.com/en-us/cpp/sanitizers/asan-error-examples 10 | confidence: LOW 11 | # NOTE: valloc(), reallocf(), aligned_alloc(), and custom wrappers 12 | # are not covered. 13 | # NOTE: overloaded operators, VirtualAlloc()/VirtualFree(), 14 | # mmap()/munmap() are not covered. 15 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/mismatched-memory-management-cpp.yaml 16 | category: security 17 | message: >- 18 | The software attempts to return a memory resource to the system, but it calls a release function that is not compatible with the function that was originally used to allocate that resource. When the memory management functions are mismatched, the consequences may be as severe as code execution, memory corruption, or program crash. Consequences and ease of exploit will vary depending on the implementation of the routines and the object being managed. Due to inherent limitations of Semgrep, this rule might generate many false positives and should therefore be customized for your codebase. 19 | severity: INFO 20 | languages: 21 | - cpp 22 | pattern-either: 23 | # free 24 | - patterns: 25 | - pattern: free($PTR); 26 | - pattern-not-inside: | 27 | $PTR = malloc(...); 28 | ... 29 | free($PTR); 30 | - pattern-not-inside: | 31 | $PTR = ($CAST)malloc(...); 32 | ... 33 | free($PTR); 34 | - pattern-not-inside: | 35 | $PTR = calloc(...); 36 | ... 37 | free($PTR); 38 | - pattern-not-inside: | 39 | $PTR = ($CAST)calloc(...); 40 | ... 41 | free($PTR); 42 | - pattern-not-inside: | 43 | $PTR = realloc(...); 44 | ... 45 | free($PTR); 46 | - pattern-not-inside: "$PTR = ($CAST)realloc(...);\n...\nfree($PTR); \n" 47 | - pattern-not-inside: "$PTR = strdup(...);\n...\nfree($PTR); \n" 48 | - pattern-not-inside: "$PTR = strndup(...);\n...\nfree($PTR); \n" 49 | # delete[] 50 | - patterns: 51 | - pattern: delete[]($PTR); 52 | - pattern-not-inside: | 53 | $PTR = new $OBJ[$SIZE]; 54 | ... 55 | delete[]($PTR); 56 | - pattern-not: delete[](this); 57 | - metavariable-regex: 58 | metavariable: $PTR 59 | regex: . 60 | # delete 61 | - patterns: 62 | - pattern: delete($PTR); 63 | - pattern-not-inside: | 64 | $PTR = new $OBJ; 65 | ... 66 | delete($PTR); 67 | - pattern-not: delete(this); 68 | - metavariable-regex: 69 | metavariable: $PTR 70 | regex: . 71 | - patterns: 72 | - pattern: delete($PTR); 73 | - pattern-inside: | 74 | $PTR = new $OBJ[$SIZE]; 75 | ... 76 | delete($PTR); 77 | - pattern-not: delete(this); 78 | - metavariable-regex: 79 | metavariable: $PTR 80 | regex: . 81 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/privacy.en.md: -------------------------------------------------------------------------------- 1 | // ruleid: privacy 2 | test completely private 3 | // ruleid: privacy 4 | test military grade 5 | // ruleid: privacy 6 | test military-grade 7 | // ruleid: privacy 8 | test totally secure 9 | // ruleid: privacy 10 | test unbreakable encryption 11 | // ruleid: privacy 12 | test unhackable 13 | // ruleid: privacy 14 | test hackerproof 15 | // ruleid: privacy 16 | test hacker-proof 17 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/privacy.md: -------------------------------------------------------------------------------- 1 | // ruleid: privacy 2 | test completely private 3 | // ruleid: privacy 4 | test military grade 5 | // ruleid: privacy 6 | test military-grade 7 | // ruleid: privacy 8 | test totally secure 9 | // ruleid: privacy 10 | test unbreakable encryption 11 | // ruleid: privacy 12 | test unhackable 13 | // ruleid: privacy 14 | test hackerproof 15 | // ruleid: privacy 16 | test hacker-proof 17 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/refcounted-usage.cpp: -------------------------------------------------------------------------------- 1 | // ruleid: refcounted-usage 2 | class MyClass : public base::RefCounted { 3 | }; 4 | 5 | // ruleid: refcounted-usage 6 | class ThreadSafeClass : public base::RefCountedThreadSafe { 7 | }; 8 | 9 | // ruleid: refcounted-usage 10 | base::RefCountedData shared_integer(42); 11 | 12 | // ok: refcounted-usage 13 | class RegularClass { 14 | }; 15 | 16 | // ruleid: refcounted-usage 17 | using MyRefCountedType = base::RefCounted; 18 | 19 | // ruleid: refcounted-usage 20 | class NestedRefCounted : public base::RefCountedThreadSafe { 21 | // ruleid: refcounted-usage 22 | base::RefCountedData nested_data_; 23 | }; 24 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/refcounted-usage.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: refcounted-usage 3 | metadata: 4 | author: Artem Chaikin 5 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/refcounted-usage.yaml 6 | assignees: | 7 | stoletheminerals 8 | cdesouza-chromium 9 | bridiver 10 | pattern-either: 11 | - pattern: base::RefCounted<...> 12 | - pattern: base::RefCountedThreadSafe<...> 13 | - pattern: base::RefCountedData<...> 14 | message: "Reference counting is occasionally useful but is more often a sign that someone isn't thinking carefully about ownership. Use it when ownership is truly shared (for example, multiple tabs sharing the same renderer process), not for when lifetime management is difficult to reason about." 15 | languages: 16 | - generic 17 | paths: 18 | include: 19 | - "*.c" 20 | - "*.cpp" 21 | - "*.cc" 22 | - "*.h" 23 | - "*.hh" 24 | - "*.hcc" 25 | - "*.mm" 26 | severity: WARNING 27 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/reinterpret_cast.cpp: -------------------------------------------------------------------------------- 1 | // ruleid: reinterpret_cast 2 | std::string_view der_cert(reinterpret_cast(cert->pbCertEncoded), cert->cbCertEncoded); 3 | // ruleid: reinterpret_cast 4 | const uint8_t* string_data =reinterpret_cast(response_body.data()); 5 | // ruleid: reinterpret_cast 6 | uint32_t value = *reinterpret_cast(bytes.data()); 7 | // ruleid: reinterpret_cast 8 | int rv = PKCS5_PBKDF2_HMAC(mnemonic.data(), mnemonic.length(), reinterpret_cast(salt.data()), salt.length(), 2048, EVP_sha512(),seed->size(), seed->data()); 9 | // ruleid: reinterpret_cast 10 | float* float_data = reinterpret_cast(const_cast(data)); 11 | // ok: reinterpret_cast 12 | auto orig_fn = reinterpret_cast(g_originals.functions[GET_MODULE_FILENAME_EX_W_ID]); 13 | // ruleid: reinterpret_cast 14 | size_t bytes_read = reinterpret_cast(arg0); 15 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/reinterpret_cast.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: reinterpret_cast 3 | metadata: 4 | author: Artem Chaikin 5 | references: 6 | - https://chromium.googlesource.com/chromium/src/+/main/docs/unsafe_buffers.md#Avoid-reinterpret_cast 7 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/reinterpret_cast.yaml 8 | assignees: | 9 | stoletheminerals 10 | thypon 11 | cdesouza-chromium 12 | languages: [cpp] 13 | message: "Using `reinterpret_cast` against some data types may lead to undefined behaviour. In general, when needing to do these conversions, check how Chromium upstream does them. Most of the times a reinterpret_cast is wrong and there's no guarantee the compiler will generate the code that you thought it would." 14 | severity: WARNING 15 | patterns: 16 | - pattern: reinterpret_cast<$T>($ARG) 17 | - metavariable-regex: 18 | metavariable: $T 19 | regex: ^(.*int.*|.*double.*|.*float.*|.*char.*|.*size_t.*)$ # this probably needs to be tweaked 20 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/typos.c: -------------------------------------------------------------------------------- 1 | // original author: Marco Ivaldi 2 | // original source: https://github.com/0xdea/semgrep-rules/blob/main/c/typos.c 3 | // LICENSE: MIT 4 | // author: Andrea Brancaleoni 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | int bad1() 11 | { 12 | char *src, *dst; 13 | int left; 14 | 15 | while (*src && left) { 16 | *dst++=*src++; 17 | // ruleid: raptor-typos 18 | if (left = 0) { 19 | die("badlen"); 20 | } 21 | left--; 22 | } 23 | } 24 | 25 | void 26 | good1(char *path, char *dir, char *obj) 27 | { 28 | char *last; 29 | 30 | // ok: raptor-typos 31 | if ( (last = strrchr(path,'/')) != NULL ) { 32 | strcpy(obj, last + 1); 33 | if (last == path) { 34 | strcpy(dir, "/"); 35 | } else { 36 | *last = '\0'; 37 | strcpy(dir, path); 38 | *last = '/'; 39 | } 40 | } else { 41 | dir[0] = dir[0] = '\0'; 42 | } 43 | } 44 | 45 | void 46 | good2(char *path, char *dir, char *obj) 47 | { 48 | char *last; 49 | 50 | // ok: raptor-typos 51 | if (char *last = strrchr(path,'/' )) { 52 | strcpy(obj, last + 1); 53 | if (last == path) { 54 | strcpy(dir, "/"); 55 | } else { 56 | *last = '\0'; 57 | strcpy(dir, path); 58 | *last = '/'; 59 | } 60 | } else { 61 | dir[0] = dir[0] = '\0'; 62 | } 63 | } 64 | 65 | int bad2(char *username) 66 | { 67 | int f; 68 | f = get_security_flags(username); 69 | 70 | // ruleid: raptor-typos 71 | if (f = FLAG_AUTHENTICATED) { 72 | return LOGIN_OK; 73 | } 74 | return LOGIN_FAILED; 75 | } 76 | 77 | int bad3(char *src, char *dst) 78 | { 79 | // ok: raptor-typos 80 | if (get_string(src) && 81 | check_for_overflow(src) & copy_string(dst, src)) { 82 | printf("string safely copied\n"); 83 | } 84 | } 85 | 86 | int bad4(char *src, int len) 87 | { 88 | char dst[256]; 89 | 90 | // ruleid: raptor-typos 91 | if (len > 0 && len <= sizeof(dst)); 92 | memcpy(dst, src, len); 93 | } 94 | 95 | int bad5(char *src, char *dst) 96 | { 97 | int i; 98 | // ruleid: raptor-typos 99 | for (i == 5; src[i] && i < 10; i++) { 100 | dst[i - 5] = src[i]; 101 | } 102 | } 103 | 104 | int bad6(char *userinput) 105 | { 106 | // ruleid: raptor-typos 107 | char buf[040]; 108 | 109 | snprintf(buf, 40, "%s", userinput); 110 | } 111 | 112 | int bad7() 113 | { 114 | // ok: raptor-typos 115 | if (frag_len & 116 | !BUF_MEM_grow_clean(s->init_buf, (int)frag_len + 117 | DTLS1_HM_HEADER_LENGTH + s->init_num)) { 118 | SSLerr(SSL_F_DTLS1_GET_MESSAGE_FRAGMENT, ERR_R_BUF_LIB); 119 | goto err; 120 | } 121 | } 122 | 123 | int bad8(int j) 124 | { 125 | int i = 10; 126 | 127 | // ruleid: raptor-typos 128 | i =+ j; 129 | } 130 | 131 | int isValid(int value) 132 | { 133 | // ruleid: raptor-typos 134 | if (value = 100) { 135 | printf("Value is valid\n"); 136 | return(1); 137 | } 138 | 139 | printf("Value is not valid\n"); 140 | return(0); 141 | } 142 | 143 | void processString (char *str) 144 | { 145 | int i; 146 | 147 | for(i = 0; i < strlen(str); i++) { 148 | if (isalnum(str[i])){ 149 | processChar(str[i]); 150 | // ruleid: raptor-typos 151 | } else if (str[i] = ':') { 152 | movingToNewInput(); 153 | } 154 | } 155 | } 156 | 157 | #define SIZE 50 158 | int *tos, *p1, stack[SIZE]; 159 | 160 | void push(int i) 161 | { 162 | p1++; 163 | if (p1 == (tos + SIZE)) { 164 | printf("Print stack overflow error message and exit\n"); 165 | } 166 | // ok: raptor-typos 167 | *p1 == i; 168 | } 169 | 170 | int pop(void) 171 | { 172 | if (p1 == tos) { 173 | printf("Print stack underflow error message and exit\n"); 174 | } 175 | p1--; 176 | return *(p1 + 1); 177 | } 178 | 179 | int main(int argc, char *argv[]) 180 | { 181 | tos = stack; 182 | p1 = stack; 183 | // ... 184 | return 0; 185 | } 186 | // ok: raptor-typos 187 | void TearDown() { settings_map_->ShutdownOnUIThread(); } 188 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/typos.cc: -------------------------------------------------------------------------------- 1 | // ok: raptor-typos 2 | if (auto* playlist_service = 3 | playlist::PlaylistServiceFactory::GetForBrowserContext( 4 | web_contents->GetBrowserContext())) { 5 | playlist_service->ConfigureWebPrefsForBackgroundWebContents(web_contents, 6 | web_prefs); 7 | } 8 | 9 | if (host_content_settings_map) { 10 | // ok: raptor-typos 11 | if (std::unique_ptr 12 | domain_block_navigation_throttle = brave_shields:: 13 | DomainBlockNavigationThrottle::MaybeCreateThrottleFor( 14 | handle, g_brave_browser_process->ad_block_service(), 15 | g_brave_browser_process->ad_block_service() 16 | ->custom_filters_provider(), 17 | EphemeralStorageServiceFactory::GetForContext(context), 18 | host_content_settings_map, 19 | g_browser_process->GetApplicationLocale())) { 20 | throttles.push_back(std::move(domain_block_navigation_throttle)); 21 | } 22 | } 23 | 24 | content::StoragePartitionConfig 25 | BraveContentBrowserClient::GetStoragePartitionConfigForSite( 26 | content::BrowserContext* browser_context, 27 | const GURL& site) { 28 | // ok: raptor-typos 29 | if (auto* request_otr_service = 30 | request_otr::RequestOTRServiceFactory::GetForBrowserContext( 31 | browser_context)) { 32 | if (request_otr_service->IsOTR(site)) { 33 | CHECK(site.has_host()); // upstream also does this before accessing 34 | // site.host() 35 | return content::StoragePartitionConfig::Create( 36 | browser_context, site.host(), /*partition_name=*/"request_otr", 37 | /*in_memory=*/true); 38 | } 39 | 40 | // ruleid: raptor-typos 41 | if (request_otr_service = 42 | request_otr::RequestOTRServiceFactory::GetForBrowserContext( 43 | browser_context)) { 44 | if (request_otr_service->IsOTR(site)) { 45 | CHECK(site.has_host()); 46 | } 47 | 48 | 49 | } 50 | 51 | return ChromeContentBrowserClient::GetStoragePartitionConfigForSite( 52 | browser_context, site); 53 | } 54 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/typos.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: raptor-typos 3 | metadata: 4 | author: Andrea Brancaleoni 5 | original_author: Marco Ivaldi 6 | references: 7 | - https://cwe.mitre.org/data/definitions/480 8 | - https://cwe.mitre.org/data/definitions/481 9 | - https://cwe.mitre.org/data/definitions/482 10 | - https://cwe.mitre.org/data/definitions/483 11 | - https://g.co/kgs/PCHQjJ 12 | - https://www.sei.cmu.edu/downloads/sei-cert-c-coding-standard-2016-v01.pdf 13 | confidence: LOW 14 | license: MIT 15 | original_source: https://raw.githubusercontent.com/0xdea/semgrep-rules/main/c/typos.yaml 16 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/typos.yaml 17 | category: security 18 | # NOTE: common issues with comments are not covered. 19 | # NOTE: constructs such as assert(var == val) lead to false positives. 20 | message: >- 21 | The programmer accidentally uses the wrong operator, which changes the application logic in security-relevant ways. These types of errors are generally the result of a typo. This rule also covers some other common typo patterns. (see $EXPR1) 22 | severity: WARNING 23 | languages: 24 | - c 25 | - cpp 26 | pattern-either: 27 | # == instead of = in assignment (the ternary operator is not supported by Semgrep) 28 | - pattern: for ($EXPR1 == $EXPR2; $EXPR3; $EXPR4) ... 29 | # NOTE: removed since anything similar to DCHECK leads to false positive here 30 | # - pattern: $EXPR1 == $EXPR2; 31 | # = instead of == in comparison 32 | - patterns: 33 | - pattern: if (<... $EXPR1 = $EXPR2 ...>) ... 34 | - pattern-not-inside: if (<... ($EXPR1 = $EXPR2) == $EXPR ...>) ... 35 | - pattern-not-inside: if (<... ($EXPR1 = $EXPR2) != $EXPR ...>) ... 36 | - pattern-not-inside: if (<... ($EXPR1 = $EXPR2) < $EXPR ...>) ... 37 | - pattern-not-inside: if (<... ($EXPR1 = $EXPR2) <= $EXPR ...>) ... 38 | - pattern-not-inside: if (<... ($EXPR1 = $EXPR2) > $EXPR ...>) ... 39 | - pattern-not-inside: if (<... ($EXPR1 = $EXPR2) >= $EXPR ...>) ... 40 | - pattern-not-regex: "if\\s*\\(\\s*[_a-zA-Z][_a-zA-Z0-9:<>]{0,40}\\**\\s+\\**[_a-zA-Z][_a-zA-Z0-9]{0,30}\\s*" 41 | - pattern-not-regex: "if\\s*\\(\\s*[_a-zA-Z][_a-zA-Z0-9*:]{0,40}<[_a-zA-Z][_a-zA-Z0-9:]{0,30}>\\s*" 42 | # REMOVED: False Positive on Chrome, & instead of && in comparison 43 | # - patterns: 44 | # - pattern: if (<... $EXPR1 & $EXPR2 ...>) ... 45 | # - pattern-not-inside: if (<... ($EXPR1 & $EXPR2) == $EXPR ...>) ... 46 | # - pattern-not-inside: if (<... ($EXPR1 & $EXPR2) != $EXPR ...>) ... 47 | # - pattern-not-inside: if (<... ($EXPR1 & $EXPR2) < $EXPR ...>) ... 48 | # - pattern-not-inside: if (<... ($EXPR1 & $EXPR2) <= $EXPR ...>) ... 49 | # - pattern-not-inside: if (<... ($EXPR1 & $EXPR2) > $EXPR ...>) ... 50 | # - pattern-not-inside: if (<... ($EXPR1 & $EXPR2) >= $EXPR ...>) ... 51 | # REMOVED: False Positive on Chrome, | instead of || in comparison 52 | # - patterns: 53 | # - pattern: if (<... $EXPR1 | $EXPR2 ...>) ... 54 | # - pattern-not-inside: if (<... ($EXPR1 | $EXPR2) == $EXPR ...>) ... 55 | # - pattern-not-inside: if (<... ($EXPR1 | $EXPR2) != $EXPR ...>) ... 56 | # - pattern-not-inside: if (<... ($EXPR1 | $EXPR2) < $EXPR ...>) ... 57 | # - pattern-not-inside: if (<... ($EXPR1 | $EXPR2) <= $EXPR ...>) ... 58 | # - pattern-not-inside: if (<... ($EXPR1 | $EXPR2) > $EXPR ...>) ... 59 | # - pattern-not-inside: if (<... ($EXPR1 | $EXPR2) >= $EXPR ...>) ... 60 | # =+ instead of += (and =- instead of -=) 61 | - pattern: $EXPR1 =+ $EXPR2 62 | # - pattern: $EXPR1 =- $EXPR2 63 | # ; at the end of if() or for() statement 64 | - pattern: if ($COND); 65 | - pattern: for ($EXPR1; $EXPR2; $EXPR3); 66 | # accidental octal conversion 67 | - patterns: 68 | - pattern-either: 69 | - pattern: $TYPE $ARR[$SIZE]; 70 | - pattern: $TYPE $ARR[$SIZE] = $EXPR; 71 | - metavariable-regex: 72 | metavariable: $SIZE 73 | regex: '^0.*' 74 | # - pattern: if ($COND) $BODY; 75 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/unsafe-cpp-constructs.cpp: -------------------------------------------------------------------------------- 1 | #ifdef UNSAFE_BUFFERS_BUILD 2 | // TODO(): Remove this and 3 | // convert code to safer constructs. 4 | // ruleid: unsafe_cpp_constructs 5 | #pragma allow_unsafe_buffers 6 | #endif 7 | // ruleid: unsafe_cpp_constructs 8 | std::next(it); 9 | // ruleid: unsafe_cpp_constructs 10 | std::advance(cert_iter, cert_idx); 11 | // ruleid: unsafe_cpp_constructs 12 | std::prev(it); 13 | // ruleid: unsafe_cpp_constructs 14 | const void* const kUserDataKey = &kUserDataKey; 15 | // ok: unsafe_cpp_constructs 16 | static void RegisterCallback(AtExitCallbackType func, uint8_t param); 17 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/unsafe-cpp-constructs.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: unsafe_cpp_constructs 3 | metadata: 4 | author: Artem Chaikin 5 | references: 6 | - https://github.com/brave/brave-browser/wiki/Security-reviews 7 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/unsafe-cpp-constructs.yaml 8 | assignees: | 9 | stoletheminerals 10 | thypon 11 | cdesouza-chromium 12 | category: security 13 | languages: [cpp] 14 | message: "Potentially unsafe C++ construct detected" 15 | severity: WARNING 16 | patterns: 17 | - pattern-either: 18 | - pattern: "std::next(...)" 19 | - pattern: "std::advance(...)" 20 | - pattern: "std::prev(...)" 21 | - pattern-regex: "void\\*" 22 | - pattern-regex: "#pragma allow_unsafe_buffers" 23 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/unsafejs-in-cpp.cc: -------------------------------------------------------------------------------- 1 | int main() { 2 | // ruleid: unsafe-js-in-cpp-strings 3 | const std::string kGetContentLength = "document.body.innerHTML.length"; 4 | 5 | const std::string kGetStyleLength = 6 | // ruleid: unsafe-js-in-cpp-strings 7 | "document.getElementById('brave_speedreader_style').innerHTML.length"; 8 | // ruleid: unsafe-js-in-cpp-strings 9 | const std::string altDot = "asd.document.write"; 10 | } -------------------------------------------------------------------------------- /assets/semgrep_rules/client/unsafejs-in-cpp.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: unsafe-js-in-cpp-strings 3 | metadata: 4 | author: Andrea Brancaleoni 5 | confidence: LOW 6 | assignees: | 7 | diracdeltas 8 | thypon 9 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/unsafejs-in-cpp.yaml 10 | category: security 11 | message: Unsafe JS in CPP strings 12 | languages: 13 | - c 14 | - cpp 15 | paths: 16 | include: 17 | - "*.cpp" 18 | - "*.cc" 19 | - "*.c" 20 | - "*.h" 21 | - "*.hpp" 22 | - "*.hh" 23 | - "*.mm" 24 | exclude: 25 | - test/ 26 | - "*.test.cc" 27 | - "*browsertest*.cc" 28 | - third_party/rust/* 29 | severity: WARNING 30 | patterns: 31 | - pattern-either: 32 | - pattern-regex: innerHTML 33 | - pattern-regex: document\.write 34 | - pattern-inside: | 35 | "..." 36 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/web-contents-user-data.cc: -------------------------------------------------------------------------------- 1 | // ruleid: web-contents-user-data 2 | class MyTest : public WebContentsUserData { 3 | } 4 | // ruleid: web-contents-user-data 5 | class MyTest : public content::WebContentsUserData { 6 | } 7 | // ok: web-contents-user-data 8 | class MyTest : public WebContentsObserver { 9 | } 10 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/web-contents-user-data.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: web-contents-user-data 3 | metadata: 4 | author: Brian Johnson 5 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/web-contents-user-data.yaml 6 | assignees: | 7 | goodov 8 | cdesouza-chromium 9 | bridiver 10 | category: correctness 11 | references: 12 | - https://chromium.googlesource.com/chromium/src/+/main/docs/chrome_browser_design_principles.md#structure_modularity 13 | pattern-either: 14 | - pattern: public content::WebContentsUserData 15 | - pattern: public WebContentsUserData 16 | message: | 17 | Prefer dependency injection 18 | 19 | References: 20 | - https://chromium.googlesource.com/chromium/src/+/main/docs/chrome_browser_design_principles.md#structure_modularity 21 | languages: 22 | - generic 23 | paths: 24 | include: 25 | - "*.cc" 26 | - "*.h" 27 | severity: INFO 28 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/web-ui-origin-encoding.ts: -------------------------------------------------------------------------------- 1 | function testWebUIOriginEncoding() { 2 | const param = "param=test param with spaces and URL https://example.com"; 3 | const unencodedUrl = "https://example.com" 4 | const hash = "#section1"; 5 | const path = "/path/to/resource"; 6 | 7 | // ok: web-ui-origin-encoding 8 | const faviconUrl = "chrome://favicon"; 9 | 10 | // ok: web-ui-origin-encoding 11 | const faviconUrl2 = "chrome://favicon/"; 12 | 13 | // ok: web-ui-origin-encoding 14 | const faviconUrl3 = `chrome://favicon/size/64@1x/${unencodedUrl}`; 15 | 16 | // ok: web-ui-origin-encoding 17 | const staticUrl = "chrome://brave/settings"; 18 | 19 | // ok: web-ui-origin-encoding 20 | const staticUrlWithHash = "chrome://brave/settings#general"; 21 | 22 | // ok: web-ui-origin-encoding 23 | const goodPathUrl = `brave://wallet${path}`; //should ignore as it's a common approach for wallet routing 24 | 25 | // ok: web-ui-origin-encoding 26 | const goodUrl1 = `chrome://brave/${encodeURIComponent(param + param)}`; 27 | 28 | // ok: web-ui-origin-encoding 29 | const goodUrl2 = "chrome://brave/settings?" + encodeURIComponent(param) + encodeURIComponent(hash); 30 | 31 | // ok: web-ui-origin-encoding 32 | const goodUrl3 = `chrome://brave-rewards/${path}?param=${encodeURIComponent(param)}`; 33 | 34 | // ok: web-ui-origin-encoding 35 | const goodUrl4 = `chrome://brave-rewards` + path + `?param=${encodeURIComponent(param)}`; 36 | 37 | // ruleid: web-ui-origin-encoding 38 | const badFaviconUrl3 = `chrome://favicon2?url=${unencodedUrl}`; // favicon2 needs to be URI encoded still 39 | 40 | // ruleid: web-ui-origin-encoding 41 | const partialEncodingUrl = `chrome://brave/${encodeURIComponent( 42 | param 43 | )}${hash}`; // should still trigger as 'hash' isn't encoded 44 | 45 | // ruleid: web-ui-origin-encoding 46 | const multiParamEncoded = `chrome://brave/settings?param1=${encodeURIComponent( 47 | param 48 | )}¶m2=${encodeURIComponent(path)}`; // false positive, but too complex to handle this case so can just be manually checked 49 | 50 | // ruleid: web-ui-origin-encoding 51 | const badBraveWalletUrl = `brave://wallet/${param}` 52 | 53 | // ruleid: web-ui-origin-encoding 54 | const badUrl1 = `chrome://brave/${param}`; // should still throw because it's unencoded data 55 | 56 | // ruleid: web-ui-origin-encoding 57 | const badUrl2 = "chrome://brave/settings?" + param + hash; 58 | 59 | // ruleid: web-ui-origin-encoding 60 | const badUrl3 = `chrome://brave-rewards${path}?param=${param}`; 61 | } 62 | -------------------------------------------------------------------------------- /assets/semgrep_rules/client/web-ui-origin-encoding.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: web-ui-origin-encoding 3 | languages: 4 | - typescript 5 | severity: WARNING 6 | message: URI components should be encoded using encodeURIComponent. This 7 | prevents passed URLs and other encoded data from causing the WebUI URL 8 | from being malformed or parsed incorrectly. 9 | metadata: 10 | author: | 11 | Kyle Den Hartog 12 | Andrea Brancaleoni 13 | confidence: LOW 14 | assignees: | 15 | kdenhartog 16 | thypon 17 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/web-ui-origin-encoding.yaml 18 | category: security 19 | references: 20 | - https://github.com/brave/brave-browser/issues/43367 21 | paths: 22 | include: 23 | - "*.ts" 24 | - "*.js" 25 | - "*.tsx" 26 | exclude: 27 | - test/ 28 | - "*.test.ts" 29 | patterns: 30 | - pattern-inside: '"..."' # get only strings 31 | - pattern-either: 32 | - pattern-regex: chrome:// # pattern should start with chrome:// to detect WebUI URLs 33 | - pattern-regex: brave://wallet # pattern should start with chrome:// to detect WebUI URLs 34 | - pattern-regex: .*(\$\{|\+).* # pattern should either contain a variable expansion with ${...} or adding a new string 35 | - pattern-not-regex: .*encodeURIComponent(...).* # pattern should not contain encodeURIComponent 36 | - pattern-not-regex: chrome://favicon/size/ # negate chrome://favicon legacy version which needs to be unencoded 37 | - pattern-not-regex: brave://wallet(\$\{)([A-Za-z0-9\/]+)\} # ignore cases where wallet routing via paths are in use 38 | -------------------------------------------------------------------------------- /assets/semgrep_rules/generate-compound.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | SEMGREP_VERSION = `semgrep --version`.strip 4 | 5 | HOST = 'https://semgrep.dev' 6 | 7 | files = Dir['client/*.yaml', 'services/*.yaml', 'frozen/*/vuln.yaml', 'frozen/*/audit.yml', 'generated/*/vulns.yaml', 'generated/*/audit.yaml'] 8 | 9 | rules = {'rules' => []} 10 | 11 | files.each do |fname| 12 | begin 13 | irules = YAML.load(File.read(fname))['rules'] 14 | puts "#{fname}: #{irules.length}" 15 | 16 | rules['rules'].concat irules 17 | rescue 18 | puts "Error in #{fname}" 19 | end 20 | end 21 | 22 | puts "#rules: #{rules['rules'].length}" 23 | 24 | File.write("compound.yaml", YAML.dump(rules)) 25 | 26 | # require 'pry' 27 | # binding.pry 28 | 29 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/activerecord-sanitize-sql-noop.rb: -------------------------------------------------------------------------------- 1 | # ruleid: activerecord-sanitize-sql-noop 2 | ActiveRecord::Base.sanitize_sql("#{channel}_channel_id") 3 | 4 | # ok: activerecord-sanitize-sql-noop 5 | sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4]) 6 | # => "name='foo''bar' and group_id=4" 7 | 8 | # ok: activerecord-sanitize-sql-noop 9 | sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) 10 | # => "name='foo''bar' and group_id='4'" 11 | 12 | # ok: activerecord-sanitize-sql-noop 13 | sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4]) 14 | # => "name='foo''bar' and group_id='4'" 15 | 16 | # ruleid: activerecord-sanitize-sql-noop 17 | sanitize_sql_for_conditions("#{user_generated}") 18 | 19 | # ruleid: activerecord-sanitize-sql-noop 20 | sanitize_sql_for_conditions(some_variable) 21 | # possibly a false-positive in the case that some_variable is the correct kind of hash 22 | 23 | # ok: activerecord-sanitize-sql-noop 24 | sanitize_sql_for_order([Arel.sql("field(id, ?)"), [1,3,2]]) 25 | # => "field(id, 1,3,2)" 26 | 27 | # ruleid: activerecord-sanitize-sql-noop 28 | sanitize_sql_for_order("id ASC") 29 | # => "id ASC" 30 | # Yes, directly from the Rails documentation, and not dangerous as constant, but a no-op so bad 31 | 32 | # ruleid: activerecord-sanitize-sql-noop 33 | ActiveRecord::Base.sanitize_sql_for_order("#{order} ASC") 34 | # Like, you're just asking for errors to happen if you aren't whitelisting order anyway. -------------------------------------------------------------------------------- /assets/semgrep_rules/services/activerecord-sanitize-sql-noop.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: activerecord-sanitize-sql-noop 3 | patterns: 4 | - pattern-either: 5 | - pattern: ActiveRecord::Base.$FUNC($STR) 6 | - pattern: $FUNC($STR) 7 | - metavariable-regex: 8 | metavariable: $STR 9 | regex: "^[^[]" 10 | - metavariable-regex: 11 | metavariable: $FUNC 12 | regex: "^sanitize_sql(_for_(order|conditions))?$" 13 | message: | 14 | When $FUNC is called with a string argument rather than an array/hash, it returns the string as-is without sanitization. 15 | The method name is dangerously misleading. 16 | The method's intended use is to safely insert variables into a string containing '?' or ':param', producing a valid SQL fragment for use where parameterized queries will not work. 17 | This method will NOT sanitize just a SQL string. 18 | User input here is likely a SQL injection vulnerability. 19 | languages: 20 | - ruby 21 | severity: INFO 22 | metadata: 23 | author: Ben Caller 24 | references: 25 | - https://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html 26 | confidence: LOW 27 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/activerecord-sanitize-sql-noop.yaml 28 | category: security 29 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/brave-third-party-action-not-pinned-to-commit-sha.test.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request_target: 3 | pull_request: 4 | jobs: 5 | build: 6 | name: Build and test 7 | runs-on: ubuntu-latest 8 | steps: 9 | # ruleid: brave-third-party-action-not-pinned-to-commit-sha 10 | - uses: actions/checkout@v2 # v2.1.2 11 | with: 12 | ref: ${{ github.event.pull_request.head.sha }} 13 | # ruleid: brave-third-party-action-not-pinned-to-commit-sha 14 | - uses: actions/setup-node@master # v1.2.3 15 | - run: | 16 | npm install 17 | npm build 18 | # ok: brave-third-party-action-not-pinned-to-commit-sha 19 | - uses: ./.github/actions/do-a-local-action 20 | with: 21 | arg1: ${{ secrets.supersecret1 }} 22 | # ok: brave-third-party-action-not-pinned-to-commit-sha 23 | - uses: completely/fakeaction@5fd3084fc36e372ff1fff382a39b10d03659f355 # v1.2.3 24 | with: 25 | arg2: ${{ secrets.supersecret2 }} 26 | # ok: brave-third-party-action-not-pinned-to-commit-sha 27 | - uses: docker://alpine@sha256:402d21757a03a114d273bbe372fa4b9eca567e8b6c332fa7ebf982b902207242 # v1.2.3 28 | # ruleid: brave-third-party-action-not-pinned-to-commit-sha 29 | - uses: completely/fakeaction@5fd3084 # v1.2.3 30 | with: 31 | arg2: ${{ secrets.supersecret2 }} 32 | # ruleid: brave-third-party-action-not-pinned-to-commit-sha 33 | - uses: completely/fakeaction@5fd3084 34 | # ruleid: brave-third-party-action-not-pinned-to-commit-sha 35 | - uses: fakerepo/comment-on-pr@v1 36 | with: 37 | message: | 38 | Thank you! 39 | # ok: brave-third-party-action-not-pinned-to-commit-sha 40 | - uses: brave-intl/test@v1 41 | # ok: brave-third-party-action-not-pinned-to-commit-sha 42 | - uses: brave/test@v1 43 | # ok: brave-third-party-action-not-pinned-to-commit-sha 44 | - uses: brave-experiments/test@v1 45 | # ruleid: brave-third-party-action-not-pinned-to-commit-sha 46 | - uses: aws-actions/test@v1 47 | # ruleid: brave-third-party-action-not-pinned-to-commit-sha 48 | - uses: github/test@v1 49 | # ruleid: brave-third-party-action-not-pinned-to-commit-sha 50 | - uses: ruby/setup-ruby@v1 51 | # ruleid: brave-third-party-action-not-pinned-to-commit-sha 52 | - uses: slackapi/slack-github-action@v1.24.0 53 | # ruleid: brave-third-party-action-not-pinned-to-commit-sha 54 | - uses: fakerepo/comment-on-pr 55 | with: 56 | message: | 57 | Thank you! 58 | # ruleid: brave-third-party-action-not-pinned-to-commit-sha 59 | - uses: docker://gcr.io/cloud-builders/gradle 60 | # ruleid: brave-third-party-action-not-pinned-to-commit-sha 61 | - uses: docker://alpine:3.8 62 | - name: Notify Slack of success 63 | # ruleid: brave-third-party-action-not-pinned-to-commit-sha 64 | uses: 8398a7/action-slack@28ba43ae48961b90635b50953d216767a6bea486 65 | 66 | build2: 67 | name: Build and test using a local workflow 68 | # ok: brave-third-party-action-not-pinned-to-commit-sha 69 | uses: ./.github/workflows/use_a_local_workflow.yml@master 70 | secrets: inherit 71 | with: 72 | examplearg: true 73 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/brave-third-party-action-not-pinned-to-commit-sha.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: brave-third-party-action-not-pinned-to-commit-sha 3 | languages: 4 | - yaml 5 | severity: ERROR 6 | message: | 7 | An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA or is missing the semver reference comment 8 | 9 | You can use pinact - https://github.com/suzuki-shunsuke/pinact - to pin them 10 | 11 | 👍 12 | 13 | `uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1` 14 | 15 | 👎 16 | 17 | `uses: actions/cache@v3` 18 | `uses: actions/cache@v3.3.1` 19 | 20 | [GHA Policies](https://github.com/brave/internal/wiki/Pull-request-security-audit-checklist) 21 | patterns: 22 | - pattern-regex: "uses:\\s+(?.*)\\s*$" 23 | - metavariable-pattern: 24 | metavariable: $USES 25 | language: generic 26 | patterns: 27 | - pattern-not-regex: ^[.]/ 28 | - pattern-not-regex: ^brave/ 29 | - pattern-not-regex: ^brave-intl/ 30 | - pattern-not-regex: ^brave-experiments/ 31 | - pattern-not-regex: "@[0-9a-f]{40}\\s+#\\s+v?\\d+\\.\\d+\\.\\d+$" 32 | - pattern-not-regex: "^docker://.*@sha256:[0-9a-f]{64}\\s+#\\s+v?\\d+\\.\\d+\\.\\d+$" 33 | metadata: 34 | cwe: 35 | - "CWE-1357: Reliance on Insufficiently Trustworthy Component" 36 | owasp: A06:2021 - Vulnerable and Outdated Components 37 | references: 38 | - https://owasp.org/Top10/A06_2021-Vulnerable_and_Outdated_Components 39 | - https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions 40 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/brave-third-party-action-not-pinned-to-commit-sha.yaml 41 | category: security 42 | technology: 43 | - github-actions 44 | subcategory: 45 | - vuln 46 | likelihood: LOW 47 | impact: LOW 48 | confidence: HIGH 49 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 50 | vulnerability_class: 51 | - Other 52 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/detected-aws-access-key-id-value.js: -------------------------------------------------------------------------------- 1 | // ruleid: detected-aws-access-key-id-value 2 | var AWS_ACCESS_KEY_ID = "AKIABBBBBBBBBBBBBB1B"; 3 | 4 | // ruleid: detected-aws-access-key-id-value 5 | /* 6 | AKIABBBBBBBBBBBBBB1B 7 | */ 8 | 9 | // ok: detected-aws-access-key-id-value 10 | const SOUVLAKIAISTOTALLYDELICIOUS = true; -------------------------------------------------------------------------------- /assets/semgrep_rules/services/detected-aws-access-key-id-value.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: detected-aws-access-key-id-value 3 | patterns: 4 | - pattern-regex: (^|[^A-Za-z0-9])(A3T[A-Z0-9]|AKIA|AGPA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16} 5 | - pattern-not-regex: (?i)example|sample|test|fake 6 | languages: 7 | - regex 8 | message: AWS Access Key ID Value detected. This is a sensitive credential and should not be hardcoded here. Instead, read this value from an environment variable or keep it in a separate, private file. 9 | severity: ERROR 10 | metadata: 11 | cwe: 12 | - "CWE-798: Use of Hard-coded Credentials" 13 | source-rule-url: https://github.com/grab/secret-scanner/blob/master/scanner/signatures/pattern.go 14 | category: security 15 | technology: 16 | - secrets 17 | - aws 18 | confidence: LOW 19 | owasp: 20 | - A07:2021 - Identification and Authentication Failures 21 | references: 22 | - https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures 23 | - https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-prefixes 24 | - https://semgrep.dev/r?q=generic.secrets.security.detected-aws-access-key-id-value.detected-aws-access-key-id-value 25 | cwe2022-top25: true 26 | cwe2021-top25: true 27 | subcategory: 28 | - audit 29 | likelihood: LOW 30 | impact: HIGH 31 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 32 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/detected-aws-access-key-id-value.yaml 33 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/docker-compose-no-new-privileges.test.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | name: mytest 3 | services: 4 | # Ignore when extended (hopefully the base file is secure :/) 5 | # ok: docker-compose-no-new-privileges 6 | ser: 7 | e: f 8 | extends: 9 | file: docker-compose-common.yml 10 | service: lts 11 | g: h 12 | image: links 13 | i: j 14 | # ruleid: docker-compose-no-new-privileges 15 | bad: 16 | a: b 17 | image: links 18 | c: d 19 | # Ignore when extended (hopefully the base file is secure :/) 20 | # ok: docker-compose-no-new-privileges 21 | vice: 22 | x: y 23 | extends: 24 | file: ../../docker-compose.yml 25 | service: vice 26 | volumes: 27 | - /x:/y 28 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/docker-compose-no-new-privileges.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: docker-compose-no-new-privileges 3 | patterns: 4 | - pattern-inside: | 5 | version: ... 6 | ... 7 | services: 8 | ... 9 | - pattern: | 10 | $SERVICE: 11 | ... 12 | image: ... 13 | - pattern-not: | 14 | $SERVICE: 15 | ... 16 | image: ... 17 | ... 18 | security_opt: 19 | - ... 20 | - no-new-privileges=true 21 | - ... 22 | - pattern-not: | 23 | $SERVICE: 24 | ... 25 | extends: ... 26 | - focus-metavariable: "$SERVICE" 27 | message: Service '$SERVICE' allows for privilege escalation via setuid or setgid binaries. Add 'no-new-privileges=true' in 'security_opt' to prevent this. 28 | metadata: 29 | cwe: 30 | - 'CWE-732: Incorrect Permission Assignment for Critical Resource' 31 | owasp: 32 | - A05:2021 - Security Misconfiguration 33 | - A06:2017 - Security Misconfiguration 34 | references: 35 | - https://docs.docker.com/engine/reference/run/#security-configuration 36 | - https://raesene.github.io/blog/2019/06/01/docker-capabilities-and-no-new-privs/ 37 | - https://www.kernel.org/doc/Documentation/prctl/no_new_privs.txt 38 | - https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-4-add-no-new-privileges-flag 39 | category: security 40 | technology: 41 | - docker-compose 42 | cwe2021-top25: true 43 | subcategory: 44 | - audit 45 | likelihood: LOW 46 | impact: HIGH 47 | confidence: LOW 48 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 49 | vulnerability_class: 50 | - Improper Authorization 51 | source-rule-url: https://semgrep.dev/r/yaml.docker-compose.security.no-new-privileges.no-new-privileges 52 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/docker-compose-no-new-privileges.yaml 53 | languages: 54 | - yaml 55 | severity: WARNING 56 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/find-links-without-no-index.txt: -------------------------------------------------------------------------------- 1 | sphinx-tabs == 3.4.1 2 | sphinx-rtd-theme 3 | sphinx == 5.2.3 4 | sphinx-toolbox == 3.4.0 5 | tlcpack-sphinx-addon==0.2.2 6 | sphinxcontrib_httpdomain==1.8.1 7 | sphinxcontrib-napoleon==0.7 8 | sphinx-reredirects==0.1.2 9 | // ruleid: find-links-without-no-index 10 | --find-links https://mlc.ai/wheels rw 11 | mlc-ai-nightly 12 | --find-links --no-index https://mlc.ai/wheels 13 | --no-index --find-links https://mlc.ai/wheels 14 | // ruleid: find-links-without-no-index 15 | pip3 install --quiet --pre -U -f https://mlc.ai/wheels mlc-ai-nightly 16 | // ruleid: find-links-without-no-index 17 | pip install --quiet --pre -U -f https://mlc.ai/wheels mlc-ai-nightly 18 | pip install --quiet --pre -U -f --no-index https://mlc.ai/wheels mlc-ai-nightly 19 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/find-links-without-no-index.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: find-links-without-no-index 3 | metadata: 4 | author: Artem Chaikin 5 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/find-links-without-no-index.yaml 6 | references: 7 | - https://portswigger.net/daily-swig/dependency-confusion-attack-mounted-via-pypi-repo-exposes-flawed-package-installer-behavior 8 | - https://www.bleepingcomputer.com/news/security/pytorch-discloses-malicious-dependency-chain-compromise-over-holidays/ 9 | confidence: LOW 10 | category: security 11 | pattern-either: 12 | - pattern-regex: ^(?!.*--no-index).*--find-links 13 | - pattern-regex: ^(?!.*--no-index).*(pip|pip3)\s.*\s-f 14 | message: "When --find-links or -f is used without --no-index, pip may try to install the package from PyPI. Add --no-index to avoid dependency confusion." 15 | severity: INFO 16 | languages: 17 | - generic 18 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/http-parse-multipart-dos.go: -------------------------------------------------------------------------------- 1 | func handleBad(w http.ResponseWriter, r *http.Request) error { 2 | // ruleid: http-parse-multipart-dos 3 | if err = r.ParseMultipartForm(maxSize); err != nil { 4 | return err 5 | } 6 | return nil 7 | } 8 | 9 | func handleOK(w http.ResponseWriter, r *http.Request) error { 10 | r.Body = http.MaxBytesReader(w, r.Body, 123) 11 | fmt.Print("banana") 12 | // ok: http-parse-multipart-dos 13 | if err = r.ParseMultipartForm(maxSize); err != nil { 14 | return err 15 | } 16 | return nil 17 | } -------------------------------------------------------------------------------- /assets/semgrep_rules/services/http-parse-multipart-dos.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: http-parse-multipart-dos 3 | metadata: 4 | author: Ben Caller 5 | confidence: LOW 6 | references: 7 | - https://pkg.go.dev/net/http#Request.ParseMultipartForm 8 | - https://pkg.go.dev/net/http#MaxBytesReader 9 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/http-parse-multipart-dos.yaml 10 | assignees: | 11 | thypon 12 | kdenhartog 13 | category: security 14 | severity: INFO 15 | languages: 16 | - go 17 | patterns: 18 | - pattern: $R.ParseMultipartForm($MAXSIZE) 19 | - pattern-not-inside: | 20 | $R.Body = http.MaxBytesReader($W, <...$R.Body...>, $LIMIT) 21 | ... 22 | fix: $R.Body = http.MaxBytesReader($W, $R.Body, $MAXSIZE) 23 | message: |- 24 | ParseMultipartForm is vulnerable to Denial of Service (DoS) by clients sending a large HTTP request body. 25 | The specified limit of $MAXSIZE is the maximum amount stored in memory. 26 | The remainder is still parsed and stored on disk in temporary files. 27 | Wrapping $R.Body with http.MaxBytesReader prevents this wasting of server resources. 28 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/insecure-types.go: -------------------------------------------------------------------------------- 1 | // LICENSE: Commons Clause License Condition v1.0[LGPL-2.1-only] 2 | // original source: https://github.com/returntocorp/semgrep-rules/blob/5b098c252feec688d243cef046d07597a546c25b/go/template/security/insecure-types.go 3 | 4 | package main 5 | 6 | import "fmt" 7 | import "html/template" 8 | 9 | func main() { 10 | var g = "foo" 11 | 12 | // ruleid:go-insecure-templates 13 | const a template.HTML = fmt.Sprintf("link") 14 | // ruleid:go-insecure-templates 15 | var b template.CSS = "a { text-decoration: underline; } " 16 | 17 | // ruleid:go-insecure-templates 18 | var c template.HTMLAttr = fmt.Sprintf("herf=%q") 19 | 20 | // ruleid:go-insecure-templates 21 | const d template.JS = "{foo: 'bar'}" 22 | 23 | // ruleid:go-insecure-templates 24 | var e template.JSStr = "setTimeout('alert()')"; 25 | 26 | // ruleid:go-insecure-templates 27 | var f template.Srcset = g; 28 | 29 | // ok:go-insecure-templates 30 | tmpl, err := template.New("test").ParseFiles("file.txt") 31 | 32 | // other code 33 | myTpl.Execute(w, a); 34 | } 35 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/insecure-types.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: go-insecure-templates 3 | patterns: 4 | - pattern-inside: | 5 | import "html/template" 6 | ... 7 | - pattern-either: 8 | - pattern: var $VAR template.HTML = $EXP 9 | - pattern: var $VAR template.CSS = $EXP 10 | - pattern: var $VAR template.HTMLAttr = $EXP 11 | - pattern: var $VAR template.JS = $EXP 12 | - pattern: var $VAR template.JSStr = $EXP 13 | - pattern: var $VAR template.Srcset = $EXP 14 | message: >- 15 | usage of insecure template types. They are documented as a security risk. See https://golang.org/pkg/html/template/#HTML. 16 | severity: WARNING 17 | metadata: 18 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/insecure-types.yaml 19 | original_source: https://github.com/returntocorp/semgrep-rules/blob/5b098c252feec688d243cef046d07597a546c25b/go/template/security/insecure-types.yaml 20 | cwe: 21 | - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" 22 | references: 23 | - https://golang.org/pkg/html/template/#HTML 24 | - https://twitter.com/empijei/status/1275177219011350528 25 | category: security 26 | technology: 27 | - template 28 | confidence: LOW 29 | owasp: 30 | - A07:2017 - Cross-Site Scripting (XSS) 31 | - A03:2021 - Injection 32 | cwe2022-top25: true 33 | cwe2021-top25: true 34 | subcategory: 35 | - audit 36 | likelihood: LOW 37 | impact: MEDIUM 38 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 39 | languages: 40 | - go 41 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/internal-digest-call.py: -------------------------------------------------------------------------------- 1 | def signature(**kwargs): 2 | # ruleid: internal-digest-call 3 | sig = _INTERNAL_DIGEST_NEVER_CALL_DIRECTLY(kwargs) 4 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/internal-digest-call.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: internal-digest-call 3 | pattern-regex: _INTERNAL_DIGEST_NEVER_CALL_DIRECTLY 4 | message: Internal Digest Direct Call, never call this directly 5 | languages: 6 | - python 7 | severity: WARNING 8 | metadata: 9 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/internal-digest-call.yaml 10 | assignees: | 11 | stoletheminerals 12 | cdesouza-chromium 13 | bridiver 14 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/io-readall-dos.go: -------------------------------------------------------------------------------- 1 | func handleBad(w http.ResponseWriter, r *http.Request) []byte { 2 | // ruleid: io-readall-dos 3 | payload, _ = io.ReadAll(r.Body) 4 | return payload 5 | } 6 | 7 | func handleOK(w http.ResponseWriter, r *http.Request) []byte { 8 | r.Body = http.MaxBytesReader(w, r.Body, 123) 9 | fmt.Print("banana") 10 | // ok: io-readall-dos 11 | payload, _ = io.ReadAll(r.Body) 12 | return payload 13 | } 14 | 15 | // ok: io-readall-dos 16 | io.ReadAll(io.LimitReader(r.Body, u.maxRequestSize)) 17 | 18 | // ok: io-readall-dos 19 | io.ReadAll(x.y) -------------------------------------------------------------------------------- /assets/semgrep_rules/services/io-readall-dos.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: io-readall-dos 3 | metadata: 4 | author: Ben Caller 5 | confidence: LOW 6 | references: 7 | - https://pkg.go.dev/io#ReadAll 8 | - https://pkg.go.dev/net/http#MaxBytesReader 9 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/io-readall-dos.yaml 10 | assignees: | 11 | thypon 12 | kdenhartog 13 | category: security 14 | severity: INFO 15 | languages: 16 | - go 17 | patterns: 18 | - pattern: io.ReadAll($R.Body) 19 | - pattern-not-inside: | 20 | $R.Body = http.MaxBytesReader($W, <...$R.Body...>, $LIMIT) 21 | ... 22 | fix: io.ReadAll(http.MaxBytesReader(w, $R.Body, MAX_REQUEST_SIZE)) 23 | message: |- 24 | io.ReadAll is vulnerable to Denial of Service (DoS) by clients sending a large HTTP request body. 25 | Wrapping $R.Body with http.MaxBytesReader (or io.LimitReader) prevents this wasting of server resources. 26 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/jinja-safe-usages.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block body %} 4 |

{{article.title}}

5 | Written by {{article.author}} on {{article.create_date}} 6 |
7 |
8 | 9 | {{article.body | safe}} 10 | 11 | {{article.body | safe }} 12 | 13 | {{article.head | clean | safe}} 14 | 15 | {{article.head|clean|safe}} 16 | 17 | {{article.body | safeBlah}} 18 |
19 | {% endblock %} -------------------------------------------------------------------------------- /assets/semgrep_rules/services/jinja-safe-usages.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: template-unescaped-with-safe-noclean 3 | message: Detected a segment of a Flask template where autoescaping is explicitly disabled with '| safe' filter. This allows rendering of raw HTML in this segment. Ensure no user data is rendered here, otherwise this is a cross-site scripting (XSS) vulnerability. 4 | metadata: 5 | cwe: 6 | - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" 7 | owasp: 8 | - A07:2017 - Cross-Site Scripting (XSS) 9 | - A03:2021 - Injection 10 | references: 11 | - https://flask.palletsprojects.com/en/1.1.x/security/#cross-site-scripting-xss 12 | category: security 13 | technology: 14 | - flask 15 | cwe2022-top25: true 16 | cwe2021-top25: true 17 | subcategory: 18 | - audit 19 | likelihood: LOW 20 | impact: MEDIUM 21 | confidence: LOW 22 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 23 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/jinja-safe-usages.yaml 24 | languages: 25 | - regex 26 | paths: 27 | include: 28 | - "*.html" 29 | severity: WARNING 30 | patterns: 31 | - pattern-regex: "{{.*?\\|\\s*safe(\\s*}})?" 32 | - pattern-not-regex: "{{.*?clean\\s*\\|\\s*safe(\\s*}})?" 33 | - pattern-not-regex: "{{.*?\\|\\s*safe[^\\s}]" 34 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/missing-integrity.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/missing-integrity.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: missing-integrity 3 | metadata: 4 | category: security 5 | technology: 6 | - html 7 | cwe: 8 | - 'CWE-353: Missing Support for Integrity Check' 9 | owasp: 10 | - A08:2021 - Software and Data Integrity Failures 11 | confidence: LOW 12 | references: 13 | - https://owasp.org/Top10/A08_2021-Software_and_Data_Integrity_Failures 14 | subcategory: 15 | - audit 16 | likelihood: LOW 17 | impact: LOW 18 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/missing-integrity.yaml 19 | patterns: 20 | - pattern-either: 21 | - pattern: 22 | - pattern: 23 | - metavariable-pattern: 24 | metavariable: $...A 25 | patterns: 26 | - pattern-either: 27 | - pattern: src='... :// ...' 28 | - pattern: src="... :// ..." 29 | - pattern: href='... :// ...' 30 | - pattern: href="... :// ..." 31 | - pattern: src='//...' 32 | - pattern: src="//..." 33 | - pattern: href='//...' 34 | - pattern: href="//..." 35 | - pattern-not-regex: (?is).*integrity= 36 | - pattern-not-regex: (google-analytics.com|fonts.googleapis.com|googletagmanager.com) 37 | - pattern-not-regex: 'chrome:\/\/' 38 | - pattern-not-regex: 'chrome-untrusted:\/\/' 39 | paths: 40 | include: 41 | - '*.html' 42 | message: >- 43 | This tag is missing an 'integrity' subresource integrity attribute. The 'integrity' attribute allows for the browser to verify that externally hosted files (for example from a CDN) are delivered without unexpected manipulation. Without this attribute, if an attacker can modify the externally hosted resource, this could lead to XSS and other types of attacks. To prevent this, include the base64-encoded cryptographic hash of the resource (file) you’re telling the browser to fetch in the 'integrity' attribute for all externally hosted files. 44 | severity: WARNING 45 | languages: [generic] 46 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/missing-noopener-window-open-native.js: -------------------------------------------------------------------------------- 1 | // ruleid: missing-noopener-window-open-native 2 | window.open("something") 3 | // ruleid: missing-noopener-window-open-native 4 | window.open("ciao", "biao") 5 | // ruleid: missing-noopener-window-open-native 6 | open("ciao", "ciao") 7 | 8 | // ok: missing-noopener-window-open-native 9 | window.open("ciao", "bao", "noopeners", "asd") 10 | // ruleid: missing-noopener-window-open-native 11 | window.open("ciao", "bao", "antani", "asd") 12 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/missing-noopener-window-open-native.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: missing-noopener-window-open-native 3 | message: window.open should enforce `noopener` to avoid tab-nabbing vulnerabilities. 4 | metadata: 5 | author: Andrea Brancaleoni @ Brave 6 | confidence: LOW 7 | cwe: 8 | - "CWE-200: Exposure of Sensitive Information to an Unauthorized Actor" 9 | owasp: 10 | - A01:2021 - Broken Access Control 11 | references: 12 | - https://web.dev/external-anchors-use-rel-noopener/ 13 | - https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer 14 | category: security 15 | cwe2021-top25: true 16 | subcategory: 17 | - audit 18 | likelihood: LOW 19 | impact: LOW 20 | license: MIT 21 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/missing-noopener-window-open-native.yaml 22 | languages: 23 | - typescript 24 | - javascript 25 | paths: 26 | exclude: 27 | - '*test*' 28 | severity: INFO 29 | patterns: 30 | - pattern-either: 31 | - patterns: 32 | - pattern-either: 33 | - pattern: window.open($...URL) 34 | - pattern: document.open($...URL) 35 | - pattern: open($...URL) 36 | - metavariable-comparison: 37 | metavariable: $...URL 38 | comparison: not re.match('.*(chrome|brave)(-untrusted)?://.*', str($...URL)) and re.match('^([^,]*|[^,]*,[^,]*)$', str($...URL)) 39 | - patterns: 40 | - pattern-either: 41 | - pattern: window.open($URL, $TARGET, $FEATURES, ...) 42 | - pattern: document.open($URL, $TARGET, $FEATURES, ...) 43 | - pattern: open($URL, $TARGET, $FEATURES, ...) 44 | - metavariable-comparison: 45 | metavariable: $...FEATURES 46 | comparison: not re.match(".*no(opener|referrer).*", str($FEATURES)) 47 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/missing-noopener-window-open.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/no-backticks-in-js-handlers.html: -------------------------------------------------------------------------------- 1 | // ruleid: no-backticks-in-js-handlers 2 | onclick="call('good', `{{var}}`, `{{var}}`)" 3 | // ruleid: no-backticks-in-js-handlers 4 | onclick='call("good", `{{var}}`, `{{var}}`)' 5 | // ruleid: no-backticks-in-js-handlers 6 | onclick=call('good', `{{var}}`, `{{var}}`) 7 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/no-backticks-in-js-handlers.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: no-backticks-in-js-handlers 3 | metadata: 4 | author: Andrea Brancaleoni @ Brave 5 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/no-backticks-in-js-handlers.yaml 6 | category: security 7 | patterns: 8 | - pattern-either: 9 | - pattern-inside: $HANDLER="..." 10 | - pattern-inside: $HANDLER='...' 11 | - pattern-inside: $HANDLER=... 12 | - pattern-regex: '`{{[^}]+}}`' 13 | - metavariable-regex: 14 | metavariable: $HANDLER 15 | regex: (?i)on[a-z]{3,40} 16 | message: | 17 | Backtick in JS handler may cause XSS since they are typically not auto-escaped in variables. 18 | 19 | Consider using single or double quotes instead of backticks. 20 | languages: [generic] 21 | severity: WARNING 22 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/nodejs-insecure-url-parse.js: -------------------------------------------------------------------------------- 1 | // ruleid: nodejs-insecure-url-parse 2 | url.parse("here lies dragons"); 3 | // ruleid: nodejs-insecure-url-parse 4 | require('url').parse("here lies dragons"); 5 | 6 | var uparser = require('url'); 7 | 8 | // ruleid: nodejs-insecure-url-parse 9 | uparser.parse("here lies dragons"); 10 | 11 | function() { 12 | // ruleid: nodejs-insecure-url-parse 13 | uparser.parse("here lies dragons"); 14 | } 15 | 16 | // ruleid: nodejs-insecure-url-parse 17 | setTimeout(()=> uparser.parse("here lies dragons"), 1000); 18 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/nodejs-insecure-url-parse.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: nodejs-insecure-url-parse 3 | metadata: 4 | author: Andrea Brancaleoni 5 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/nodejs-insecure-url-parse.yaml 6 | assignees: | 7 | thypon 8 | fmarier 9 | references: 10 | - https://nodejs.org/api/url.html#urlparseurlstring-parsequerystring-slashesdenotehost 11 | - https://nodejs.org/api/url.html#the-whatwg-url-api 12 | category: security 13 | pattern-either: 14 | - pattern: url.parse(...) 15 | - pattern: require('url').parse(...) 16 | message: Avoid using url.parse() as it is prone to security issues such as hostname spoofing. Use the URL class instead. 17 | severity: ERROR 18 | languages: 19 | - javascript 20 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/not-using-hasownproperty-proto-access.js: -------------------------------------------------------------------------------- 1 | export function getThing(thing) { 2 | // ruleid: not-using-hasownproperty-proto-access 3 | if (thing in thingContainer) { 4 | return thingContainer[thing]; 5 | } 6 | } -------------------------------------------------------------------------------- /assets/semgrep_rules/services/not-using-hasownproperty-proto-access.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: not-using-hasownproperty-proto-access 3 | pattern: | 4 | if ($X in $OBJ) { 5 | ... 6 | $OBJ[$X] 7 | ... 8 | } 9 | message: This allows $X to be `__proto__` or `toString`. To prevent prototype access use Object.prototype.hasOwnProperty.call($OBJ, $X) or use a Map. 10 | languages: 11 | - js 12 | - ts 13 | severity: WARNING 14 | metadata: 15 | category: security 16 | references: 17 | - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty 18 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/not-using-hasownproperty-proto-access.yaml 19 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/path-travesal-by-string-interpolation.ts: -------------------------------------------------------------------------------- 1 | async function x() { 2 | // ruleid: path-travesal-by-string-interpolation 3 | await fetch(`${env.API_HOSTNAME}/api/subscriptions/${subscriptionId}`, 1337); 4 | // ruleid: path-travesal-by-string-interpolation 5 | await fetch(`${env.API_HOSTNAME}/api/subscriptions/${subscriptionId}`); 6 | // ok: path-travesal-by-string-interpolation 7 | await fetch(`${env.API_HOSTNAME}/api/subscriptions/?s=${subscriptionId}`, 123); 8 | } -------------------------------------------------------------------------------- /assets/semgrep_rules/services/path-travesal-by-string-interpolation.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: path-travesal-by-string-interpolation 3 | metadata: 4 | author: Ben Caller 5 | confidence: MEDIUM 6 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/path-travesal-by-string-interpolation.yaml 7 | category: security 8 | message: The code contains new security hotspots which should be checked manually by a security team member! Could a user perform path traversal by setting a variable to include `../`? 9 | severity: INFO 10 | languages: 11 | - ts 12 | - js 13 | paths: 14 | include: 15 | - "*.server.ts" 16 | - "*.server.js" 17 | - path-travesal-by-string-interpolation.ts 18 | - path-travesal-by-string-interpolation.svelte 19 | patterns: 20 | - pattern: fetch($URL, ...) 21 | - metavariable-regex: 22 | metavariable: $URL 23 | # Trigger on /x/${v} but don't trigger on /x/?y=${v} 24 | regex: (`.[^#?]+\$) 25 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/pip-extra-index-url.Makefile: -------------------------------------------------------------------------------- 1 | venv/activate/test: 2 | $(info Creating virtual environment for test at ${VENV_PATH}) 3 | python3.11 -m venv $(VENV_PATH) 4 | $(VENV_PIP) install --upgrade pip wheel setuptools 5 | # ruleid: pip-extra-index-url 6 | $(VENV_PIP) install --extra-index-url ${PYPI_INDEX_URL} --trusted-host ${PYPI_TRUSTED_HOST} my-very-private-package 7 | $(VENV_PIP) install --editable ${WHATEVER_PATH}/../../shared/horse/ -------------------------------------------------------------------------------- /assets/semgrep_rules/services/pip-extra-index-url.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: pip-extra-index-url 3 | metadata: 4 | author: Ben Caller 5 | references: 6 | - https://portswigger.net/daily-swig/dependency-confusion-attack-mounted-via-pypi-repo-exposes-flawed-package-installer-behavior 7 | - https://www.bleepingcomputer.com/news/security/pytorch-discloses-malicious-dependency-chain-compromise-over-holidays/ 8 | confidence: LOW 9 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/pip-extra-index-url.yaml 10 | category: security 11 | message: >- 12 | Use --index-url instead of --extra-index-url to avoid dependency confusion. When using --extra-index-url, pip looks on pypi.org as well as the private index. It may install a malicious package from pypi.org with the same name as your private package instead of the package in your private index. 13 | severity: INFO 14 | languages: 15 | - generic 16 | pattern: --extra-index-url 17 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/react-href-var.tsx: -------------------------------------------------------------------------------- 1 | // ok: react-href-var 2 | let zzz = ; 3 | 4 | function test1(input) { 5 | // ruleid: react-href-var 6 | const params = {href: input.a}; 7 | return React.createElement("a", params); 8 | } 9 | 10 | // ok: react-href-var 11 | {collaborationSectionData.paragraphs.map((item, i) => ( 12 | 13 | ))} 14 | 15 | // ok: react-href-var 16 | let zzz = ; 17 | 18 | // ok: react-href-var 19 | let zzz = ; 20 | 21 | // ok: react-href-var 22 | let zzz = ; 23 | 24 | function test1(input) { 25 | // ok: react-href-var 26 | if(input.startsWith("https:")) { 27 | const params = {href: input}; 28 | return React.createElement("a", params); 29 | } 30 | } 31 | 32 | function test2(input) { 33 | // ok: react-href-var 34 | const params = {href: "#"+input}; 35 | return React.createElement("a", params); 36 | } 37 | 38 | function test2(input) { 39 | // ok: react-href-var 40 | const params = {href: "#"+input}; 41 | return React.createElement("a", params); 42 | } 43 | 44 | 45 | // ok: react-href-var 46 | const b = ; 47 | 48 | // ok: react-href-var 49 | let x = ; 50 | 51 | // ok: react-href-var 52 | let x = ; 53 | 54 | function okTest1() { 55 | // ok: react-href-var 56 | return React.createElement("a", {href: "https://www.example.com"}); 57 | } 58 | 59 | function F({ info }) { 60 | const u = info.data.url.url; 61 | // ok: react-href-var 62 | return Link 63 | } 64 | 65 | function G({ info }) { 66 | const u = info.data.url.url; 67 | // ruleid: react-href-var 68 | return Link 69 | } -------------------------------------------------------------------------------- /assets/semgrep_rules/services/react-href-var.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: react-href-var 3 | message: Detected a variable used in an anchor tag with the 'href' attribute. A malicious actor may be able to input the 'javascript:' URI, which could cause cross-site scripting (XSS). It is recommended to disallow 'javascript:' URIs within your application. 4 | metadata: 5 | cwe: 6 | - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" 7 | owasp: 8 | - A07:2017 - Cross-Site Scripting (XSS) 9 | - A03:2021 - Injection 10 | references: 11 | - https://reactjs.org/blog/2019/08/08/react-v16.9.0.html#deprecating-javascript-urls 12 | - https://pragmaticwebsecurity.com/articles/spasecurity/react-xss-part1.html 13 | category: security 14 | confidence: LOW 15 | technology: 16 | - react 17 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 18 | cwe2022-top25: true 19 | cwe2021-top25: true 20 | subcategory: 21 | - audit 22 | likelihood: LOW 23 | impact: MEDIUM 24 | vulnerability_class: 25 | - Cross-Site-Scripting (XSS) 26 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/react-href-var.yaml 27 | original_source: https://semgrep.dev/r/typescript.react.security.audit.react-href-var.react-href-var 28 | languages: 29 | - typescript 30 | - javascript 31 | severity: WARNING 32 | mode: taint 33 | pattern-sources: 34 | - label: TAINTED 35 | patterns: 36 | - pattern-either: 37 | - pattern-inside: | 38 | function ...({..., $X, ...}) { ... } 39 | - pattern-inside: | 40 | function ...(..., $X, ...) { ... } 41 | - focus-metavariable: $X 42 | - pattern-either: 43 | - pattern: $X.$Y 44 | - pattern: $X[...] 45 | - pattern-not-inside: | 46 | $F. ... .$SANITIZEUNC(...) 47 | - label: CONCAT 48 | requires: TAINTED 49 | patterns: 50 | - pattern-either: 51 | - pattern: | 52 | `...${$X}...` 53 | - pattern: | 54 | $SANITIZE + <... $X ...> 55 | - pattern-not: | 56 | `${$X}...` 57 | - pattern-not: | 58 | $X + ... 59 | - focus-metavariable: $X 60 | - label: CLEAN 61 | by-side-effect: true 62 | patterns: 63 | - pattern-either: 64 | - pattern: $A($SOURCE) 65 | - pattern: $SANITIZE. ... .$A($SOURCE) 66 | - pattern: $A. ... .$SANITIZE($SOURCE) 67 | - focus-metavariable: $SOURCE 68 | - metavariable-regex: 69 | metavariable: $A 70 | regex: (?i)(.*valid|.*sanitiz) 71 | pattern-sinks: 72 | - requires: TAINTED and not CONCAT and not CLEAN 73 | patterns: 74 | - focus-metavariable: $X 75 | - pattern-either: 76 | - pattern: | 77 | <$EL href={$X} /> 78 | - pattern: | 79 | React.createElement($EL, {href: $X}) 80 | - pattern-inside: | 81 | $PARAMS = {href: $X}; 82 | ... 83 | React.createElement($EL, $PARAMS); 84 | - metavariable-pattern: 85 | patterns: 86 | - pattern-not-regex: (?i)(button|SecureLink) 87 | metavariable: $EL 88 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/ssti.go: -------------------------------------------------------------------------------- 1 | // LICENSE: Commons Clause License Condition v1.0[LGPL-2.1-only] 2 | // original source: https://github.com/returntocorp/semgrep-rules/blob/5b098c252feec688d243cef046d07597a546c25b/go/template/security/insecure-types.go 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "html/template" 9 | "net/http" 10 | ) 11 | 12 | type User struct { 13 | ID int 14 | Email string 15 | Password string 16 | } 17 | 18 | func match1(w http.ResponseWriter, req *http.Request) { 19 | 20 | var user1 = &User{1, "user@gmail.com", "Sup3rSecr3t123!"} 21 | query := req.URL.Query().Get("query") 22 | // ruleid:go-ssti 23 | var text = fmt.Sprintf(` 24 | 25 | 26 | SSTI 27 | 28 | 29 |

Hello {{ .Email }}

30 |

Search result for %s

31 | 32 | `, query) 33 | tmpl := template.New("hello") 34 | tmpl, err := tmpl.Parse(text) 35 | if err != nil { 36 | fmt.Println(err) 37 | } 38 | tmpl.Execute(w, user1) 39 | } 40 | 41 | func match2(w http.ResponseWriter, req *http.Request) { 42 | 43 | var user1 = &User{1, "user@gmail.com", "Sup3rSecr3t123!"} 44 | if err := req.ParseForm(); err != nil { 45 | fmt.Fprintf(w, "ParseForm() err: %v", err) 46 | return 47 | } 48 | query := req.Form.Get("query") 49 | // ruleid:go-ssti 50 | var text = fmt.Sprintf(` 51 | 52 | 53 | SSTI 54 | 55 | 56 |

Hello {{ .Email }}

57 |

Search result for %s

58 | 59 | `, query) 60 | tmpl := template.New("hello") 61 | tmpl, err := tmpl.Parse(text) 62 | if err != nil { 63 | fmt.Println(err) 64 | } 65 | tmpl.Execute(w, user1) 66 | } 67 | 68 | func no_match(w http.ResponseWriter, req *http.Request) { 69 | 70 | var user1 = &User{1, "user@gmail.com", "Sup3rSecr3t123!"} 71 | query := "constant string" 72 | // ok:go-ssti 73 | var text = fmt.Sprintf(` 74 | 75 | 76 | SSTI 77 | 78 | 79 |

Hello {{ .Email }}

80 |

Search result for %s

81 | 82 | `, query) 83 | tmpl := template.New("hello") 84 | tmpl, err := tmpl.Parse(text) 85 | if err != nil { 86 | fmt.Println(err) 87 | } 88 | tmpl.Execute(w, user1) 89 | } 90 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/ssti.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: go-ssti 3 | patterns: 4 | - pattern-inside: | 5 | import ("html/template") 6 | ... 7 | - pattern: $TEMPLATE = fmt.Sprintf("...", $ARG, ...) 8 | - patterns: 9 | - pattern-either: 10 | - pattern-inside: | 11 | func $FN(..., $REQ *http.Request, ...){ 12 | ... 13 | } 14 | - pattern-inside: | 15 | func $FN(..., $REQ http.Request, ...){ 16 | ... 17 | } 18 | - pattern-inside: | 19 | func(..., $REQ *http.Request, ...){ 20 | ... 21 | } 22 | - patterns: 23 | - pattern-either: 24 | - pattern-inside: | 25 | $ARG := $REQ.URL.Query().Get(...) 26 | ... 27 | $T, $ERR := $TMPL.Parse($TEMPLATE) 28 | - pattern-inside: | 29 | $ARG := $REQ.Form.Get(...) 30 | ... 31 | $T, $ERR := $TMPL.Parse($TEMPLATE) 32 | - pattern-inside: | 33 | $ARG := $REQ.PostForm.Get(...) 34 | ... 35 | $T, $ERR := $TMPL.Parse($TEMPLATE) 36 | message: >- 37 | A server-side template injection occurs when an attacker is able to use native template syntax to inject a malicious payload into a template, which is then executed server-side. When using "html/template" always check that user inputs are validated and sanitized before included within the template. 38 | languages: [go] 39 | severity: ERROR 40 | metadata: 41 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/ssti.yaml 42 | original_source: https://github.com/returntocorp/semgrep-rules/blob/5b098c252feec688d243cef046d07597a546c25b/go/template/security/ssti.yaml 43 | category: security 44 | cwe: 45 | - 'CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine' 46 | references: 47 | - https://www.onsecurity.io/blog/go-ssti-method-research/ 48 | - http://blog.takemyhand.xyz/2020/05/ssti-breaking-gos-template-engine-to.html 49 | technology: 50 | - go 51 | confidence: MEDIUM 52 | subcategory: 53 | - vuln 54 | likelihood: LOW 55 | impact: HIGH 56 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 57 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/starts-with-partial-host-py.py: -------------------------------------------------------------------------------- 1 | # ruleid: starts-with-partial-host-py 2 | my_urI[0].startswith("https://x.y") 3 | 4 | # ruleid: starts-with-partial-host-py 5 | request.url.startswith('https://example.com') 6 | 7 | # ruleid: starts-with-partial-host-py 8 | url.startswith('http://127.0.0.1:') 9 | 10 | # ok: starts-with-partial-host-py 11 | url.startswith("https://ba.na/x") 12 | 13 | # ok: starts-with-partial-host-py 14 | url.startswith("https://") 15 | 16 | # ok: starts-with-partial-host-py 17 | url.startswith("xyz://abc/https://def") -------------------------------------------------------------------------------- /assets/semgrep_rules/services/starts-with-partial-host-py.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: starts-with-partial-host-py 3 | metadata: 4 | author: Ben Caller 5 | confidence: LOW 6 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/starts-with-partial-host-py.yaml 7 | category: security 8 | patterns: 9 | - pattern: $URL.startswith("$PREFIX") 10 | - metavariable-regex: 11 | metavariable: $PREFIX 12 | regex: (?i)^https?://[^/]+$ 13 | - metavariable-regex: 14 | # Avoid false positives where we actually have an origin or hostname 15 | metavariable: $URL 16 | regex: (?i).*ur[li].* 17 | message: | 18 | Add a forward-slash at the end to prevent matching `$PREFIX.e.vil` or `$PREFIX@e.vil`. 19 | Even better, properly parse the URL and match a list of origins/hosts. 20 | languages: [python] 21 | severity: WARNING 22 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/svelte-html-usages.svelte: -------------------------------------------------------------------------------- 1 | 2 | {@html trigger} 3 | 4 | {@html 5 | i18n.replace("a", "z") 6 | } 7 | 8 | 9 | {@html safeTranslate( 10 | i18n, key, {} 11 | )} 12 | 13 | {@html MyIcon} 14 | 15 | {@html serializeJsonLD({})} -------------------------------------------------------------------------------- /assets/semgrep_rules/services/svelte-html-usages.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: svelte-html-usages 3 | metadata: 4 | author: Andrea Brancaleoni 5 | references: 6 | - https://cwe.mitre.org/data/definitions/546 7 | - https://cwe.mitre.org/data/definitions/615 8 | - https://btlr.dev/blog/how-to-find-vulnerabilities-in-code-bad-words 9 | confidence: LOW 10 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/svelte-html-usages.yaml 11 | category: security 12 | message: >- 13 | The code contains new security hotspots (`{@html expression}`) which should be checked manually by a security team member! 14 | severity: INFO 15 | languages: 16 | - generic 17 | paths: 18 | include: 19 | - "*.svelte" 20 | patterns: 21 | - pattern-regex: \{@html\s+[^A-Z] 22 | - pattern-not-regex: \{@html\s+safeTranslate\( 23 | - pattern-not-regex: \{@html\s+sanitize\( 24 | - pattern-not-regex: \{@html\s+serializeJsonLD\( 25 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/svelte-purifyConfig-usage.svelte: -------------------------------------------------------------------------------- 1 | // ruleid: svelte-purifyConfig-usages 2 | purifyConfig(test, 3) 3 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/svelte-purifyConfig-usage.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: svelte-purifyConfig-usages 3 | metadata: 4 | author: Andrea Brancaleoni 5 | references: 6 | - https://cwe.mitre.org/data/definitions/546 7 | - https://cwe.mitre.org/data/definitions/615 8 | - https://btlr.dev/blog/how-to-find-vulnerabilities-in-code-bad-words 9 | confidence: LOW 10 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/svelte-purifyConfig-usage.yaml 11 | category: security 12 | message: >- 13 | The code contains new security hotspots (`purifyConfig`) which should be checked manually by a security team member! 14 | severity: INFO 15 | languages: 16 | - generic 17 | paths: 18 | include: 19 | - "*.svelte" 20 | - "*.ts" 21 | - "*.js" 22 | patterns: 23 | - pattern-regex: purifyConfig\([^)]+\) 24 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/url-constructor-base.js: -------------------------------------------------------------------------------- 1 | // ruleid: url-constructor-base 2 | var unsafe = new URL(variable, "https://brave.com"); 3 | // ruleid: url-constructor-base 4 | var unsafe = new URL(variable + "xyz", constantOrVariable); 5 | // ruleid: url-constructor-base 6 | var unsafe = new URL(`${variable}/xyz`, constantOrVariable); 7 | // ruleid: url-constructor-base 8 | var unsafe = new URL(`/${variable}/xyz`, constantOrVariable); 9 | // ruleid: url-constructor-base 10 | var unsafe = new URL("https://brave.com" + variable, constantOrVariable); 11 | // ruleid: url-constructor-base 12 | var unsafe = new URL("/" + variable, constantOrVariable); 13 | // ruleid: url-constructor-base 14 | var unsafe = new URL("https://" + variable, constantOrVariable); 15 | // ruleid: url-constructor-base 16 | var unsafe = new URL("file" + variable, constantOrVariable); 17 | 18 | 19 | // No base: 20 | 21 | // ok: url-constructor-base 22 | var notUnsafe0 = new URL(variable); 23 | // ok: url-constructor-base 24 | var notUnsafe1 = new URL(variable + "xyz"); 25 | // ok: url-constructor-base 26 | var notUnsafe2 = new URL(`${variable}/xyz`); 27 | 28 | // Unable to start with double forward slashes, double backslashes, https:// or mess with hostname 29 | 30 | // ok: url-constructor-base 31 | var notUnsafe3 = new URL(`/const`, location.origin); 32 | // todook: url-constructor-base 33 | var notUnsafe4 = new URL("?" + variable, constantOrVariable); 34 | // todook: url-constructor-base 35 | var notUnsafe5 = new URL(`/a${variable}/xyz`, "https://brave.com"); 36 | // todook: url-constructor-base 37 | var notUnsafe6 = new URL("https://not.sure/" + variable, "https://brave.com"); 38 | // todook: url-constructor-base 39 | var notUnsafe7 = new URL(`#${variable}`, constantOrVariable); 40 | 41 | // ok: url-constructor-base 42 | var notUnsafe8 = new URL("https://not.sure/" + variable, "https://brave.com"); 43 | if(notUnsafe8.origin !== "https://brave.com") { 44 | throw new Error("X"); 45 | } -------------------------------------------------------------------------------- /assets/semgrep_rules/services/url-constructor-base.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: url-constructor-base 3 | metadata: 4 | author: Ben Caller 5 | confidence: LOW 6 | references: 7 | - https://developer.mozilla.org/en-US/docs/Web/API/URL/URL#parameters 8 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/url-constructor-base.yaml 9 | assignees: | 10 | thypon 11 | kdenhartog 12 | category: security 13 | message: Are you using the `URL(url, base)` constructor as a security control to limit the origin with base `$BASE`? The base is ignored whenever url looks like an absolute URL, e.g. when it begins `protocol://`. `\\\\` or `//x.y`. Verify that the URL's origin is as expected rather than relying on the URL constructor. 14 | severity: INFO 15 | languages: 16 | - ts 17 | - js 18 | patterns: 19 | - pattern: new URL($A, $BASE) 20 | # Exclude constant string 21 | - pattern-not: new URL("...", $BASE) 22 | # Hopefully a sensible check for the correct origin 23 | - pattern-not-inside: | 24 | $VAR = new URL($A, $BASE) 25 | ... 26 | <... $VAR.origin ...> 27 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/var-in-href.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block body %} 4 | 5 | Click 6 | 7 | this 8 | 9 | link 10 | {% endblock %} -------------------------------------------------------------------------------- /assets/semgrep_rules/services/var-in-href.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: var-in-href 3 | message: Detected a template variable used in an anchor tag with the 'href' attribute. This allows a malicious actor to input the 'javascript:' URI and is subject to cross- site scripting (XSS) attacks. If using Flask, use 'url_for()' to safely generate a URL. If using Django, use the 'url' filter to safely generate a URL. If using Mustache, use a URL encoding library, or prepend a slash '/' to the variable for relative links (`href="/{{link}}"`). You may also consider setting the Content Security Policy (CSP) header. 4 | metadata: 5 | cwe: 6 | - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" 7 | owasp: 8 | - A07:2017 - Cross-Site Scripting (XSS) 9 | - A03:2021 - Injection 10 | references: 11 | - https://flask.palletsprojects.com/en/1.1.x/security/#cross-site-scripting-xss#:~:text=javascript:%20URI 12 | - https://docs.djangoproject.com/en/3.1/ref/templates/builtins/#url 13 | - https://github.com/pugjs/pug/issues/2952 14 | - https://content-security-policy.com/ 15 | - https://semgrep.dev/r/generic.html-templates.security.var-in-href.var-in-href 16 | category: security 17 | technology: 18 | - html-templates 19 | confidence: LOW 20 | cwe2022-top25: true 21 | cwe2021-top25: true 22 | subcategory: 23 | - audit 24 | likelihood: LOW 25 | impact: MEDIUM 26 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 27 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/var-in-href.yaml 28 | languages: 29 | - generic 30 | paths: 31 | include: 32 | - "*.html" 33 | - "*.mustache" 34 | - "*.hbs" 35 | severity: WARNING 36 | patterns: 37 | - pattern-inside: 38 | - pattern-either: 39 | - pattern: href = {{ ... }} 40 | - pattern: href = "{{ ... }}" 41 | - pattern: href = '{{ ... }}' 42 | - pattern-not-inside: href = {{ url_for(...) ... }} 43 | - pattern-not-inside: href = "{{ url_for(...) ... }}" 44 | - pattern-not-inside: href = '{{ url_for(...) ... }}' 45 | - pattern-not-inside: href = "/{{ ... }}" 46 | - pattern-not-inside: href = '/{{ ... }}' 47 | # exception for sanititze_url 48 | - pattern-not-regex: '.*\|\s*sanitize_url\s*\}\}.' 49 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/var-in-script-tag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 |
14 | 15 |

{{ this_is_fine }}

16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /assets/semgrep_rules/services/var-in-script-tag.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: var-in-script-tag 3 | message: Detected a template variable used in a script tag. Although template variables are HTML escaped, HTML escaping does not always prevent cross-site scripting (XSS) attacks when used directly in JavaScript. If you need this data on the rendered page, consider placing it in the HTML portion (outside of a script tag). Alternatively, use a JavaScript-specific encoder, such as the one available in OWASP ESAPI. For Django, you may also consider using the 'json_script' template tag and retrieving the data in your script by using the element ID (e.g., `document.getElementById`). Use the `tojson` filter for JSON. 4 | metadata: 5 | cwe: 6 | - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" 7 | owasp: 8 | - A07:2017 - Cross-Site Scripting (XSS) 9 | - A03:2021 - Injection 10 | references: 11 | - https://adamj.eu/tech/2020/02/18/safely-including-data-for-javascript-in-a-django-template/?utm_campaign=Django%2BNewsletter&utm_medium=rss&utm_source=Django_Newsletter_12A 12 | - https://www.veracode.com/blog/secure-development/nodejs-template-engines-why-default-encoders-are-not-enough 13 | - https://github.com/ESAPI/owasp-esapi-js 14 | - https://semgrep.dev/r/generic.html-templates.security.var-in-script-tag.var-in-script-tag 15 | - https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.tojson 16 | category: security 17 | technology: 18 | - html-templates 19 | confidence: LOW 20 | cwe2022-top25: true 21 | cwe2021-top25: true 22 | subcategory: 23 | - audit 24 | likelihood: LOW 25 | impact: MEDIUM 26 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 27 | source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/services/var-in-script-tag.yaml 28 | languages: 29 | - generic 30 | paths: 31 | include: 32 | - "*.mustache" 33 | - "*.hbs" 34 | - "*.html" 35 | severity: WARNING 36 | patterns: 37 | - pattern-inside: 38 | - pattern-not-inside: 30 | 31 | -------------------------------------------------------------------------------- /t3sts/dtd/theme=🌚 dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /t3sts/pipaudit/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "actiontest" 3 | version = "0.0.1" 4 | description = "Test of pip-audit" 5 | readme = "README.md" 6 | requires-python = ">=3.8" 7 | classifiers = [ 8 | "Programming Language :: Python :: 3", 9 | ] 10 | dependencies = [ 11 | "fastapi==0.104.1", "pydantic==1.10.13", 12 | "shortuuid==1.0.11", 13 | "Jinja2>=3.1.2", "transformers==4.36.2" 14 | ] 15 | 16 | [project.optional-dependencies] 17 | dev = ["black==23.7.0", "pylint==2.17.5", "polib==1.2.0"] 18 | test = [ "pytest", "pytest-asyncio", "requests", "httpx", "pytest_httpx", "time-machine", "aioresponses" ] -------------------------------------------------------------------------------- /t3sts/pipaudit/requirements-flask.txt: -------------------------------------------------------------------------------- 1 | # This is probably ok 2 | boto3 3 | 4 | # This has a vulnerability 5 | flask==2.3.1 6 | salt==2016.3 -------------------------------------------------------------------------------- /t3sts/tf/example.tf: -------------------------------------------------------------------------------- 1 | resource "aws_vpc" "main" { 2 | cidr_block = "10.0.0.0/16" 3 | } 4 | 5 | resource "aws_security_group" "example" { 6 | name = "example" 7 | description = "Very complex Security Group for testing" 8 | vpc_id = aws_vpc.main.id 9 | 10 | tags = { 11 | "Name" = "example" 12 | } 13 | } 14 | 15 | resource "aws_security_group_rule" "example_rule" { 16 | security_group_id = aws_security_group.example.id 17 | description = "Allow all traffic from Internet which will make tfsec throw an error" 18 | 19 | type = "ingress" 20 | from_port = "0" 21 | to_port = "65535" 22 | protocol = "tcp" 23 | 24 | cidr_blocks = ["0.0.0.0/0"] 25 | ipv6_cidr_blocks = ["::/0"] 26 | } 27 | 28 | 29 | resource "azurerm_resource_group" "example" { 30 | name = "example-resources" 31 | location = "West Europe" 32 | } 33 | 34 | resource "azurerm_managed_disk" "example" { 35 | name = "acctestmd" 36 | location = "West Europe" 37 | resource_group_name = azurerm_resource_group.example.name 38 | storage_account_type = "Standard_LRS" 39 | create_option = "Empty" 40 | disk_size_gb = "1" 41 | 42 | encryption_settings { 43 | enabled = false 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /t3sts/tf/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "~> 4.55.0" 8 | } 9 | 10 | azurerm = { 11 | source = "hashicorp/azurerm" 12 | version = "~> 3.44.0" 13 | } 14 | } 15 | } 16 | 17 | provider "aws" { 18 | region = "eu-west-1" 19 | } 20 | 21 | provider "azurerm" { 22 | features {} 23 | } 24 | --------------------------------------------------------------------------------