├── .gitallowed ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── features-request-------.md │ └── issue-with-ruleset------.md ├── renovate.json ├── workflows │ ├── loop.yml │ ├── opengrep-brave-core.yml │ ├── lint.yml │ ├── full-loop.yml │ ├── opengrep-self-test.yml │ └── update-opengrep-version.yml └── dependabot.yml ├── requirements.txt ├── Gemfile ├── socket.yml ├── assets ├── opengrep_rules │ ├── services │ │ ├── svelte-purifyConfig-usage.svelte │ │ ├── internal-digest-call.py │ │ ├── not-using-hasownproperty-proto-access.js │ │ ├── no-backticks-in-js-handlers.html │ │ ├── detected-aws-access-key-id-value.js │ │ ├── var-in-href.html │ │ ├── svelte-html-usages.svelte │ │ ├── missing-noopener-window-open.html │ │ ├── pip-extra-index-url.Makefile │ │ ├── path-travesal-by-string-interpolation.ts │ │ ├── missing-noopener-window-open-native.js │ │ ├── http-parse-multipart-dos.go │ │ ├── starts-with-partial-host-py.py │ │ ├── nodejs-insecure-url-parse.js │ │ ├── io-readall-dos.go │ │ ├── internal-digest-call.yaml │ │ ├── docker-compose-no-new-privileges.test.yaml │ │ ├── var-in-script-tag.html │ │ ├── jinja-safe-usages.html │ │ ├── find-links-without-no-index.txt │ │ ├── not-using-hasownproperty-proto-access.yaml │ │ ├── nodejs-insecure-url-parse.yaml │ │ ├── no-backticks-in-js-handlers.yaml │ │ ├── svelte-purifyConfig-usage.yaml │ │ ├── starts-with-partial-host-py.yaml │ │ ├── find-links-without-no-index.yaml │ │ ├── pip-extra-index-url.yaml │ │ ├── svelte-html-usages.yaml │ │ ├── io-readall-dos.yaml │ │ ├── insecure-types.go │ │ ├── url-constructor-base.yaml │ │ ├── http-parse-multipart-dos.yaml │ │ ├── activerecord-sanitize-sql-noop.yaml │ │ ├── path-travesal-by-string-interpolation.yaml │ │ ├── activerecord-sanitize-sql-noop.rb │ │ ├── jinja-safe-usages.yaml │ │ ├── detected-aws-access-key-id-value.yaml │ │ ├── insecure-types.yaml │ │ ├── url-constructor-base.js │ │ ├── react-href-var.tsx │ │ ├── missing-noopener-window-open-native.yaml │ │ ├── docker-compose-no-new-privileges.yaml │ │ ├── ssti.go │ │ ├── brave-third-party-action-not-pinned-to-commit-sha.yaml │ │ ├── missing-integrity.yaml │ │ ├── missing-integrity.html │ │ ├── var-in-script-tag.yaml │ │ ├── var-in-href.yaml │ │ ├── github-workflow-missing-permissions.yaml │ │ ├── ssti.yaml │ │ └── brave-third-party-action-not-pinned-to-commit-sha.test.yaml │ ├── blocklist-internal.txt │ ├── blocklist-brave-core.txt │ ├── client │ │ ├── cast-signed-to-unsigned.c │ │ ├── check_includes.gni │ │ ├── licensing.txt │ │ ├── android-profile-original.java │ │ ├── in-process-browser-test.cc │ │ ├── const_cast.cpp │ │ ├── web-contents-user-data.cc │ │ ├── ui_build_gn.gn │ │ ├── android-profile-static.java │ │ ├── get-visible-entry.cc │ │ ├── sources_gni.gni │ │ ├── browser-dependency-inversion.cc │ │ ├── privacy.en.md │ │ ├── privacy.md │ │ ├── java_sources_gni.gni │ │ ├── unsafejs-in-cpp.cc │ │ ├── brave-isolated-world.cpp │ │ ├── brave-execute-script.cpp │ │ ├── interesting-api-calls.cc │ │ ├── android-profile-original.yaml │ │ ├── unsafe-cpp-constructs.cpp │ │ ├── licensing.yaml │ │ ├── const_cast.yaml │ │ ├── in-process-browser-test.yaml │ │ ├── refcounted-usage.cpp │ │ ├── check_includes.yaml │ │ ├── glide-library.yaml │ │ ├── android-profile-static.yaml │ │ ├── java_sources_gni.yaml │ │ ├── unsafe-cpp-constructs.yaml │ │ ├── get-visible-entry.yaml │ │ ├── reinterpret_cast.cpp │ │ ├── brave-isolated-world.yaml │ │ ├── sources_gni.yaml │ │ ├── unsafejs-in-cpp.yaml │ │ ├── brave-execute-script-ios.yaml │ │ ├── brave-execute-script.yaml │ │ ├── brave-execute-script-ios.swift │ │ ├── web-contents-user-data.yaml │ │ ├── ui_build_gn.yaml │ │ ├── refcounted-usage.yaml │ │ ├── reinterpret_cast.yaml │ │ ├── licensing.html │ │ ├── chromium-insecure-gurl.cpp │ │ ├── crash-vs-dump-without-crashing.yaml │ │ ├── android-resolve-intent.yaml │ │ ├── cast-signed-to-unsigned.yaml │ │ ├── chromium-insecure-gurl.yaml │ │ ├── glide-library.java │ │ ├── dangling-pointer-trait.yaml │ │ ├── mismatched-memory-management-cpp.cpp │ │ ├── interesting-api-calls.yaml │ │ ├── android-resolve-intent.kt │ │ ├── web-ui-origin-encoding.yaml │ │ ├── check-vs-dcheck.yaml │ │ ├── android-resolve-intent.java │ │ ├── crash-vs-dump-without-crashing.cpp │ │ ├── dangling-pointer-trait.cc │ │ ├── browser-dependency-inversion.yaml │ │ ├── typos.cc │ │ ├── chromium-uaf.yaml │ │ ├── check-vs-dcheck.cpp │ │ ├── web-ui-origin-encoding.ts │ │ ├── chromium-uaf.cpp │ │ └── brave-missing-break-in-switch.yaml │ ├── blocklist-static.txt │ ├── blocklist-bsg.txt │ └── generate-compound.rb ├── debug.sh ├── xmllint.sh ├── tfsec.sh ├── dtd │ └── blocklist.txt ├── tfsec.jq ├── reviewdog.sh ├── npm-audit.py └── cleaner.rb ├── t3sts ├── pipaudit │ ├── requirements-flask.txt │ └── pyproject.toml ├── brakeman │ └── app │ │ └── models │ │ └── thing.rb ├── tf │ ├── main.tf │ └── example.tf └── dtd │ ├── theme=🌚 dark.svg │ ├── $pac-color.svg │ └── script.svg ├── run.js ├── SECURITY.md ├── Gemfile.lock ├── actions ├── dependabot-auto-dismiss │ ├── dismiss.txt │ ├── action.cjs │ └── action.yml ├── renovate-sanity-check │ ├── action.cjs │ └── action.yml ├── add-maintainer-custom-property │ ├── action.cjs │ └── action.yml ├── add-runtime-custom-property │ ├── action.cjs │ └── action.yml ├── dependabot-nudge │ ├── action.cjs │ └── action.yml ├── check-new-repos │ ├── action.yml │ └── action.cjs ├── older-than-2y │ ├── action.yml │ └── action.cjs └── opengrep-compare │ └── action.yml ├── src ├── getConfig.js ├── getMaintainers.js ├── getProperties.js ├── kubeGetRepositories.js ├── steps │ ├── assigneeRemoved.js │ ├── commentsNumber.js │ ├── cleanupComments.js │ ├── assigneesAfter.js │ ├── hotwords.js │ └── unverifiedCommits.js ├── pullRequestChangedFiles.js └── updateRuntimeProperty.js ├── package.json └── README.md /.gitallowed: -------------------------------------------------------------------------------- 1 | AKIABBBBBBBBBBBBBB1B 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @brave/sec-team 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pip-audit~=2.9.0 2 | setuptools~=79.0.1 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gem 'brakeman', '6.2.2' 3 | -------------------------------------------------------------------------------- /socket.yml: -------------------------------------------------------------------------------- 1 | # top level version field is required 2 | version: 2 3 | 4 | projectIgnorePaths: 5 | - "t3sts" 6 | -------------------------------------------------------------------------------- /assets/opengrep_rules/services/svelte-purifyConfig-usage.svelte: -------------------------------------------------------------------------------- 1 | // ruleid: svelte-purifyConfig-usages 2 | purifyConfig(test, 3) 3 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /assets/opengrep_rules/blocklist-internal.txt: -------------------------------------------------------------------------------- 1 | https://semgrep.dev/r/typescript.react.portability.i18next.jsx-label-not-i18n.jsx-label-not-i18n 2 | -------------------------------------------------------------------------------- /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/opengrep_rules/blocklist-brave-core.txt: -------------------------------------------------------------------------------- 1 | https://semgrep.dev/r/gitlab.bandit.B404 2 | https://semgrep.dev/r/python.lang.security.audit.eval-detected.eval-detected -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /assets/opengrep_rules/blocklist-static.txt: -------------------------------------------------------------------------------- 1 | https://github.com/brave/security-action/blob/main/assets/opengrep_rules/services/var-in-href.yaml 2 | https://github.com/brave/security-action/blob/main/assets/opengrep_rules/services/var-in-script-tag.yaml 3 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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 | -------------------------------------------------------------------------------- /run.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { parseArgs } from 'node:util' 4 | const [,, _module, ...args] = process.argv 5 | 6 | const run = (await import(_module)).default 7 | const innerArgs = parseArgs({ args, strict: false }) 8 | 9 | console.log(await run(innerArgs.values)) 10 | -------------------------------------------------------------------------------- /.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/opengrep_rules/" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /t3sts/brakeman/app/models/thing.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "active_record" 4 | 5 | class Thing < ActiveRecord::Base 6 | def inject(params) 7 | Thing.first.where((((("username = '" + params[:user][:name]) + "' AND password = '") + params[:user][:password]) + "'")) 8 | end 9 | end -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /assets/opengrep_rules/client/ui_build_gn.gn: -------------------------------------------------------------------------------- 1 | sources = [ 2 | # ruleid:ui_build_gn 3 | "//brave/browser/brave_browser_features.cc", 4 | # ruleid:ui_build_gn 5 | "brave_browser_features.h", 6 | ] 7 | 8 | deps = [ 9 | # ok:ui_build_gn 10 | "//base", 11 | # ok:ui_build_gn 12 | "//brave/app:brave_generated_resources_grit", 13 | ] 14 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/xmllint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 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 | 7 | # Only check SVGs 8 | [[ "$1" == *".svg" ]] || exit 0 9 | 10 | xmllint --dtdvalid $SCRIPTPATH/dtd/svg11-secure-flat.dtd --noout "$1" 2>&1 | grep -v '^Document' 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/opengrep_rules/client/sources_gni.gni: -------------------------------------------------------------------------------- 1 | brave_chrome_browser_sources = [ 2 | # ruleid:sources_gni 3 | "//brave/browser/brave_browser_features.cc", 4 | # ruleid:sources_gni 5 | "brave_browser_features.h", 6 | ] 7 | 8 | brave_chrome_browser_deps = [ 9 | # ok:sources_gni 10 | "//base", 11 | # ok:sources_gni 12 | "//brave/app:brave_generated_resources_grit", 13 | ] 14 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_rules/client/java_sources_gni.gni: -------------------------------------------------------------------------------- 1 | brave_java_sources = [ 2 | # ruleid:java_sources_gni 3 | "../../brave/android/java/org/chromium/chrome/browser/BraveAdFreeCalloutDialogFragment.java", 4 | # ruleid:java_sources_gni 5 | "BraveAppHooks.java", 6 | ] 7 | 8 | # ruleid:java_sources_gni 9 | brave_java_sources += brave_ads_java_sources 10 | 11 | # ok:java_sources_gni 12 | brave_java_deps += brave_ads_java_deps 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/tfsec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 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 | 7 | ARGS="" 8 | if ls *.tfvars 2> /dev/null; then 9 | for TFVARS in *.tfvars; do 10 | ARGS+="--tfvars-file $TFVARS " 11 | done 12 | fi 13 | 14 | tfsec "$1" $ARGS --format=json | jq -r -f "$SCRIPTPATH/tfsec.jq" 15 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_rules/services/missing-noopener-window-open.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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 | author: Andrea Brancaleoni 10 | category: security 11 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/services/internal-digest-call.yaml 12 | assignees: | 13 | stoletheminerals 14 | cdesouza-chromium 15 | bridiver 16 | -------------------------------------------------------------------------------- /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" ] -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/getConfig.js: -------------------------------------------------------------------------------- 1 | export default async function getConfig ({ owner, repo, path, github, githubToken, debug = false }) { 2 | if (!github && githubToken) { 3 | const { Octokit } = await import('@octokit/core') 4 | 5 | github = new Octokit({ auth: githubToken }) 6 | } 7 | 8 | try { 9 | const { data } = await github.rest.repos.getContent({ 10 | owner, 11 | repo, 12 | path 13 | }) 14 | const fileContent = Buffer.from(data.content, 'base64').toString('utf8') 15 | if (debug) console.log(fileContent) 16 | return JSON.parse(fileContent) 17 | } catch (err) { 18 | if (debug) console.log(err) 19 | return {} 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /t3sts/dtd/theme=🌚 dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/opengrep_rules/client/const_cast.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: const_cast 3 | metadata: 4 | author: Brian Johnson 5 | category: correctness 6 | references: 7 | - https://chromium.googlesource.com/chromium/src/+/main/styleguide/c++/c++-dos-and-donts.md#use-correctly 8 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/client/const_cast.yaml 9 | assignees: | 10 | bridiver 11 | cdesouza-chromium 12 | languages: [cpp] 13 | message: Avoid const_cast to remove const, except when implementing non-const getters in terms of const getters. 14 | severity: WARNING 15 | patterns: 16 | - pattern: const_cast<$T>($ARG) 17 | -------------------------------------------------------------------------------- /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 && message.length > 0) { await sendSlackMessage({ debug, username: 'renovate-sanity-check', message, color: 'yellow', channel: '#secops-hotspots', token: inputs.slack_token }) } 13 | } 14 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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/opengrep_rules/generate-compound.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | SEMGREP_VERSION = `opengrep --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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "bugs": { 4 | "url": "https://github.com/brave/security-action/issues", 5 | "email": "security@brave.com" 6 | }, 7 | "scripts": { 8 | "lint": "standard --ignore assets/opengrep_rules", 9 | "lint-fix": "standard --fix --ignore assets/opengrep_rules", 10 | "lint-rules": "node src/checkRuleMetadata.js", 11 | "test": "find src -name '*.test.js' -type f -exec node {} \\;" 12 | }, 13 | "dependencies": { 14 | "@octokit/core": "^6.1.4", 15 | "@slack/web-api": "^7.0.0", 16 | "@tryfabric/mack": "^1.2.1", 17 | "glob": "^12.0.0", 18 | "js-yaml": "^4.1.1", 19 | "markdown-to-txt": "^2.0.1" 20 | }, 21 | "devDependencies": { 22 | "standard": "17.1.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /assets/opengrep_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 | author: Andrea Brancaleoni 16 | category: security 17 | references: 18 | - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty 19 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/services/not-using-hasownproperty-proto-access.yaml 20 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_rules/client/java_sources_gni.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: java_sources_gni 3 | metadata: 4 | author: Brian Johnson 5 | category: correctness 6 | references: 7 | - https://github.com/brave/brave-core/blob/master/docs/gni_sources.md 8 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/client/java_sources_gni.yaml 9 | assignees: | 10 | brave/java-sources-gni-reviewers 11 | languages: [generic] 12 | severity: ERROR 13 | paths: 14 | include: 15 | - "*java*_sources*.gni" 16 | message: > 17 | New source files should not be added to sources.gni. Please see 18 | https://github.com/brave/brave-core/blob/master/docs/gni_sources.md for 19 | details 20 | pattern-either: 21 | - pattern-regex: .*\.java 22 | - pattern-regex: .*_sources \+= .* 23 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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/opengrep_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/tfsec.jq: -------------------------------------------------------------------------------- 1 | # Convert TFsec JSON output to Reviewdog Diagnostic Format (rdjson) 2 | # https://github.com/reviewdog/reviewdog/blob/f577bd4b56e5973796eb375b4205e89bce214bd9/proto/rdf/reviewdog.proto 3 | { 4 | source: { 5 | name: "tfsec", 6 | url: "https://github.com/aquasecurity/tfsec" 7 | }, 8 | diagnostics: (.results // {}) | map({ 9 | message: .description, 10 | code: { 11 | value: .rule_id, 12 | url: .links[0], 13 | } , 14 | location: { 15 | path: .location.filename, 16 | range: { 17 | start: { 18 | line: .location.start_line, 19 | }, 20 | } 21 | }, 22 | severity: (if .severity | startswith("HIGH") then 23 | "E" 24 | elif .severity | startswith("MEDIUM") then 25 | "W" 26 | else 27 | "I" 28 | end), 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /.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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 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 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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 | -------------------------------------------------------------------------------- /t3sts/dtd/$pac-color.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_rules/client/sources_gni.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: sources_gni 3 | metadata: 4 | author: Brian Johnson 5 | category: correctness 6 | references: 7 | - https://github.com/brave/brave-core/blob/master/docs/gni_sources.md 8 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/client/sources_gni.yaml 9 | assignees: | 10 | brave/sources-gni-reviewers 11 | languages: [generic] 12 | severity: ERROR 13 | paths: 14 | include: 15 | - "*_sources.gni" 16 | - "sources.gni" 17 | - "sources_gni.gni" 18 | message: > 19 | New source files should not be added to sources.gni. Please see 20 | https://github.com/brave/brave-core/blob/master/docs/gni_sources.md for 21 | details 22 | pattern-either: 23 | - pattern-regex: .*\.h 24 | - pattern-regex: .*\.cc 25 | - pattern-regex: .*\.mm 26 | - pattern-regex: .*\.cpp 27 | - pattern-regex: .*\.hpp 28 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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 | -------------------------------------------------------------------------------- /.github/workflows/opengrep-brave-core.yml: -------------------------------------------------------------------------------- 1 | name: Opengrep Brave Core Check 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | # Run weekly on Monday at 9am UTC 6 | - cron: '0 9 * * 1' 7 | pull_request: 8 | types: [opened, synchronize, reopened] 9 | paths: 10 | - 'assets/opengrep_rules/**' 11 | 12 | permissions: 13 | contents: read 14 | pull-requests: write 15 | 16 | jobs: 17 | opengrep-brave-core: 18 | name: Check Brave Core with Opengrep Rules 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout security-action 22 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 23 | with: 24 | fetch-depth: 0 25 | 26 | - name: Fetch base branch 27 | run: git fetch origin main 28 | 29 | - name: Run Opengrep on Brave Core 30 | id: opengrep-scan 31 | uses: ./actions/opengrep-compare 32 | with: 33 | github_token: ${{ secrets.GITHUB_TOKEN }} 34 | target_repo: brave/brave-core 35 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_rules/client/ui_build_gn.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: ui_build_gn 3 | metadata: 4 | author: Brian Johnson 5 | category: correctness 6 | references: 7 | - https://github.com/brave/brave-core/blob/master/docs/gni_sources.md#circular-dependencies-circular-dependencies 8 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/client/ui_build_gn.yaml 9 | assignees: | 10 | brave/ui-build-gn-reviewers 11 | languages: [generic] 12 | severity: ERROR 13 | paths: 14 | include: 15 | - "browser/ui/BUILD.gn" 16 | # For testing only 17 | - "ui_build_gn.gn" 18 | message: > 19 | brave/browser/ui/BUILD.gn should not have new source files added to it. see 20 | https://github.com/brave/brave-core/blob/master/docs/gni_sources.md#circular-dependencies-circular-dependencies 21 | pattern-either: 22 | - pattern-regex: .*\.h 23 | - pattern-regex: .*\.cc 24 | - pattern-regex: .*\.mm 25 | - pattern-regex: .*\.cpp 26 | - pattern-regex: .*\.hpp 27 | -------------------------------------------------------------------------------- /assets/opengrep_rules/client/refcounted-usage.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: refcounted-usage 3 | metadata: 4 | author: Artem Chaikin 5 | category: security 6 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/client/refcounted-usage.yaml 7 | assignees: | 8 | stoletheminerals 9 | cdesouza-chromium 10 | bridiver 11 | pattern-either: 12 | - pattern: base::RefCounted<...> 13 | - pattern: base::RefCountedThreadSafe<...> 14 | - pattern: base::RefCountedData<...> 15 | 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." 16 | languages: 17 | - generic 18 | paths: 19 | include: 20 | - "*.c" 21 | - "*.cpp" 22 | - "*.cc" 23 | - "*.h" 24 | - "*.hh" 25 | - "*.hcc" 26 | - "*.mm" 27 | severity: WARNING 28 | -------------------------------------------------------------------------------- /assets/opengrep_rules/client/reinterpret_cast.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: reinterpret_cast 3 | metadata: 4 | author: Artem Chaikin 5 | category: security 6 | references: 7 | - https://chromium.googlesource.com/chromium/src/+/main/docs/unsafe_buffers.md#Avoid-reinterpret_cast 8 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/client/reinterpret_cast.yaml 9 | assignees: | 10 | stoletheminerals 11 | thypon 12 | cdesouza-chromium 13 | languages: [cpp] 14 | 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." 15 | severity: WARNING 16 | patterns: 17 | - pattern: reinterpret_cast<$T>($ARG) 18 | - metavariable-regex: 19 | metavariable: $T 20 | regex: ^(.*int.*|.*double.*|.*float.*|.*char.*|.*size_t.*)$ # this probably needs to be tweaked 21 | -------------------------------------------------------------------------------- /assets/opengrep_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 | -------------------------------------------------------------------------------- /t3sts/dtd/script.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 30 | 31 | -------------------------------------------------------------------------------- /assets/opengrep_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 | -------------------------------------------------------------------------------- /src/getMaintainers.js: -------------------------------------------------------------------------------- 1 | export default async function getMaintainers ({ 2 | org, 3 | githubToken = null, 4 | github = null 5 | }) { 6 | if (!github && githubToken) { 7 | const { Octokit } = await import('octokit') 8 | 9 | github = new Octokit({ auth: githubToken }) 10 | } 11 | 12 | if (!github && !githubToken) { 13 | throw new Error('either githubToken or github is required!') 14 | } 15 | 16 | const properties = await github.paginate('GET /orgs/{org}/properties/values', { 17 | org, 18 | headers: { 19 | 'X-GitHub-Api-Version': '2022-11-28' 20 | } 21 | }) 22 | 23 | // hash out of properties array 24 | const props = {} 25 | for (const prop of properties) { 26 | props[prop.repository_name] = prop.properties.reduce((obj, item) => { 27 | obj[item.property_name] = item.value 28 | return obj 29 | }, {}) 30 | } 31 | 32 | let maintainers = '' 33 | 34 | for (const repo of Object.keys(props).sort()) { 35 | if (props[repo].maintainers) { 36 | maintainers += `- https://github.com/${org}/${repo} maintainers: ${props[repo].maintainers.split(',').join(', ')}\n` 37 | } 38 | } 39 | 40 | return maintainers 41 | } 42 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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/opengrep_rules/client/crash-vs-dump-without-crashing.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: chromium-crash-vs-dump-without-crashing 3 | metadata: 4 | author: Andrea Brancaleoni 5 | references: 6 | - https://chromium.googlesource.com/chromium/src/+/main/base/check.h#324 7 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/client/crash-vs-dump-without-crashing.yaml 8 | assignees: | 9 | thypon 10 | cdesouza-chromium 11 | category: correctness 12 | languages: 13 | - cpp 14 | - c 15 | message: | 16 | Consider using DUMP_WILL_BE_CHECK() instead of base::debug::DumpWithoutCrashing() 17 | when the intent is to eventually become a CHECK, as it better communicates this 18 | intent and provides better debugging information. However, DumpWithoutCrashing() 19 | is appropriate for debugging issues that may not become CHECKs. 20 | severity: INFO 21 | patterns: 22 | - pattern: base::debug::DumpWithoutCrashing() 23 | - pattern-not-inside: | 24 | void $FUNC(...) { 25 | ... 26 | base::debug::DumpWithoutCrashing($LOCATION, $INTERVAL); 27 | ... 28 | } 29 | -------------------------------------------------------------------------------- /.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 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | node-version: [18.x] 19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 20 | steps: 21 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | cache: 'npm' 27 | - run: npm ci 28 | - run: npm run lint 29 | - run: npm run lint-rules 30 | - run: npm test 31 | - if: ${{ github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]' }} 32 | run: npm audit 33 | -------------------------------------------------------------------------------- /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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 17 | with: 18 | node-version: '24.x' 19 | - id: npm 20 | run: cd ${{ github.action_path }}/../..; npm ci 21 | shell: bash 22 | - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 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'}) -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/getProperties.js: -------------------------------------------------------------------------------- 1 | export default async function getProperties ({ owner, repo, github, githubToken, debug = false, prefix = '' }) { 2 | if (!github && githubToken) { 3 | const { Octokit } = await import('@octokit/core') 4 | 5 | github = new Octokit({ auth: githubToken }) 6 | } 7 | 8 | try { 9 | const properties = await github.request('GET /repos/{owner}/{repo}/properties/values', { 10 | owner, 11 | repo, 12 | headers: { 13 | 'X-GitHub-Api-Version': '2022-11-28' 14 | } 15 | }) 16 | if (debug) console.log(properties) 17 | 18 | const output = {} 19 | 20 | // copy properties not starting with prefix to output 21 | properties.data.forEach(c => { 22 | if (!c.property_name.startsWith(prefix)) { 23 | output[c.property_name] = c.value 24 | } 25 | }) 26 | 27 | // copy properties starting with prefix to output 28 | if (prefix) { 29 | properties.data.forEach(c => { 30 | if (c.property_name.startsWith(prefix)) { 31 | const name = c.property_name.substring(prefix.length) 32 | output[name] = c.value 33 | } 34 | }) 35 | } 36 | 37 | return output 38 | } catch (err) { 39 | console.log(err) 40 | return {} 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/dependabot-2.0.json 2 | version: 2 3 | updates: 4 | - package-ecosystem: "bundler" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | open-pull-requests-limit: 0 9 | groups: 10 | security-updates: 11 | applies-to: security-updates 12 | patterns: 13 | - "*" 14 | 15 | - package-ecosystem: "github-actions" 16 | directory: "/" 17 | schedule: 18 | interval: "daily" 19 | open-pull-requests-limit: 0 20 | groups: 21 | security-updates: 22 | applies-to: security-updates 23 | patterns: 24 | - "*" 25 | 26 | - package-ecosystem: "npm" 27 | directory: "/" 28 | schedule: 29 | interval: "daily" 30 | open-pull-requests-limit: 0 31 | groups: 32 | security-updates: 33 | applies-to: security-updates 34 | patterns: 35 | - "*" 36 | 37 | - package-ecosystem: "pip" 38 | directory: "/" 39 | schedule: 40 | interval: "daily" 41 | exclude-paths: 42 | - "t3sts/**" 43 | open-pull-requests-limit: 0 44 | groups: 45 | security-updates: 46 | applies-to: security-updates 47 | patterns: 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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 17 | with: 18 | node-version: '24.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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 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/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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 17 | with: 18 | node-version: '24.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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 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-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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 20 | with: 21 | node-version: '24.x' 22 | - id: npm 23 | run: cd ${{ github.action_path }}/../..; npm ci 24 | shell: bash 25 | - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 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 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_rules/services/activerecord-sanitize-sql-noop.yaml 28 | category: security 29 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_rules/client/cast-signed-to-unsigned.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: cast-signed-to-unsigned 3 | metadata: 4 | author: Andrea Brancaleoni 5 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/client/cast-signed-to-unsigned.yaml 6 | assignees: | 7 | fmarier 8 | bridiver 9 | category: security 10 | patterns: 11 | - pattern: ($CAST)($TYPE $X) 12 | - metavariable-regex: 13 | metavariable: $CAST 14 | regex: ^(u.*)$ 15 | - metavariable-regex: 16 | metavariable: $TYPE 17 | regex: ^([^u].*)$ 18 | message: | 19 | Semgrep found a cast from $TYPE (signed) to $CAST. 20 | 21 | For arithmetic use `base::CheckAdd(value1, value2).AssignIfValid(&result)`. 22 | In case casting the value is required, consider using `base::checked_cast`, 23 | or if your want to fail without a check `IsValueInRangeForNumericType`. 24 | 25 | References: 26 | 27 | - https://chromium.googlesource.com/chromium/src/+/main/docs/security/integer-semantics.md#be-aware-of-the-subtleties-of-integer-types 28 | - https://chromium.googlesource.com/chromium/src/+/main/base/numerics/README.md 29 | - https://google.github.io/styleguide/cppguide.html#Casting 30 | - https://chromium.googlesource.com/chromium/src/+/main/styleguide/c++/c++.md 31 | languages: [cpp, c] 32 | severity: WARNING 33 | -------------------------------------------------------------------------------- /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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 19 | with: 20 | node-version: '24.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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 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/opengrep_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/opengrep_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 | -------------------------------------------------------------------------------- /src/kubeGetRepositories.js: -------------------------------------------------------------------------------- 1 | export default async function kubeGetRepositories ({ 2 | debug = false, 3 | orgFilter, 4 | directory 5 | }) { 6 | if (!directory) { 7 | throw new Error('directory is required!') 8 | } 9 | 10 | if (!orgFilter) { 11 | orgFilter = /.*/ 12 | } 13 | 14 | if (typeof orgFilter === 'string') { 15 | orgFilter = new RegExp(orgFilter) 16 | } 17 | 18 | debug = debug === 'true' || debug === true 19 | 20 | const fs = await import('fs') 21 | const yaml = await import('js-yaml') 22 | const glob = await import('glob') 23 | 24 | const files = glob.sync(`${directory}/**/*.yaml`) 25 | if (debug) { console.log(`files: ${files.length}`) } 26 | const repos = [] 27 | for (const file of files) { 28 | const content = fs.readFileSync(file, 'utf8') 29 | yaml.loadAll(content, (doc) => { 30 | if (doc && doc.kind === 'GitRepository' && doc.spec.url) { 31 | repos.push(doc.spec.url) 32 | } 33 | }) 34 | } 35 | 36 | const uniqueRepos = [...new Set(repos)].sort().map((url) => { 37 | // url in the format of ssh://git@github.com/example-org/example-ops 38 | // get the last two elements and return an object with organization and name 39 | const parts = url.split('/') 40 | const name = parts.pop() 41 | const organization = parts.pop() 42 | 43 | return { org: organization, name } 44 | }) 45 | 46 | return uniqueRepos.filter(r => orgFilter.test(r.org)) 47 | } 48 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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 | mode: taint 20 | pattern-sources: 21 | - label: TAINTED 22 | patterns: 23 | - pattern-either: 24 | - pattern: $VAR 25 | - metavariable-pattern: 26 | metavariable: $VAR 27 | patterns: 28 | - pattern-not-regex: ^(env|process|config)\. 29 | pattern-sinks: 30 | - requires: TAINTED 31 | patterns: 32 | - pattern: fetch($URL, ...) 33 | - focus-metavariable: $URL 34 | - metavariable-pattern: 35 | metavariable: $URL 36 | patterns: 37 | - pattern-regex: (`.[^#?]+\$) 38 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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 | author: Andrea Brancaleoni 6 | cwe: 7 | - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" 8 | owasp: 9 | - A07:2017 - Cross-Site Scripting (XSS) 10 | - A03:2021 - Injection 11 | references: 12 | - https://flask.palletsprojects.com/en/1.1.x/security/#cross-site-scripting-xss 13 | category: security 14 | technology: 15 | - flask 16 | cwe2022-top25: true 17 | cwe2021-top25: true 18 | subcategory: 19 | - audit 20 | likelihood: LOW 21 | impact: MEDIUM 22 | confidence: LOW 23 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 24 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/services/jinja-safe-usages.yaml 25 | languages: 26 | - regex 27 | paths: 28 | include: 29 | - "*.html" 30 | severity: WARNING 31 | patterns: 32 | - pattern-regex: "{{.*?\\|\\s*safe(\\s*}})?" 33 | - pattern-not-regex: "{{.*?clean\\s*\\|\\s*safe(\\s*}})?" 34 | - pattern-not-regex: "{{.*?\\|\\s*safe[^\\s}]" 35 | -------------------------------------------------------------------------------- /src/steps/assigneeRemoved.js: -------------------------------------------------------------------------------- 1 | export default async function assigneeRemoved ({ 2 | context, 3 | github, 4 | githubToken, 5 | assignees 6 | }) { 7 | console.log('assignees: %o', assignees) 8 | const assigneesOutput = assignees.split(/\s+/).filter((str) => str !== '') 9 | const query = `query ($owner: String!, $name: String!, $prnumber: Int!) { 10 | repository(owner: $owner, name: $name) { 11 | pullRequest(number: $prnumber) { 12 | timelineItems(last: 100, itemTypes: UNLABELED_EVENT) { 13 | nodes { 14 | ... on UnlabeledEvent { 15 | label { 16 | name 17 | } 18 | actor { 19 | login 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | }` 27 | const variables = { 28 | owner: context.repo.owner, 29 | name: context.repo.repo, 30 | prnumber: context.issue.number 31 | } 32 | const result = await github.graphql(query, variables) 33 | const timelineItems = result.repository.pullRequest.timelineItems 34 | console.log('timelineItems: %o', timelineItems) 35 | const removedByAssigneeEvents = timelineItems.nodes.filter( 36 | timelineItem => ( 37 | timelineItem.label.name === 'needs-security-review' && 38 | assigneesOutput.some((a) => timelineItem.actor.login === a) 39 | ) 40 | ).length 41 | console.log('RemovedByAssigneeEvents: %d', removedByAssigneeEvents) 42 | return removedByAssigneeEvents > 0 43 | } 44 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 22 | with: 23 | node-version: '24.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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 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-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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 22 | with: 23 | node-version: '24.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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 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 | -------------------------------------------------------------------------------- /src/pullRequestChangedFiles.js: -------------------------------------------------------------------------------- 1 | export default async function pullRequestChangedFIles ({ github, githubToken, owner, name, prnumber }) { 2 | if (!github && githubToken) { 3 | const { Octokit } = await import('octokit') 4 | 5 | github = new Octokit({ auth: githubToken }) 6 | } 7 | 8 | if (!github && !githubToken) { 9 | throw new Error('either githubToken or github is required!') 10 | } 11 | 12 | prnumber = parseInt(prnumber, 10) 13 | 14 | const query = `query ($owner: String!, $name: String!, $prnumber: Int!, $cursor: String) { 15 | repository(owner: $owner, name: $name) { 16 | pullRequest(number: $prnumber) { 17 | files(first: 100, after: $cursor) { 18 | pageInfo { 19 | endCursor 20 | hasNextPage 21 | } 22 | nodes { 23 | path 24 | additions 25 | deletions 26 | } 27 | } 28 | } 29 | } 30 | }` 31 | 32 | const vars = { 33 | owner, 34 | name, 35 | prnumber 36 | } 37 | 38 | let hasNextPage = true 39 | let paths = [] 40 | while (hasNextPage) { 41 | const { repository } = await github.graphql(query, vars) 42 | const files = repository.pullRequest.files 43 | 44 | // prepare the next iteration 45 | hasNextPage = files.pageInfo.hasNextPage 46 | vars.cursor = files.pageInfo.endCursor 47 | 48 | // append new paths to paths array 49 | // check for additions only, deletions are not relevant, in this case 50 | paths = paths.concat( 51 | files.nodes.filter(file => file.additions /* + file.deletions */ > 0).map(file => file.path)) 52 | } 53 | 54 | return paths 55 | } 56 | -------------------------------------------------------------------------------- /assets/opengrep_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 | author: Andrea Brancaleoni 12 | cwe: 13 | - "CWE-798: Use of Hard-coded Credentials" 14 | source-rule-url: https://github.com/grab/secret-scanner/blob/master/scanner/signatures/pattern.go 15 | category: security 16 | technology: 17 | - secrets 18 | - aws 19 | confidence: LOW 20 | owasp: 21 | - A07:2021 - Identification and Authentication Failures 22 | references: 23 | - https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures 24 | - https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-prefixes 25 | - https://semgrep.dev/r?q=generic.secrets.security.detected-aws-access-key-id-value.detected-aws-access-key-id-value 26 | cwe2022-top25: true 27 | cwe2021-top25: true 28 | subcategory: 29 | - audit 30 | likelihood: LOW 31 | impact: HIGH 32 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 33 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/services/detected-aws-access-key-id-value.yaml 34 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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)?|assert)$ 26 | - pattern-not-regex: RunOnUIThread 27 | - pattern-not-regex: CalledOnValidThread 28 | -------------------------------------------------------------------------------- /src/updateRuntimeProperty.js: -------------------------------------------------------------------------------- 1 | export default async function updateRuntimeProperty ({ 2 | githubToken = null, 3 | github = null, 4 | debug = false, 5 | repositories, 6 | runtime, 7 | org, 8 | core 9 | }) { 10 | if (!github && githubToken) { 11 | const { Octokit } = await import('octokit') 12 | 13 | github = new Octokit({ auth: githubToken }) 14 | } 15 | 16 | if (!github && !githubToken) { 17 | throw new Error('either githubToken or github is required!') 18 | } 19 | 20 | if (!runtime) { 21 | throw new Error('runtime is required!') 22 | } 23 | 24 | if (!repositories) { 25 | throw new Error('repositories is required!') 26 | } 27 | 28 | if (!org) { 29 | throw new Error('org is required! No token can modify more than one org property.') 30 | } 31 | 32 | debug = debug === 'true' || debug === true 33 | 34 | // if repositories is a string, split it on spaces 35 | if (typeof repositories === 'string') { 36 | repositories = repositories.split(' ').map(r => r.trim()).map((r) => { 37 | const s = r.split('/') 38 | return { org: s[0], name: s[1] } 39 | }).filter(r => r.org === org) 40 | } 41 | 42 | for (const repo of repositories) { 43 | if (debug) { console.log('updating runtime property') } 44 | if (core) { 45 | core.setSecret(repo.org) 46 | core.setSecret(repo.name) 47 | } 48 | 49 | await github.request('PATCH /orgs/{org}/properties/values', { 50 | org: repo.org, 51 | repository_names: [repo.name], 52 | properties: [ 53 | { 54 | property_name: 'runtime', 55 | value: runtime 56 | } 57 | ], 58 | headers: { 59 | 'X-GitHub-Api-Version': '2022-11-28' 60 | } 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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/opengrep_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 | author: Andrea Brancaleoni 19 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/services/insecure-types.yaml 20 | original_source: https://github.com/returntocorp/semgrep-rules/blob/5b098c252feec688d243cef046d07597a546c25b/go/template/security/insecure-types.yaml 21 | cwe: 22 | - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" 23 | references: 24 | - https://golang.org/pkg/html/template/#HTML 25 | - https://twitter.com/empijei/status/1275177219011350528 26 | category: security 27 | technology: 28 | - template 29 | confidence: LOW 30 | owasp: 31 | - A07:2017 - Cross-Site Scripting (XSS) 32 | - A03:2021 - Injection 33 | cwe2022-top25: true 34 | cwe2021-top25: true 35 | subcategory: 36 | - audit 37 | likelihood: LOW 38 | impact: MEDIUM 39 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 40 | languages: 41 | - go 42 | -------------------------------------------------------------------------------- /assets/opengrep_rules/client/check-vs-dcheck.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: chromium-security-dcheck-should-be-check 3 | metadata: 4 | author: Andrea Brancaleoni 5 | references: 6 | - https://chromium.googlesource.com/chromium/src/+/main/styleguide/c++/checks.md 7 | - https://chromium.googlesource.com/chromium/src/+/main/styleguide/c++/c++.md#CHECK_DCHECK_and_NOTREACHED 8 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/client/check-vs-dcheck.yaml 9 | assignees: | 10 | thypon 11 | cdesouza-chromium 12 | category: security 13 | languages: [cpp, c] 14 | message: | 15 | DCHECK() should not be used for security-critical invariants that must be verified in production. 16 | Use CHECK() instead to ensure the invariant is always verified, not just in debug builds. 17 | Common security-critical patterns: origin validation, bounds checking, privilege verification. 18 | severity: WARNING 19 | patterns: 20 | - pattern-either: 21 | - pattern: DCHECK($CONDITION); 22 | - pattern: DCHECK_EQ(..., $CONDITION); 23 | - pattern: DCHECK_EQ($CONDITION, ...); 24 | - pattern: DCHECK_NE($CONDITION, ...); 25 | - pattern: DCHECK_NE(..., $CONDITION); 26 | - pattern: DCHECK_LT($CONDITION, ...); 27 | - pattern: DCHECK_LT(..., $CONDITION); 28 | - pattern: DCHECK_LE(..., $CONDITION); 29 | - pattern: DCHECK_LE($CONDITION, ...); 30 | - pattern: DCHECK_GT(..., $CONDITION); 31 | - pattern: DCHECK_GT($CONDITION, ...); 32 | - pattern: DCHECK_GE(..., $CONDITION); 33 | - pattern: DCHECK_GE($CONDITION, ...); 34 | - metavariable-regex: 35 | metavariable: $CONDITION 36 | regex: .*(origin|security|privilege|auth|permission|bound|size|index|offset|length|capacity).* -------------------------------------------------------------------------------- /assets/opengrep_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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/opengrep_rules/client/crash-vs-dump-without-crashing.cpp: -------------------------------------------------------------------------------- 1 | // Test cases for crash-vs-dump-without-crashing rule 2 | #include "base/debug/dump_without_crashing.h" 3 | #include "base/check.h" 4 | 5 | class TestClass { 6 | public: 7 | void BadExamples() { 8 | // SHOULD TRIGGER: Direct DumpWithoutCrashing usage (should use DUMP_WILL_BE_CHECK) 9 | if (some_error_condition_) { 10 | // ruleid: chromium-crash-vs-dump-without-crashing 11 | base::debug::DumpWithoutCrashing(); 12 | return; 13 | } 14 | 15 | // SHOULD TRIGGER: Another direct usage 16 | if (!IsValidState()) { 17 | // ruleid: chromium-crash-vs-dump-without-crashing 18 | base::debug::DumpWithoutCrashing(); 19 | // Continue execution but log crash dump 20 | } 21 | } 22 | 23 | void GoodExamples() { 24 | // SHOULD NOT TRIGGER: Using DUMP_WILL_BE_CHECK (preferred) 25 | if (some_error_condition_) { 26 | // ok: chromium-crash-vs-dump-without-crashing 27 | DUMP_WILL_BE_CHECK(); 28 | return; 29 | } 30 | 31 | // SHOULD NOT TRIGGER: Using CHECK for immediate crash 32 | // ok: chromium-crash-vs-dump-without-crashing 33 | CHECK(IsValidState()) << "Invalid state detected"; 34 | 35 | // SHOULD NOT TRIGGER: Using DCHECK for debug-only checks 36 | // ok: chromium-crash-vs-dump-without-crashing 37 | DCHECK(some_condition_) << "Debug assertion failed"; 38 | } 39 | 40 | void FunctionWithParameters() { 41 | // SHOULD NOT TRIGGER: DumpWithoutCrashing with parameters (internal usage) 42 | if (critical_error_) { 43 | // ok: chromium-crash-vs-dump-without-crashing 44 | base::debug::DumpWithoutCrashing(FROM_HERE, base::Days(1)); 45 | return; 46 | } 47 | } 48 | 49 | private: 50 | bool some_error_condition_ = false; 51 | bool some_condition_ = true; 52 | bool critical_error_ = false; 53 | 54 | bool IsValidState() const { return true; } 55 | }; -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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/opengrep_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/opengrep_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/opengrep_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 | } -------------------------------------------------------------------------------- /src/steps/commentsNumber.js: -------------------------------------------------------------------------------- 1 | // regex to match "" 2 | const categoryRegex = // 3 | 4 | export default async function commentsNumber ({ 5 | github, 6 | githubToken, 7 | context 8 | }) { 9 | if (!github && githubToken) { 10 | const { Octokit } = await import('octokit') 11 | 12 | github = new Octokit({ auth: githubToken }) 13 | } 14 | 15 | const categories = new Set() 16 | 17 | const query = `query($owner:String!, $name:String!, $prnumber:Int!) { 18 | repository(owner:$owner, name:$name) { 19 | pullRequest(number:$prnumber) { 20 | reviewThreads(last:100) { 21 | nodes { 22 | isOutdated 23 | comments(first:1) { 24 | totalCount 25 | nodes { 26 | author { 27 | login 28 | } 29 | body 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | }` 37 | const variables = { 38 | owner: context.repo.owner, 39 | name: context.repo.repo, 40 | prnumber: context.issue.number 41 | } 42 | const result = await github.graphql(query, variables) 43 | const threads = result.repository.pullRequest.reviewThreads 44 | const comments = threads.nodes.filter( 45 | reviewThread => ( 46 | !(reviewThread.isOutdated === true && reviewThread.comments.totalCount === 1) && 47 | reviewThread.comments.nodes[0].author.login === 'github-actions' && 48 | reviewThread.comments.nodes[0].body.includes('
Cc ') 49 | ) 50 | ).map(reviewThread => reviewThread.comments.nodes[0]) 51 | 52 | for (const comment of comments) { 53 | const category = comment.body.match(categoryRegex)?.[1] 54 | if (category) { 55 | categories.add(category) 56 | } 57 | } 58 | console.log('Comments: %d', comments.length) 59 | return { number: comments.length, categories: Array.from(categories) } 60 | } 61 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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 | -------------------------------------------------------------------------------- /src/steps/cleanupComments.js: -------------------------------------------------------------------------------- 1 | export default async function cleanupComments ({ 2 | github, 3 | githubToken, 4 | context 5 | }) { 6 | if (!github && githubToken) { 7 | const { Octokit } = await import('octokit') 8 | 9 | github = new Octokit({ auth: githubToken }) 10 | } 11 | 12 | const query = `query($owner:String!, $name:String!, $prnumber:Int!) { 13 | repository(owner:$owner, name:$name) { 14 | pullRequest(number:$prnumber) { 15 | reviewThreads(last:100) { 16 | nodes { 17 | isOutdated 18 | comments(first:1) { 19 | totalCount 20 | nodes { 21 | id 22 | author { 23 | login 24 | } 25 | body 26 | } 27 | } 28 | } 29 | } 30 | } 31 | } 32 | }` 33 | const variables = { 34 | owner: context.repo.owner, 35 | name: context.repo.repo, 36 | prnumber: context.issue.number 37 | } 38 | const result = await github.graphql(query, variables) 39 | const threads = result.repository.pullRequest.reviewThreads 40 | const deletableComments = threads.nodes.filter( 41 | reviewThread => ( 42 | reviewThread.isOutdated === true && 43 | reviewThread.comments.totalCount === 1 && 44 | reviewThread.comments.nodes[0].author.login === 'github-actions' && 45 | reviewThread.comments.nodes[0].body.includes('
Cc ') 46 | ) 47 | ).map( 48 | reviewThread => ( 49 | reviewThread.comments.nodes[0].id 50 | ) 51 | ) 52 | console.log('Delete', deletableComments) 53 | if (deletableComments) { 54 | const deleteMutation = `mutation($comment:ID!) { 55 | deletePullRequestReviewComment(input: {id:$comment}) { 56 | clientMutationId 57 | } 58 | }` 59 | for (const commentId of deletableComments) { 60 | console.log('Deleting %s', commentId) 61 | await github.graphql(deleteMutation, { comment: commentId }) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.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 | pull-requests: write 12 | jobs: 13 | full-loop: 14 | name: full-loop 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 18 | with: 19 | fetch-depth: 0 20 | - uses: ./actions/main 21 | name: Run action on full security-action repo 22 | id: action 23 | with: 24 | debug: true 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | baseline_scan_only: false 27 | gh_to_slack_user_map: ${{ secrets.GH_TO_SLACK_USER_MAP }} 28 | - run: | 29 | set -e 30 | if ((${{ fromJson(steps.action.outputs.reviewdog-findings) }} < 106)); then 31 | echo "Too few reviewdog findings" 32 | exit 1 33 | fi 34 | if ((${{ fromJson(steps.action.outputs.safesvg-count) }} < 2)); then 35 | echo "Too few safesvg findings" 36 | exit 1 37 | fi 38 | if ((${{ fromJson(steps.action.outputs.tfsec-count) }} < 4)); then 39 | echo "Too few tfsec findings" 40 | exit 1 41 | fi 42 | if ((${{ fromJson(steps.action.outputs.opengrep-count) }} < 97)); then 43 | echo "Too few opengrep findings" 44 | exit 1 45 | fi 46 | if ((${{ fromJson(steps.action.outputs.sveltegrep-count) }} < 3)); then 47 | echo "Too few sveltegrep findings" 48 | exit 1 49 | fi 50 | if ((${{ fromJson(steps.action.outputs.npm-audit-count) }} < 3)); then 51 | echo "Too few npm-audit findings" 52 | exit 1 53 | fi 54 | if ((${{ fromJson(steps.action.outputs.pip-audit-count) }} < 2)); then 55 | echo "Too few pip-audit findings" 56 | exit 1 57 | fi 58 | shell: bash 59 | name: Check number of findings 60 | -------------------------------------------------------------------------------- /assets/opengrep_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 | author: Andrea Brancaleoni 30 | cwe: 31 | - 'CWE-732: Incorrect Permission Assignment for Critical Resource' 32 | owasp: 33 | - A05:2021 - Security Misconfiguration 34 | - A06:2017 - Security Misconfiguration 35 | references: 36 | - https://docs.docker.com/engine/reference/run/#security-configuration 37 | - https://raesene.github.io/blog/2019/06/01/docker-capabilities-and-no-new-privs/ 38 | - https://www.kernel.org/doc/Documentation/prctl/no_new_privs.txt 39 | - https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-4-add-no-new-privileges-flag 40 | category: security 41 | technology: 42 | - docker-compose 43 | cwe2021-top25: true 44 | subcategory: 45 | - audit 46 | likelihood: LOW 47 | impact: HIGH 48 | confidence: LOW 49 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 50 | vulnerability_class: 51 | - Improper Authorization 52 | source-rule-url: https://semgrep.dev/r/yaml.docker-compose.security.no-new-privileges.no-new-privileges 53 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/services/docker-compose-no-new-privileges.yaml 54 | languages: 55 | - yaml 56 | severity: WARNING 57 | -------------------------------------------------------------------------------- /assets/opengrep_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 | -------------------------------------------------------------------------------- /src/steps/assigneesAfter.js: -------------------------------------------------------------------------------- 1 | export default async function assigneesAfter ({ 2 | github, 3 | githubToken, 4 | context, 5 | owner, 6 | repo, 7 | number, 8 | assignees 9 | }) { 10 | if (!github && githubToken) { 11 | const { Octokit } = await import('octokit') 12 | 13 | github = new Octokit({ auth: githubToken }) 14 | } 15 | 16 | if (!github) { 17 | throw new Error('github or githubToken is required') 18 | } 19 | 20 | if (typeof number === 'string') { 21 | number = parseInt(number, 10) 22 | } 23 | 24 | const query = `query($owner:String!, $name:String!, $prnumber:Int!) { 25 | repository(owner:$owner, name:$name) { 26 | pullRequest(number:$prnumber) { 27 | reviewThreads(last:100) { 28 | nodes { 29 | comments(first:1) { 30 | totalCount 31 | nodes { 32 | id 33 | author { 34 | login 35 | } 36 | body 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | }` 44 | const variables = { 45 | owner: owner || context.repo.owner, 46 | name: repo || context.repo.repo, 47 | prnumber: number || context.issue.number 48 | } 49 | const result = await github.graphql(query, variables) 50 | const threads = result.repository.pullRequest.reviewThreads 51 | const outputAssignees = [...new Set(threads.nodes.filter( 52 | reviewThread => ( 53 | reviewThread.comments.nodes[0].author.login === 'github-actions' && 54 | reviewThread.comments.nodes[0].body.includes('
Cc ') 55 | ) 56 | ).map( 57 | e => e.comments.nodes[0].body 58 | .replace(/\n\n/, '') 59 | .replace(/.*
Cc(.*)/, '$1') 60 | .replaceAll('@', '').trim().split(' ') 61 | ).flat())] 62 | 63 | console.log('assignees: %o', outputAssignees) 64 | if (outputAssignees.length > 0) { 65 | return outputAssignees.join('\n') 66 | } else { 67 | return assignees.split(/\s+/).filter((str) => str !== '').join('\n') 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_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 | author: Andrea Brancaleoni 35 | cwe: 36 | - "CWE-1357: Reliance on Insufficiently Trustworthy Component" 37 | owasp: A06:2021 - Vulnerable and Outdated Components 38 | references: 39 | - https://owasp.org/Top10/A06_2021-Vulnerable_and_Outdated_Components 40 | - https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions 41 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/services/brave-third-party-action-not-pinned-to-commit-sha.yaml 42 | category: security 43 | technology: 44 | - github-actions 45 | subcategory: 46 | - vuln 47 | likelihood: LOW 48 | impact: LOW 49 | confidence: HIGH 50 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 51 | vulnerability_class: 52 | - Other 53 | -------------------------------------------------------------------------------- /assets/opengrep_rules/services/missing-integrity.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - id: missing-integrity 3 | metadata: 4 | author: Andrea Brancaleoni 5 | category: security 6 | technology: 7 | - html 8 | cwe: 9 | - 'CWE-353: Missing Support for Integrity Check' 10 | owasp: 11 | - A08:2021 - Software and Data Integrity Failures 12 | confidence: LOW 13 | references: 14 | - https://owasp.org/Top10/A08_2021-Software_and_Data_Integrity_Failures 15 | subcategory: 16 | - audit 17 | likelihood: LOW 18 | impact: LOW 19 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/services/missing-integrity.yaml 20 | patterns: 21 | - pattern-either: 22 | - pattern: 23 | - pattern: 24 | - metavariable-pattern: 25 | metavariable: $...A 26 | patterns: 27 | - pattern-either: 28 | - pattern: src='... :// ...' 29 | - pattern: src="... :// ..." 30 | - pattern: href='... :// ...' 31 | - pattern: href="... :// ..." 32 | - pattern: src='//...' 33 | - pattern: src="//..." 34 | - pattern: href='//...' 35 | - pattern: href="//..." 36 | - pattern-not-regex: (?is).*integrity= 37 | - pattern-not-regex: (google-analytics.com|fonts.googleapis.com|googletagmanager.com) 38 | - pattern-not-regex: 'chrome:\/\/' 39 | - pattern-not-regex: 'chrome-untrusted:\/\/' 40 | paths: 41 | include: 42 | - '*.html' 43 | message: >- 44 | 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. 45 | severity: WARNING 46 | languages: [generic] 47 | -------------------------------------------------------------------------------- /assets/opengrep_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 | -------------------------------------------------------------------------------- /.github/workflows/opengrep-self-test.yml: -------------------------------------------------------------------------------- 1 | name: opengrep-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 | opengrep-self-test: 14 | name: opengrep-self-test 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 18 | - name: Cache opengrep 19 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 20 | with: 21 | path: ~/.opengrep 22 | key: opengrep-v1.11.2-${{ runner.os }} 23 | - name: Install opengrep 24 | uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 25 | with: 26 | script: |- 27 | const installOpengrep = (await import('${{ github.workspace }}/src/installOpengrep.js')).default 28 | await installOpengrep() 29 | - run: | 30 | python3 -m pip --disable-pip-version-check install -r requirements.txt 31 | shell: bash 32 | - run: | 33 | set -e 34 | cd assets/opengrep_rules/; opengrep test --strict . 35 | shell: bash 36 | - run: | 37 | JSON=$(opengrep \ 38 | --disable-version-check --strict --json \ 39 | $(find assets/opengrep_rules -name '*.yml' -or -name '*.yaml' -not -name '*.test.yml' -not -name '*.test.yaml' -not -path "assets/opengrep_rules/generated/*" | sed 's/^/-c /g') \ 40 | assets/opengrep_rules/{client,services} || true) 41 | ERRORS=$(echo "$JSON" | jq '.errors' || true) 42 | BADERRS=$(echo "$ERRORS" | jq '.[] | select(.level == "error")' || true) 43 | if [[ -n "$BADERRS" ]]; then 44 | echo "Opengrep rule / version issue: $BADERRS" 45 | exit 123 46 | fi 47 | RESULTCOUNT=$(echo "$JSON" | tr '\n' ' ' | tr '\t' ' ' | jq '.results | length') 48 | NUMRESULTS=$(grep -R ruleid: | wc -l) 49 | if [[ "$RESULTCOUNT" -lt "$NUMRESULTS" ]]; then 50 | echo "Found fewer than $NUMRESULTS opengrep results" 51 | exit 122 52 | fi 53 | shell: bash 54 | -------------------------------------------------------------------------------- /assets/opengrep_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/opengrep_rules/client/chromium-uaf.yaml 15 | assignees: | 16 | thypon 17 | goodov 18 | category: security 19 | message: | 20 | base::Unretained is most of the time unrequited, and a weak reference is better suited for secure coding. 21 | Consider swapping Unretained for a weak reference. 22 | base::Unretained usage may be acceptable when a callback owner is guaranteed 23 | to be destroyed with the object base::Unretained is pointing to, for example: 24 | 25 | - PrefChangeRegistrar 26 | - base::*Timer 27 | - mojo::Receiver 28 | - any other class member destroyed when the class is deallocated 29 | languages: [cpp, c] 30 | severity: WARNING 31 | - id: chromium-bind-uaf 32 | metadata: 33 | author: Andrea Brancaleoni 34 | category: security 35 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/client/chromium-uaf.yaml 36 | assignees: | 37 | thypon 38 | goodov 39 | patterns: 40 | - patterns: 41 | - pattern-either: 42 | - pattern: base::BindOnce($FIRST_ARG, $...REST_ARGS) 43 | - pattern: base::BindRepeating($FIRST_ARG, $...REST_ARGS) 44 | - metavariable-comparison: 45 | comparison: not re.match("::", str($FIRST_ARG)) and re.match("this", str($...REST_ARGS)) 46 | message: | 47 | BindOnce/BindRepeating may allow callers to access objects which may already be freed in the C++ lifecycle.
Verify the occurrences manually. 48 | languages: [cpp, c] 49 | severity: WARNING 50 | -------------------------------------------------------------------------------- /assets/opengrep_rules/client/check-vs-dcheck.cpp: -------------------------------------------------------------------------------- 1 | // Test cases for check-vs-dcheck rule 2 | #include "base/check.h" 3 | #include "url/origin.h" 4 | 5 | class TestClass { 6 | public: 7 | void SecurityChecks() { 8 | url::Origin origin; 9 | size_t buffer_size = 1024; 10 | size_t index = 0; 11 | bool has_permission = false; 12 | 13 | // SHOULD TRIGGER: Security-critical checks using DCHECK (should be CHECK) 14 | // ruleid: chromium-security-dcheck-should-be-check 15 | DCHECK(origin.IsSameOriginWith(trusted_origin_)); 16 | // ruleid: chromium-security-dcheck-should-be-check 17 | DCHECK(has_permission); 18 | // ruleid: chromium-security-dcheck-should-be-check 19 | DCHECK_LT(index, buffer_size); 20 | // ruleid: chromium-security-dcheck-should-be-check 21 | DCHECK_GE(buffer_size, min_required_size_); 22 | // ruleid: chromium-security-dcheck-should-be-check 23 | DCHECK_EQ(security_level_, SECURE); 24 | // ruleid: chromium-security-dcheck-should-be-check 25 | DCHECK(IsAuthorized(user_privilege_)); 26 | 27 | // SHOULD NOT TRIGGER: Non-security related DCHECKs (acceptable) 28 | // ok: chromium-security-dcheck-should-be-check 29 | DCHECK(widget != nullptr); 30 | // ok: chromium-security-dcheck-should-be-check 31 | DCHECK_EQ(color, RED); 32 | // ok: chromium-security-dcheck-should-be-check 33 | DCHECK(callback.is_null()); 34 | // ok: chromium-security-dcheck-should-be-check 35 | DCHECK_GT(width, 0); 36 | 37 | // SHOULD NOT TRIGGER: Correct usage with CHECK (preferred for security) 38 | // ok: chromium-security-dcheck-should-be-check 39 | CHECK(origin.IsSameOriginWith(trusted_origin_)); 40 | // ok: chromium-security-dcheck-should-be-check 41 | CHECK(has_permission); 42 | // ok: chromium-security-dcheck-should-be-check 43 | CHECK_LT(index, buffer_size); 44 | // ok: chromium-security-dcheck-should-be-check 45 | CHECK_GE(buffer_size, min_required_size_); 46 | // ok: chromium-security-dcheck-should-be-check 47 | CHECK_EQ(security_level_, SECURE); 48 | // ok: chromium-security-dcheck-should-be-check 49 | CHECK(IsAuthorized(user_privilege_)); 50 | } 51 | 52 | private: 53 | url::Origin trusted_origin_; 54 | size_t min_required_size_ = 100; 55 | int security_level_ = 0; 56 | const int SECURE = 1; 57 | 58 | bool IsAuthorized(int privilege) { return privilege > 0; } 59 | }; -------------------------------------------------------------------------------- /assets/opengrep_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 | author: Andrea Brancaleoni 6 | cwe: 7 | - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" 8 | owasp: 9 | - A07:2017 - Cross-Site Scripting (XSS) 10 | - A03:2021 - Injection 11 | references: 12 | - 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 13 | - https://www.veracode.com/blog/secure-development/nodejs-template-engines-why-default-encoders-are-not-enough 14 | - https://github.com/ESAPI/owasp-esapi-js 15 | - https://semgrep.dev/r/generic.html-templates.security.var-in-script-tag.var-in-script-tag 16 | - https://jinja.palletsprojects.com/en/3.0.x/templates/#jinja-filters.tojson 17 | category: security 18 | technology: 19 | - html-templates 20 | confidence: LOW 21 | cwe2022-top25: true 22 | cwe2021-top25: true 23 | subcategory: 24 | - audit 25 | likelihood: LOW 26 | impact: MEDIUM 27 | license: Commons Clause License Condition v1.0[LGPL-2.1-only] 28 | source: https://github.com/brave/security-action/blob/main/assets/opengrep_rules/services/var-in-script-tag.yaml 29 | languages: 30 | - generic 31 | paths: 32 | include: 33 | - "*.mustache" 34 | - "*.hbs" 35 | - "*.html" 36 | severity: WARNING 37 | patterns: 38 | - pattern-inside: 39 | - pattern-not-inside: