mirror of
https://github.com/pezkuwichain/phishing.git
synced 2026-10-20 18:56:29 +00:00
Sync the lists with polkadot-js/phishing every day, and publish them with checks (#4)
* feat: sync the lists with polkadot-js/phishing daily, and check them before publishing The lists had not moved since January 2026, the site that serves them was copied by hand, and the rebrand had turned five real phishing domains into pezkuwichain.* names, unblocking them. - scripts/syncUpstream.mjs and sync-upstream.yml take upstream's lists every day, lay pezkuwi/overlay.json over them, and commit to master. A day out of bounds (too many sites unblocked or added, scam addresses dropped, one of our domains blocked, one of our entries lost) writes nothing and opens an issue. - pezkuwi/overlay.json holds our own entries with their reasons: the five pezkuwichain.* names stay blocked on purpose, and our domains are protected. - scripts/lists.mjs holds the rules; scripts/lists.test.mjs tests them and the sync end to end; scripts/checkLists.mjs checks the committed files, including that they are what sortAll writes. lists.yml runs all three. - pezkuwi/host/ is what phishing.pezkuwichain.io runs: it pulls master hourly and refuses a rewritten history, a blocked own domain, a shrink over 2%, or shards that disagree, with a sandbox test of each refusal. - sortAll strips a trailing dot. Upstream's 817 "scam.xyz." entries were filed under an empty top-level domain in all/all.json, where checkIfDenied never looks; 816 repeated a listed name, and prenads.xyz was blocked nowhere else. - sortAll uses Node's own directory functions instead of @pezkuwi/dev, so the sync and the checks run without installing the build toolchain. - The auto-approve, auto-merge and crosscheck workflows, and the IPFS job, depended on tokens and secrets this repository does not have; removed. The remaining workflows are pinned to commits, with no default permissions and a timeout on every job. * chore(lists): first sync with polkadot-js/phishing Written by scripts/syncUpstream.mjs from upstream bf5e7c6: 658 sites added; nodelinkapp.xyz, org.in and terra-claim.com unblocked by upstream; the five polkadot-js domains the rebrand had renamed blocked again; 816 duplicate "scam.xyz." entries folded into their names and the stray all/all.json gone. 54,051 blocked sites, 279 scam addresses. * fix(lists): commit the .build shard, and refuse a shard git ignores .gitignore held "build/", which also matched all/build/all.json, the shard of the .build top-level domain. Every sort wrote it and no commit held it, so the eight .build scam sites were never published; upstream has the same gap. The directory is excluded from the ignore rule, the shard is committed, and checkLists.mjs now fails on any shard git would ignore (with the old rule restored it does). Also clears the repository's lint on lists.test.mjs: imports sorted, and node:test's describe and it calls marked void. * test(lists): await describe and it, as the repository's lint requires no-floating-promises requires the promises node:test's describe and it return to be handled, and no-void forbids handling them with void. Awaited, the 25 cases run as before.
This commit is contained in:
1 parent
70bbc740cf
commit
767a92904d
112 files changed
+5311
-4811
No files matched your search
@@ -0,0 +1,94 @@
|
||||
// Copyright 2020-2026 @pezkuwi/phishing authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// node scripts/checkLists.mjs [dir]
|
||||
//
|
||||
// Whether the committed lists can be published: well formed, sharded the way the package reads
|
||||
// them, none of our own domains blocked, our own entries in place, and every file exactly what
|
||||
// sortAll writes. CI runs it on every change; the daily sync must pass it before it commits.
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { problems, readLists, readOverlay } from './lists.mjs';
|
||||
|
||||
const dir = path.resolve(process.argv[2] ?? '.');
|
||||
const lists = readLists(dir);
|
||||
const found = problems(lists, readOverlay(dir));
|
||||
|
||||
// A shard on disk is not a shard on master: .gitignore held "build/", so all/build/all.json was
|
||||
// written by every sort and committed by none, and the host publishes what git holds.
|
||||
const shardFiles = Object.keys(lists.shards).map((top) => path.join('all', top, 'all.json'));
|
||||
const ignored = spawnSync('git', ['check-ignore', '--', ...shardFiles], { cwd: dir, encoding: 'utf8' });
|
||||
|
||||
if (ignored.status === 0) {
|
||||
for (const file of ignored.stdout.trim().split('\n')) {
|
||||
found.push(`${file} is ignored by git and would never be published`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Every file under a directory, relative path to contents.
|
||||
* @param {string} root
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
function tree (root) {
|
||||
/** @type {Record<string, string>} */
|
||||
const files = {};
|
||||
|
||||
for (const entry of fs.readdirSync(root, { recursive: true, withFileTypes: true })) {
|
||||
if (entry.isFile()) {
|
||||
const full = path.join(entry.parentPath, entry.name);
|
||||
|
||||
files[path.relative(root, full)] = fs.readFileSync(full, 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
// sortAll is the one writer of these files. Run it on a copy: if the copy comes out different,
|
||||
// the committed files were edited into a shape the next sync would rewrite, or the metadata is
|
||||
// missing entries that sortAll would stamp with today's date on every run.
|
||||
const copy = fs.mkdtempSync(path.join(os.tmpdir(), 'phishing-canonical-'));
|
||||
|
||||
try {
|
||||
for (const name of ['all.json', 'address.json', 'meta']) {
|
||||
fs.cpSync(path.join(dir, name), path.join(copy, name), { recursive: true });
|
||||
}
|
||||
|
||||
const sortAll = fileURLToPath(new URL('./sortAll.mjs', import.meta.url));
|
||||
const run = spawnSync(process.execPath, [sortAll], { cwd: copy, encoding: 'utf8' });
|
||||
|
||||
if (run.status !== 0) {
|
||||
found.push(`sortAll failed: ${run.stderr.trim()}`);
|
||||
} else {
|
||||
for (const name of ['all.json', 'address.json']) {
|
||||
if (fs.readFileSync(path.join(dir, name), 'utf8') !== fs.readFileSync(path.join(copy, name), 'utf8')) {
|
||||
found.push(`${name} is not in the form sortAll writes`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const name of ['all', 'meta']) {
|
||||
if (JSON.stringify(tree(path.join(dir, name))) !== JSON.stringify(tree(path.join(copy, name)))) {
|
||||
found.push(`${name}/ is not what sortAll writes`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
fs.rmSync(copy, { force: true, recursive: true });
|
||||
}
|
||||
|
||||
if (found.length) {
|
||||
for (const problem of found) {
|
||||
console.error(`FAIL ${problem}`);
|
||||
}
|
||||
|
||||
console.error(`${found.length} problem(s)`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`lists OK: ${lists.all.deny.length} blocked sites, ${Object.values(lists.address).flat().length} scam addresses, ${Object.keys(lists.shards).length} shards`);
|
||||
Reference in new issue
Block a user