-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
⚒️ Add trusted-list bypass automation for reviewed blocklist exceptions #239069
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
409H
wants to merge
13
commits into
main
Choose a base branch
from
feature/bypass-trusted-lists
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
96b75e1
Add description about bypassing trusted lists
409H 0e53b7e
Moved domains to skip check on trusted lists to another list file
409H 353575f
Add placeholder trusted list bypass reviewer file
409H 877dbfb
Add bypass trusted list workflow
409H 564c4ed
Add script to write bypassed domain to list to skip logic
409H c838b4a
Add utils to help with bypassing domain logic against trusted lists
409H 71983c4
Updated list of people who can skip trusted lists
409H 4e46821
Add SEAL bot
409H 5f3cfdf
Add pcaversaccio to trusted list
409H c4846e9
Fix cursor issues
409H 135ab3a
Add code changes per @0xOhm review
409H 76fc20e
Fix cursor comment on case sensitivity
409H 2be8734
Merge branch 'main' into feature/bypass-trusted-lists
409H File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| [ | ||
| "samczsun", | ||
| "409h", | ||
| "tayvano", | ||
| "mindofmar", | ||
| "0xOhm", | ||
| "security-alliance-bot", | ||
| "pcaversaccio" | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| name: Trusted List Bypass | ||
|
|
||
| on: | ||
| issue_comment: | ||
| types: [created] | ||
|
|
||
| permissions: | ||
| contents: write | ||
| issues: write | ||
| pull-requests: read | ||
|
|
||
| jobs: | ||
| trusted-list-bypass: | ||
| name: Trusted List Bypass | ||
| if: github.event.issue.pull_request && (startsWith(github.event.comment.body, '/skip-trusted-lists') || github.event.comment.body == 'Skip trusted lists') | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Get pull request | ||
| id: pr | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const { owner, repo } = context.repo; | ||
| const { data: pull } = await github.rest.pulls.get({ | ||
| owner, | ||
| repo, | ||
| pull_number: context.issue.number, | ||
| }); | ||
|
|
||
| core.setOutput('head_repo', pull.head.repo.full_name); | ||
| core.setOutput('head_ref', pull.head.ref); | ||
| core.setOutput('is_fork', String(pull.head.repo.full_name !== `${owner}/${repo}`)); | ||
|
|
||
| - name: Report unsupported fork | ||
| if: steps.pr.outputs.is_fork == 'true' | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| await github.rest.issues.createComment({ | ||
| ...context.repo, | ||
| issue_number: context.issue.number, | ||
| body: 'Trusted-list bypass automation can only push updates to branches in this repository. Please have a maintainer apply the bypass file update manually.', | ||
| }); | ||
|
|
||
| - name: Checkout automation | ||
| if: steps.pr.outputs.is_fork == 'false' | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| path: automation | ||
|
|
||
| - name: Use Node.js | ||
| if: steps.pr.outputs.is_fork == 'false' | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 20.x | ||
| cache: yarn | ||
| cache-dependency-path: automation/yarn.lock | ||
|
|
||
| - name: Install automation dependencies | ||
| if: steps.pr.outputs.is_fork == 'false' | ||
| working-directory: automation | ||
| run: yarn --frozen-lockfile | ||
|
|
||
| - name: Checkout pull request branch | ||
| if: steps.pr.outputs.is_fork == 'false' | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| repository: ${{ steps.pr.outputs.head_repo }} | ||
| ref: ${{ steps.pr.outputs.head_ref }} | ||
| path: pull-request | ||
|
|
||
| - name: Apply trusted-list bypass | ||
| if: steps.pr.outputs.is_fork == 'false' | ||
| working-directory: automation | ||
| env: | ||
| TRUSTED_LIST_BYPASS_APPROVED_BY: ${{ github.event.comment.user.login }} | ||
| TRUSTED_LIST_BYPASS_COMMENT: ${{ github.event.comment.body }} | ||
| TRUSTED_LIST_BYPASS_PR_NUMBER: ${{ github.event.issue.number }} | ||
| TRUSTED_LIST_BYPASS_TARGET: ${{ github.workspace }}/pull-request | ||
| run: node --import tsx bin/apply-trusted-list-bypass.ts | ||
|
|
||
| - name: Commit trusted-list bypass | ||
| if: success() && steps.pr.outputs.is_fork == 'false' | ||
| working-directory: pull-request | ||
| env: | ||
| HEAD_REF: ${{ steps.pr.outputs.head_ref }} | ||
| PR_NUMBER: ${{ github.event.issue.number }} | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | ||
| git add test/resources/trusted-list-bypass.txt | ||
| if git diff --cached --quiet; then | ||
| echo "No trusted-list bypass changes to commit." | ||
| exit 0 | ||
| fi | ||
| git commit -m "Add trusted list bypass for PR #${PR_NUMBER}" | ||
| git push origin "HEAD:${HEAD_REF}" | ||
|
|
||
| - name: Report completion | ||
| if: success() && steps.pr.outputs.is_fork == 'false' | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| await github.rest.issues.createComment({ | ||
| ...context.repo, | ||
| issue_number: context.issue.number, | ||
| body: 'Trusted-list bypass check completed. If a bypass was needed, I pushed it to this PR branch and CI will rerun.', | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| #!/usr/bin/env node | ||
|
|
||
| import { readFile, writeFile } from "node:fs/promises"; | ||
| import path from "node:path"; | ||
| import { detectFalsePositives, parseTrustedListBypass } from "../test/trusted-list-utils.js"; | ||
| import { Config } from "../src/types.js"; | ||
|
|
||
| const TRUSTED_LIST_IDS = ["tranco", "coinmarketcap", "snapsregistry", "coingecko", "dapps"]; | ||
| const COMMANDS = new Set(["/skip-trusted-lists", "skip trusted lists"]); | ||
|
|
||
| const policyRoot = path.dirname(__dirname); | ||
| const targetRoot = process.env.TRUSTED_LIST_BYPASS_TARGET || policyRoot; | ||
| const resourcesDir = path.join(targetRoot, "test", "resources"); | ||
| const bypassPath = path.join(resourcesDir, "trusted-list-bypass.txt"); | ||
|
|
||
| const readJson = async <T>(filePath: string): Promise<T> => { | ||
| return JSON.parse(await readFile(filePath, { encoding: "utf-8" })) as T; | ||
| }; | ||
|
|
||
| const normalizeHandle = (handle: string): string => { | ||
| return handle.replace(/^@/u, "").toLowerCase(); | ||
| }; | ||
|
|
||
| const readExistingBypass = async (): Promise<string> => { | ||
| try { | ||
| return await readFile(bypassPath, { encoding: "utf-8" }); | ||
| } catch (error) { | ||
| if ((error as NodeJS.ErrnoException).code !== "ENOENT") { | ||
| throw error; | ||
| } | ||
|
|
||
| return [ | ||
| "# These domains are intentionally blocklisted even though they appear on a trusted comparison list.", | ||
| "# Add evidence or a short reason after the domain when possible.", | ||
| "", | ||
| ].join("\n"); | ||
| } | ||
| }; | ||
|
|
||
| const getCommand = (): string => { | ||
| const firstLine = (process.env.TRUSTED_LIST_BYPASS_COMMENT || "").trim().split(/\r?\n/u)[0]?.trim() || ""; | ||
| const normalizedFirstLine = firstLine.toLowerCase(); | ||
|
|
||
| if (normalizedFirstLine.startsWith("/skip-trusted-lists")) { | ||
| return "/skip-trusted-lists"; | ||
| } | ||
|
|
||
| return normalizedFirstLine; | ||
| }; | ||
|
|
||
| const getApprovedBy = (): string => { | ||
| const approvedBy = process.env.TRUSTED_LIST_BYPASS_APPROVED_BY; | ||
| if (!approvedBy) { | ||
| throw new Error("TRUSTED_LIST_BYPASS_APPROVED_BY must be set"); | ||
| } | ||
|
|
||
| return normalizeHandle(approvedBy); | ||
| }; | ||
|
|
||
| const getPrNumber = (): string => { | ||
| const prNumber = process.env.TRUSTED_LIST_BYPASS_PR_NUMBER; | ||
| if (!prNumber) { | ||
| throw new Error("TRUSTED_LIST_BYPASS_PR_NUMBER must be set"); | ||
| } | ||
|
|
||
| return prNumber; | ||
| }; | ||
|
|
||
| const assertCommand = () => { | ||
| const command = getCommand(); | ||
| if (!COMMANDS.has(command)) { | ||
| throw new Error(`Unsupported trusted-list bypass command: ${command}`); | ||
| } | ||
| }; | ||
|
|
||
| const assertApprovedReviewer = async (approvedBy: string) => { | ||
| const reviewers = await readJson<string[]>(path.join(policyRoot, ".github", "trusted-list-bypass-reviewers.json")); | ||
| const normalizedReviewers = new Set(reviewers.map(normalizeHandle)); | ||
|
|
||
| if (!normalizedReviewers.has(approvedBy)) { | ||
| throw new Error(`@${approvedBy} is not approved to apply trusted-list bypasses`); | ||
| } | ||
| }; | ||
|
|
||
| const findBypassableEntries = async (config: Config, bypass: Set<string>): Promise<string[]> => { | ||
| const entries = new Set<string>(); | ||
|
|
||
| for (const listId of TRUSTED_LIST_IDS) { | ||
| const contents = await readFile(path.join(resourcesDir, `${listId}.txt`), { encoding: "utf-8" }); | ||
| const comparisonList = new Set(contents.split("\n")); | ||
| const falsePositives = detectFalsePositives(config.blacklist!, comparisonList, bypass); | ||
|
|
||
| for (const entry of falsePositives) { | ||
| const bypassWithEntry = new Set([...bypass, entry]); | ||
| const remaining = detectFalsePositives(config.blacklist!, comparisonList, bypassWithEntry); | ||
|
|
||
| if (!remaining.includes(entry)) { | ||
| entries.add(entry); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return Array.from(entries).sort(); | ||
| }; | ||
|
|
||
| const appendBypasses = async (contents: string, entries: string[], approvedBy: string, prNumber: string) => { | ||
| if (entries.length === 0) { | ||
| console.log("No trusted-list bypass entries need to be added."); | ||
| return; | ||
| } | ||
|
|
||
| const existing = parseTrustedListBypass(contents); | ||
| const newEntries = entries.filter((entry) => !existing.has(entry)); | ||
|
|
||
| if (newEntries.length === 0) { | ||
| console.log("All trusted-list bypass entries are already present."); | ||
| return; | ||
| } | ||
|
|
||
| const lines = newEntries.map((entry) => `${entry} # trusted-list bypass approved by @${approvedBy} in PR #${prNumber}`); | ||
| const nextContents = `${contents.trimEnd()}\n${lines.join("\n")}\n`; | ||
|
|
||
| await writeFile(bypassPath, nextContents); | ||
| console.log(`Added trusted-list bypass entries: ${newEntries.join(", ")}`); | ||
| }; | ||
|
|
||
| const main = async () => { | ||
| assertCommand(); | ||
|
|
||
| const approvedBy = getApprovedBy(); | ||
| const prNumber = getPrNumber(); | ||
|
|
||
| await assertApprovedReviewer(approvedBy); | ||
|
|
||
| const [config, bypassContents] = await Promise.all([ | ||
| readJson<Config>(path.join(targetRoot, "src", "config.json")), | ||
| readExistingBypass(), | ||
| ]); | ||
|
|
||
| const bypass = parseTrustedListBypass(bypassContents); | ||
| const entries = await findBypassableEntries(config, bypass); | ||
|
|
||
| await appendBypasses(bypassContents, entries, approvedBy, prNumber); | ||
| }; | ||
|
|
||
| main().catch((error) => { | ||
| console.error(error); | ||
| process.exit(1); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| # These domains are intentionally blocklisted even though they appear on a trusted comparison list. | ||
| # Add evidence or a short reason after the domain when possible. | ||
| mystrikingly.com | ||
| simdif.com | ||
| gb.net | ||
| btcs.love | ||
| ferozo.com | ||
| im-creator.com | ||
| free-ethereum.io | ||
| 890m.com | ||
| b5z.net | ||
| test.com | ||
| multichain.org # https://twitter.com/MultichainOrg/status/1677180114227056641 | ||
| dydx.exchange # https://x.com/dydx/status/1815780835473129702 | ||
| ambient.finance # https://x.com/pcaversaccio/status/1846851269207392722 | ||
| xyz.cutestat.com | ||
|
|
||
| # Below are unknown websites that should stay on the blocklist for brevity but make tests fail. | ||
| # This is likely because they exist on the Tranco list and for one reason or another have a high reputation score. | ||
| # NOTE: If it is on the Tranco list, please CONFIRM that you are NOT adding a false positive. | ||
| # This will trigger a manual review within the CI pipeline. | ||
| # Only once it is confirmed not to be a false positive can it be added to this list. | ||
| azureserv.com | ||
| dnset.com | ||
| dnsrd.com | ||
| prohoster.biz | ||
| kucoin.plus | ||
| ewp.live | ||
| sdstarfx.com | ||
| 1mobile.com | ||
| v6.rocks | ||
| linkpc.net | ||
| bookmanga.com | ||
| lihi.cc | ||
| mytradift.com | ||
| anondns.net | ||
| bitkeep.vip | ||
| temporary.site | ||
| misecure.com | ||
| myz.info | ||
| ton-claim.org | ||
| servehalflife.com | ||
| earnstations.com | ||
| web3quests.com | ||
| qubitscube.com | ||
| teknik.io | ||
| nflfan.org | ||
| purworejokab.go.id | ||
| ditchain.org | ||
| kuex.com | ||
| cloud.dbank.com | ||
| bybi75-alternate.app.link | ||
| mz4t6.rdtk.io | ||
| tornadoeth.cash | ||
| ether.fi # https://x.com/ether_fi/status/1838643492102283571 | ||
| curve.fi # https://x.com/CurveFinance/status/1922040492121829678 | ||
| coinmarketcap.com # https://x.com/Auri_0x/status/1936173321244066273 | ||
| cointelegraph.com # https://x.com/Cointelegraph/status/1936959898094583916 | ||
| card.inertix.pro | ||
| pepe.vip # FE Compromise | ||
| kuroro.com # FE Compromise? | ||
| maxidogetoken.com | ||
| bondex.app # XSS | ||
| irys.xyz # Drainer frontends being hosted on IPFS | ||
| 25u.com # DNS may be hijacked. | ||
| decentreland.live # somehow on tranco | ||
| mssg.me # hosting drainers | ||
| defi2026.z13.web.core.windows.net | ||
| samouraiwallet.com # https://x.com/econoalchemist/status/2035735206691315848 | ||
| chainge.finance | ||
| cow.fi # https://x.com/CoWSwap/status/2044078590514327888 | ||
| eth.limo # https://x.com/eth_limo/status/2045413512986411467 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.