Skip to content

ci: add extended security scan workflow #4

ci: add extended security scan workflow

ci: add extended security scan workflow #4

Workflow file for this run

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