Repository navigation
166 lines (152 loc) · 8.05 KB
/
Copy pathmalware-scan.yml
File metadata and controls
166 lines (152 loc) · 8.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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