302 lines
11 KiB
Bash
Executable File
302 lines
11 KiB
Bash
Executable File
#!/bin/sh
|
|
# script/check-censored: assert that the competitor name RULES.md bars appears
|
|
# nowhere in this repo, and nowhere in the built extension, except where it is
|
|
# deliberate. Our own extension to scripts-to-rule-them-all, run from
|
|
# script/check and from make build.
|
|
#
|
|
# Where the name is allowed, and why each one is not negotiable away:
|
|
#
|
|
# - script/vendor-blocklist. Build-time tooling, never shipped. A pinned
|
|
# source reference that does not say what the source is cannot be verified
|
|
# by anyone, so it names it. Whole-file exemption.
|
|
# - the two provider-shim identifiers in src/content/inpage.js. Protocol
|
|
# identifiers dApps feature-detect on; renaming them does not rename them in
|
|
# their code, it only stops this wallet working on their sites.
|
|
# - the on-chain name of the MUSD ERC-20 in src/shared/tokenList.js. It is not
|
|
# what backs symbol-spoof detection — that reads symbol and address — but
|
|
# the wallet already surfaces the on-chain name of any token the user holds
|
|
# (src/shared/balances.js), and this contract's on-chain name is that
|
|
# string, so censoring the repo cannot stop the wallet displaying it.
|
|
# Dropping the entry instead would cost the user MUSD spoof detection.
|
|
#
|
|
# Everything else fails, in the working tree and under dist/. The last two are
|
|
# literals rather than whole files, so they are enforced by counting, and each
|
|
# literal is scoped to the path allowed to carry it: a file may contain the name
|
|
# only as many times as it contains the literals permitted *there*, and zero
|
|
# times anywhere else. The emitted bundles carry them too, so a plain "the name
|
|
# must not appear in dist/" could never have passed.
|
|
#
|
|
# The name itself is not written in this file. script/vendor-blocklist is the
|
|
# one place in this repo that defines it, and this reads it back out of there —
|
|
# so the repo-wide grep this check exists to enforce keeps returning exactly the
|
|
# files named above, and this file is not one of them.
|
|
set -eu
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|
|
|
# Absolute path to this script, resolved before anything cd's anywhere: the
|
|
# scan half runs in a re-invocation through xargs, so that the paths it works on
|
|
# arrive as arguments and cannot be reshaped by field splitting on the way in.
|
|
SELF="$(cd "$(dirname "$0")" && pwd -P)/$(basename "$0")"
|
|
|
|
# Internal re-entry flag. Not part of the command-line interface.
|
|
SCAN_FLAG="--scan-paths"
|
|
|
|
VENDOR_SCRIPT="$ROOT/script/vendor-blocklist"
|
|
|
|
# Set by extract_name / make_literals_file.
|
|
NAME=""
|
|
ALLOWED_LITERALS_FILE=""
|
|
|
|
FAILED=0
|
|
|
|
cleanup() {
|
|
[ -z "$ALLOWED_LITERALS_FILE" ] || rm -f "$ALLOWED_LITERALS_FILE"
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
fail() {
|
|
echo "check-censored: FAIL: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
# The name, taken from the single place that defines it. A check scanning for a
|
|
# pattern it failed to read would pass against anything, so this refuses to
|
|
# continue unless it got something that looks like the definition.
|
|
extract_name() {
|
|
[ -f "$VENDOR_SCRIPT" ] ||
|
|
fail "$VENDOR_SCRIPT is missing, and it is where the name being
|
|
checked for is defined. Nothing was scanned."
|
|
|
|
NAME="$(grep -m1 '^UPSTREAM_ORG=' "$VENDOR_SCRIPT" | cut -d'"' -f2)" ||
|
|
fail "could not read UPSTREAM_ORG from $VENDOR_SCRIPT. Nothing was
|
|
scanned."
|
|
|
|
case "$NAME" in
|
|
"" | *[!A-Za-z0-9]*)
|
|
fail "UPSTREAM_ORG in $VENDOR_SCRIPT did not yield a plain name
|
|
(got: '$NAME'). Scanning for that would prove nothing. Nothing was
|
|
scanned."
|
|
;;
|
|
esac
|
|
}
|
|
|
|
make_literals_file() {
|
|
ALLOWED_LITERALS_FILE="$(mktemp \
|
|
"${TMPDIR:-/tmp}/autistmask-censored.XXXXXX")" ||
|
|
fail "could not create a temporary file, so nothing was scanned."
|
|
}
|
|
|
|
# The literals $1 may carry, and nothing else may. Each contains the name
|
|
# exactly once, which is what makes counting them sound; each is scoped to its
|
|
# path, so a file with no business carrying the name fails even when it spells
|
|
# it the way shipped code has to. Scoping is the point: permitting these
|
|
# literals in any file is what once let this check pass its own prose.
|
|
#
|
|
# The emitted paths are listed next to the sources they come from. If the
|
|
# bundler moves one, this goes red and the new path gets added deliberately,
|
|
# rather than a wildcard over dist/ covering whatever lands there.
|
|
allowed_literals_for() {
|
|
: >"$ALLOWED_LITERALS_FILE"
|
|
case "$1" in
|
|
src/content/inpage.js | dist/*/src/content/inpage.js)
|
|
printf 'is%s\n_%s\n' "$NAME" "$NAME" >"$ALLOWED_LITERALS_FILE"
|
|
;;
|
|
src/shared/tokenList.js | dist/*/src/background/index.js | \
|
|
dist/*/src/popup/index.js)
|
|
printf '%s USD\n' "$NAME" >"$ALLOWED_LITERALS_FILE"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# How many times does $1 contain the name (TOTAL), and how many of those are one
|
|
# of the allowed literals (ALLOWED)? Same discipline the rest of this repo's
|
|
# shell checks apply to grep: exit 0 and 1 are answers about the file, anything
|
|
# else means the file was not searched and is not an answer at all.
|
|
count_matches() {
|
|
_cm_status=0
|
|
_cm_out="$(grep -a -o -i -F -e "$NAME" -- "$1")" || _cm_status=$?
|
|
case "$_cm_status" in
|
|
0) TOTAL="$(printf '%s\n' "$_cm_out" | grep -c .)" ;;
|
|
1) TOTAL=0 ;;
|
|
*)
|
|
fail "grep exited $_cm_status reading $1, so the file was never
|
|
searched and nothing was established about it. That is a permissions or I/O
|
|
fault, not a clean file. Refusing to report success."
|
|
;;
|
|
esac
|
|
|
|
if [ "$TOTAL" -eq 0 ]; then
|
|
ALLOWED=0
|
|
return 0
|
|
fi
|
|
|
|
# No literal is permitted at this path, so every occurrence is a violation.
|
|
# Handled here rather than by grep, which is not required to say anything
|
|
# useful about an empty pattern file.
|
|
if [ ! -s "$ALLOWED_LITERALS_FILE" ]; then
|
|
ALLOWED=0
|
|
return 0
|
|
fi
|
|
|
|
_cm_status=0
|
|
_cm_out="$(grep -a -o -i -F -f "$ALLOWED_LITERALS_FILE" -- "$1")" ||
|
|
_cm_status=$?
|
|
case "$_cm_status" in
|
|
0) ALLOWED="$(printf '%s\n' "$_cm_out" | grep -c .)" ;;
|
|
1) ALLOWED=0 ;;
|
|
*)
|
|
fail "grep exited $_cm_status matching the allowed literals in $1.
|
|
Refusing to report success."
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# The per-path half, run in a re-invocation of this script so it uses the same
|
|
# counting as everything else rather than a second copy of it.
|
|
scan_paths() {
|
|
for _file in "$@"; do
|
|
# dist/ arrives absolute (find) and the worktree relative (git
|
|
# ls-files). The allowlist is keyed on repo-relative paths, so both
|
|
# forms are reduced to one before anything is decided about them.
|
|
_rel="$_file"
|
|
case "$_rel" in
|
|
"$ROOT"/*) _rel="${_rel#"$ROOT"/}" ;;
|
|
esac
|
|
|
|
case "$_rel" in
|
|
script/vendor-blocklist) continue ;;
|
|
esac
|
|
[ -f "$_file" ] || continue
|
|
|
|
allowed_literals_for "$_rel"
|
|
count_matches "$_file"
|
|
[ "$TOTAL" -gt "$ALLOWED" ] || continue
|
|
|
|
FAILED=$((FAILED + 1))
|
|
echo "check-censored: $_rel: $TOTAL occurrence(s) of the name," \
|
|
"$ALLOWED of them allowed at this path" >&2
|
|
grep -a -n -i -F -e "$NAME" -- "$_file" | cut -c1-140 | head -5 >&2
|
|
done
|
|
[ "$FAILED" -eq 0 ]
|
|
}
|
|
|
|
# Hand a NUL-delimited listing to the scan half. Returns non-zero if any path
|
|
# failed, or if the scan could not be run at all.
|
|
scan_listing() {
|
|
xargs -0 "$SELF" "$SCAN_FLAG" <"$1"
|
|
}
|
|
|
|
# Every file git tracks, plus everything untracked and not ignored: the working
|
|
# tree as a reviewer would see it, and never node_modules or dist/ (both are
|
|
# ignored; dist/ is walked separately below).
|
|
check_worktree() {
|
|
_list="$(mktemp "${TMPDIR:-/tmp}/autistmask-censored-tree.XXXXXX")" ||
|
|
fail "could not create a temporary file, so nothing was scanned."
|
|
_status=0
|
|
git ls-files -z --cached --others --exclude-standard >"$_list" ||
|
|
_status=$?
|
|
[ "$_status" -eq 0 ] || {
|
|
rm -f "$_list"
|
|
fail "git ls-files exited $_status, so the working tree was never
|
|
enumerated and nothing was established about it."
|
|
}
|
|
|
|
# Repo-relative paths. The scan half cd's to the repo root before it opens
|
|
# anything, so they reach it intact and unjoined.
|
|
WORKTREE_COUNT="$(tr -dc '\0' <"$_list" | wc -c | tr -d ' ')"
|
|
|
|
_status=0
|
|
scan_listing "$_list" || _status=$?
|
|
rm -f "$_list"
|
|
return "$_status"
|
|
}
|
|
|
|
check_dist() {
|
|
_list="$(mktemp "${TMPDIR:-/tmp}/autistmask-censored-dist.XXXXXX")" ||
|
|
fail "could not create a temporary file, so dist/ was not scanned."
|
|
_status=0
|
|
find "$ROOT/dist" -type f -print0 >"$_list" || _status=$?
|
|
[ "$_status" -eq 0 ] || {
|
|
rm -f "$_list"
|
|
fail "find exited $_status enumerating dist/, so part of the emitted
|
|
tree was never walked and an unchecked file there went unchecked. Refusing
|
|
to report success."
|
|
}
|
|
|
|
DIST_COUNT="$(tr -dc '\0' <"$_list" | wc -c | tr -d ' ')"
|
|
|
|
_status=0
|
|
scan_listing "$_list" || _status=$?
|
|
rm -f "$_list"
|
|
return "$_status"
|
|
}
|
|
|
|
usage() {
|
|
echo "usage: script/check-censored [--require-dist]" >&2
|
|
exit 2
|
|
}
|
|
|
|
main() {
|
|
cd "$ROOT"
|
|
|
|
# Internal re-entry from scan_listing's xargs.
|
|
if [ "${1-}" = "$SCAN_FLAG" ]; then
|
|
shift
|
|
extract_name
|
|
make_literals_file
|
|
scan_paths "$@"
|
|
return $?
|
|
fi
|
|
|
|
require_dist=no
|
|
case "${1-}" in
|
|
"") ;;
|
|
--require-dist) require_dist=yes ;;
|
|
*) usage ;;
|
|
esac
|
|
|
|
extract_name
|
|
make_literals_file
|
|
|
|
echo "Checking for censored names..."
|
|
|
|
tree_status=0
|
|
check_worktree || tree_status=$?
|
|
|
|
dist_status=0
|
|
dist_inspected=no
|
|
DIST_COUNT=0
|
|
if [ -d "$ROOT/dist" ]; then
|
|
dist_inspected=yes
|
|
check_dist || dist_status=$?
|
|
fi
|
|
|
|
if [ "$tree_status" -ne 0 ] || [ "$dist_status" -ne 0 ]; then
|
|
fail "the name appears outside the deliberate exceptions (reported
|
|
above). See the header of script/check-censored for what is allowed and
|
|
why."
|
|
fi
|
|
|
|
if [ "$dist_inspected" = no ]; then
|
|
if [ "$require_dist" = yes ]; then
|
|
fail "there is no dist/ to inspect and this run was asked to
|
|
require one. Run make build."
|
|
fi
|
|
cat <<EOF
|
|
################################################################################
|
|
## WARNING: dist/ WAS NOT INSPECTED BY THIS RUN AND IS NOT PROVEN CLEAN BY IT.
|
|
## There is no dist/ in this tree. The working tree is clean, but a build can
|
|
## carry text no source file does — a dependency's, or a bundler's. Every
|
|
## make build runs this check again with dist/ required, so a release artifact
|
|
## is always covered; this run simply had none to look at.
|
|
################################################################################
|
|
EOF
|
|
fi
|
|
|
|
echo "check-censored: $WORKTREE_COUNT tracked file(s) inspected," \
|
|
"$DIST_COUNT file(s) under dist/"
|
|
}
|
|
|
|
main "$@"
|