Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions .github/workflows/malware-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
name: PolinRider Malware Scan

on:
push:
branches: ["**"]
pull_request:
branches: ["**"]

jobs:
malware-scan:
name: Scan for PolinRider malware indicators
runs-on: ubuntu-latest
steps:
- name: Checkout (full history for rewritten-commit detection)
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Run PolinRider malware scan
shell: bash
run: |
set +e
FAIL=0

# ── Helpers ─────────────────────────────────────────────────────────
flag() {
echo "::error file=$1::$2"
FAIL=1
}

# ── 1. Known malware string markers ─────────────────────────────────
echo "--- Check 1: known malware strings ---"
while IFS= read -r match; do
[ -z "$match" ] && continue
file=$(echo "$match" | cut -d: -f1)
flag "$file" "Malware marker found: $match"
done < <(
grep -rn \
-e 'rmcej%otb%' \
-e 'Cot%3t=shtP' \
-e '_\$_1e42' \
--include='*.js' --include='*.ts' --include='*.mjs' \
--include='*.cjs' --include='*.json' --include='*.html' \
--include='*.sh' \
. 2>/dev/null | grep -v '^\./.git/'
)

# ── 2. .vscode/tasks.json with runOn: folderOpen ─────────────────────
echo "--- Check 2: .vscode/tasks.json runOn folderOpen ---"
find . -path '*/.vscode/tasks.json' ! -path '*/.git/*' | while read -r f; do
if grep -q '"runOn"' "$f" && grep -q 'folderOpen' "$f"; then
flag "$f" "Suspicious .vscode/tasks.json with runOn:folderOpen — classic PolinRider trigger"
fi
done

# ── 3. .vscode/settings.json with allowAutomaticTasks ────────────────
echo "--- Check 3: .vscode/settings.json allowAutomaticTasks ---"
find . -path '*/.vscode/settings.json' ! -path '*/.git/*' | while read -r f; do
if grep -q 'allowAutomaticTasks' "$f" && grep -q '"on"' "$f"; then
flag "$f" "task.allowAutomaticTasks is enabled — required to auto-run malicious VSCode tasks"
fi
done

# ── 4. Suspicious font files ─────────────────────────────────────────
echo "--- Check 4: suspicious font files ---"
while IFS= read -r f; do
flag "$f" "Suspicious font file: matches known PolinRider payload filenames"
done < <(
find . ! -path '*/.git/*' \( \
-name 'fa-solid-500.woff2' -o \
-name 'fa-solid-600.eot' -o \
-name 'fa-solid.lfk' -o \
-name 'fa-solid-300.llf' -o \
-name '*.lfk' -o \
-name '*.llf' \
\) 2>/dev/null
)

# ── 5. dist/setup.js ─────────────────────────────────────────────────
echo "--- Check 5: dist/setup.js ---"
find . -path '*/dist/setup.js' ! -path '*/.git/*' | while read -r f; do
flag "$f" "dist/setup.js present — PolinRider uses this as a dropper payload"
done

# ── 6. package.json preinstall running dist/setup.js ─────────────────
echo "--- Check 6: package.json preinstall hook ---"
find . -name 'package.json' ! -path '*/.git/*' ! -path '*/node_modules/*' | while read -r f; do
if python3 -c "
import json, sys
try:
d = json.load(open('$f'))
pre = d.get('scripts', {}).get('preinstall', '')
sys.exit(0 if ('setup.js' in pre or 'dist/setup' in pre) else 1)
except: sys.exit(1)
" 2>/dev/null; then
flag "$f" "preinstall script references dist/setup.js — PolinRider runs payload on npm install"
fi
done

# ── 7. api.js in repo root / src, plus start/dev prepended with it ───
echo "--- Check 7: rogue api.js with hijacked dev/start script ---"
find . -maxdepth 3 -name 'api.js' ! -path '*/.git/*' ! -path '*/node_modules/*' | while read -r f; do
dir=$(dirname "$f")
pkg="$dir/package.json"
if [ -f "$pkg" ]; then
if python3 -c "
import json, sys
try:
d = json.load(open('$pkg'))
scripts = d.get('scripts', {})
for key in ('dev', 'start'):
if scripts.get(key, '').startswith('node api.js'):
sys.exit(0)
sys.exit(1)
except: sys.exit(1)
" 2>/dev/null; then
flag "$f" "api.js present and dev/start script begins with 'node api.js' — PolinRider execution vector"
fi
fi
done

# ── 8. Config files with trailing code after export default ──────────
echo "--- Check 8: config files with appended code after export ---"
for pattern in \
'postcss.config.*' 'tailwind.config.*' 'eslint.config.*' \
'next.config.*' 'jest.config.*' 'vite.config.*'; do
find . -name "$pattern" ! -path '*/.git/*' ! -path '*/node_modules/*' | while read -r f; do
# Get the byte offset of last export default / module.exports
last_export=$(grep -n 'export default\|module\.exports' "$f" | tail -1 | cut -d: -f1)
[ -z "$last_export" ] && continue
total=$(wc -l < "$f")
# Allow up to 10 lines after the last export statement (closing braces, etc.)
tail_lines=$(( total - last_export ))
if [ "$tail_lines" -gt 10 ]; then
# Check if the extra lines contain non-whitespace, non-comment content
extra=$(tail -n "$tail_lines" "$f" | grep -v '^\s*$' | grep -v '^\s*//' | grep -c '.')
if [ "$extra" -gt 3 ]; then
flag "$f" "Suspicious: $extra non-empty lines after 'export default' — PolinRider hides payload after config exports"
fi
fi
done
done

# ── 9. Binary blobs disguised as font files ──────────────────────────
echo "--- Check 9: binary files with font extensions that are not fonts ---"
find . ! -path '*/.git/*' ! -path '*/node_modules/*' \
\( -name '*.woff' -o -name '*.woff2' -o -name '*.eot' -o -name '*.ttf' \) \
| while read -r f; do
# Real font files start with known magic bytes; node/JS starts with common ASCII
first_bytes=$(xxd -l 4 "$f" 2>/dev/null | awk '{print $2$3}' | head -1)
# wOFF = 774f4646, wOF2 = 774f4632, OTTO = 4f54544f, true = 00010000, ttcf = 74746366
# If it starts with something else entirely (like 0a2f2f 0a76617 etc = JS/text), flag it
case "$first_bytes" in
774f4646*|774f4632*|4f54544f*|00010000*|74746366*|000100*) ;; # valid font magic
*) flag "$f" "File has font extension but does not have valid font magic bytes — possible binary payload disguised as font" ;;
esac
done

# ── Summary ──────────────────────────────────────────────────────────
echo ""
if [ "$FAIL" -eq 0 ]; then
echo "✅ No PolinRider malware indicators found."
else
echo "🚨 One or more malware indicators were detected. See annotations above."
exit 1
fi
Loading