mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
244 lines
12 KiB
YAML
244 lines
12 KiB
YAML
name: PolinRider Malware Scan
|
|
|
|
# ── Triggers ──────────────────────────────────────────────────────────────────
|
|
# Runs on every push and every PR targeting main/master/develop.
|
|
# Also available as a manual trigger (workflow_dispatch) and on a nightly
|
|
# schedule so dormant infections in older branches are caught too.
|
|
on:
|
|
push:
|
|
branches: ["**"]
|
|
pull_request:
|
|
branches: ["**"]
|
|
schedule:
|
|
# Nightly full-repo scan at 02:00 UTC
|
|
- cron: "0 2 * * *"
|
|
workflow_dispatch:
|
|
|
|
# ── Permissions ───────────────────────────────────────────────────────────────
|
|
permissions:
|
|
contents: read # checkout
|
|
security-events: write # upload SARIF to GitHub Security tab
|
|
actions: read
|
|
checks: write # annotate PRs with scan findings
|
|
|
|
# ── Deployment gate ───────────────────────────────────────────────────────────
|
|
# All other jobs (build, test, deploy) should list this job under `needs:`.
|
|
# If this job fails (exit code 1 from the scanner), the whole workflow stops.
|
|
jobs:
|
|
polinrider-scan:
|
|
name: "PolinRider / Famous Chollima Scan"
|
|
runs-on: ubuntu-latest
|
|
# Prevent CI from being disabled by any workflow override
|
|
if: always()
|
|
|
|
steps:
|
|
# ── 1. Checkout full history ─────────────────────────────────────────────
|
|
# Full depth so we can inspect recent commits for temp_auto_push.bat traces
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
# ── 2. Detect suspicious force-push patterns in git history ──────────────
|
|
- name: Check git history for force-push and timestamp manipulation
|
|
id: git-check
|
|
shell: bash
|
|
run: |
|
|
echo "=== Checking for suspicious git history patterns ==="
|
|
|
|
# Check for .gitignore entries hiding known malware artifacts
|
|
GITIGNORE_HITS=0
|
|
if [ -f .gitignore ]; then
|
|
for pattern in "branch_structure.json" "temp_auto_push.bat" "temp_interactive_push.bat"; do
|
|
if grep -qF "$pattern" .gitignore 2>/dev/null; then
|
|
echo "::warning file=.gitignore::SUSPICIOUS: .gitignore hides known PolinRider artifact: $pattern"
|
|
GITIGNORE_HITS=$((GITIGNORE_HITS + 1))
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Check if malware persistence artifacts exist anywhere in the tree
|
|
ARTIFACTS_FOUND=0
|
|
for artifact in "temp_auto_push.bat" "temp_interactive_push.bat" "branch_structure.json"; do
|
|
FOUND=$(find . -name "$artifact" -not -path "./.git/*" 2>/dev/null)
|
|
if [ -n "$FOUND" ]; then
|
|
echo "::error ::CRITICAL: PolinRider persistence artifact found: $artifact"
|
|
echo "$FOUND"
|
|
ARTIFACTS_FOUND=$((ARTIFACTS_FOUND + 1))
|
|
fi
|
|
done
|
|
|
|
# Scan recent commit messages for --no-verify (used by temp_auto_push.bat)
|
|
NO_VERIFY_COMMITS=$(git log --oneline -50 --format="%H %s" 2>/dev/null | grep -i "no.verify\|force.*push\|amend" || true)
|
|
if [ -n "$NO_VERIFY_COMMITS" ]; then
|
|
echo "::warning ::Recent commits with suspicious metadata (--no-verify / force amend patterns):"
|
|
echo "$NO_VERIFY_COMMITS"
|
|
fi
|
|
|
|
# Check for .woff2 files with unusually large sizes (>50KB is suspicious)
|
|
find . -name "*.woff2" -not -path "./.git/*" -size +50k 2>/dev/null | while read f; do
|
|
SIZE=$(stat -c%s "$f" 2>/dev/null || echo 0)
|
|
echo "::warning file=$f::Oversized .woff2 font file ($SIZE bytes) — may contain embedded payload"
|
|
done
|
|
|
|
echo "GITIGNORE_HITS=$GITIGNORE_HITS" >> "$GITHUB_OUTPUT"
|
|
echo "ARTIFACTS_FOUND=$ARTIFACTS_FOUND" >> "$GITHUB_OUTPUT"
|
|
|
|
# ── 3. Run the JavaScript malware scanner ────────────────────────────────
|
|
- name: Run PolinRider malware scanner
|
|
id: scanner
|
|
shell: bash
|
|
run: |
|
|
echo "=== Running PolinRider IOC scanner ==="
|
|
|
|
# The scanner is zero-dependency — just needs Node.js (always present on ubuntu-latest)
|
|
node .github/scripts/scan.js \
|
|
--json \
|
|
--output scan-report.json \
|
|
.
|
|
|
|
SCANNER_EXIT=$?
|
|
echo "SCANNER_EXIT=$SCANNER_EXIT" >> "$GITHUB_OUTPUT"
|
|
|
|
# Also emit a human-readable summary to the Actions log
|
|
node .github/scripts/scan.js . || true
|
|
|
|
exit $SCANNER_EXIT
|
|
|
|
# ── 4. Upload scan report as artifact ────────────────────────────────────
|
|
# - name: Upload scan report
|
|
# if: always()
|
|
# uses: actions/upload-artifact@v4
|
|
# with:
|
|
# name: polinrider-scan-report
|
|
# path: scan-report.json
|
|
# retention-days: 90
|
|
|
|
# # ── 5. Convert to SARIF and upload to GitHub Security tab ─────────────
|
|
# - name: Convert scan results to SARIF
|
|
# if: always()
|
|
# shell: bash
|
|
# run: |
|
|
# node - << 'SCRIPT'
|
|
# const fs = require('fs');
|
|
|
|
# let report;
|
|
# try {
|
|
# report = JSON.parse(fs.readFileSync('scan-report.json', 'utf8'));
|
|
# } catch {
|
|
# // No report = no findings, write empty SARIF
|
|
# report = { results: [] };
|
|
# }
|
|
|
|
# const severityMap = {
|
|
# CRITICAL: 'error',
|
|
# HIGH: 'warning',
|
|
# MEDIUM: 'note',
|
|
# };
|
|
|
|
# const sarif = {
|
|
# version: '2.1.0',
|
|
# $schema: 'https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json',
|
|
# runs: [{
|
|
# tool: {
|
|
# driver: {
|
|
# name: 'PolinRider Malware Scanner',
|
|
# version: '1.0.0',
|
|
# informationUri: 'https://github.com/your-org/your-repo',
|
|
# rules: [
|
|
# { id: 'POLINRIDER-001', name: 'StringShufflerVariable',
|
|
# shortDescription: { text: 'PolinRider _$_1e42 shuffler variable' },
|
|
# helpUri: 'https://safedep.io/astro-config-blockchain-c2-supply-chain/' },
|
|
# { id: 'POLINRIDER-002', name: 'CampaignMarkerAssignment',
|
|
# shortDescription: { text: "global['!'] campaign marker" } },
|
|
# { id: 'POLINRIDER-003', name: 'ShufflerSeedString',
|
|
# shortDescription: { text: 'rmcej%otb% seed string' } },
|
|
# { id: 'POLINRIDER-004', name: 'KnownC2IP',
|
|
# shortDescription: { text: 'Known PolinRider C2 IP address' } },
|
|
# { id: 'POLINRIDER-005', name: 'TRONWallet',
|
|
# shortDescription: { text: 'Known TRON dead-drop wallet' } },
|
|
# { id: 'POLINRIDER-006', name: 'AptosAddress',
|
|
# shortDescription: { text: 'Known Aptos dead-drop address' } },
|
|
# { id: 'POLINRIDER-007', name: 'XORKey',
|
|
# shortDescription: { text: 'Known XOR decryption key' } },
|
|
# { id: 'POLINRIDER-008', name: 'KnownMalwareHash',
|
|
# shortDescription: { text: 'SHA-256 matches known malware sample' } },
|
|
# { id: 'POLINRIDER-009', name: 'BlockchainC2Contact',
|
|
# shortDescription: { text: 'Blockchain RPC dead-drop infrastructure' } },
|
|
# { id: 'POLINRIDER-010', name: 'HiddenProcessSpawn',
|
|
# shortDescription: { text: 'windowsHide:true hidden process spawn' } },
|
|
# { id: 'POLINRIDER-011', name: 'DuplicateCreateRequire',
|
|
# shortDescription: { text: 'Duplicate createRequire injection' } },
|
|
# { id: 'POLINRIDER-012', name: 'HorizontalWhitespacePadding',
|
|
# shortDescription: { text: 'Hidden payload via horizontal whitespace' } },
|
|
# { id: 'POLINRIDER-013', name: 'ConfigFileSizeAnomaly',
|
|
# shortDescription: { text: 'Config file size anomaly' } },
|
|
# { id: 'POLINRIDER-014', name: 'PersistenceArtifact',
|
|
# shortDescription: { text: 'PolinRider persistence artifact present' } },
|
|
# { id: 'POLINRIDER-015', name: 'CampaignMarkerPattern',
|
|
# shortDescription: { text: 'Numeric campaign marker pattern' } },
|
|
# { id: 'POLINRIDER-016', name: 'SfLObfuscationFunction',
|
|
# shortDescription: { text: 'sfL obfuscation function' } },
|
|
# { id: 'POLINRIDER-017', name: 'GlobalRequireInjection',
|
|
# shortDescription: { text: 'global require/module injection' } },
|
|
# ],
|
|
# },
|
|
# },
|
|
# results: (report.results || []).flatMap(file =>
|
|
# (file.findings || []).map(finding => ({
|
|
# ruleId: finding.id,
|
|
# level: severityMap[finding.severity] || 'warning',
|
|
# message: { text: finding.description + ' — ' + finding.matches.join('; ') },
|
|
# locations: [{
|
|
# physicalLocation: {
|
|
# artifactLocation: { uri: file.filePath.replace(/^\.\//,''), uriBaseId: '%SRCROOT%' },
|
|
# region: { startLine: 1 },
|
|
# },
|
|
# }],
|
|
# }))
|
|
# ),
|
|
# }],
|
|
# };
|
|
|
|
# fs.writeFileSync('scan-results.sarif', JSON.stringify(sarif, null, 2));
|
|
# console.log('SARIF written.');
|
|
# SCRIPT
|
|
|
|
# - name: Upload SARIF to GitHub Security tab
|
|
# if: always()
|
|
# uses: github/codeql-action/upload-sarif@v3
|
|
# with:
|
|
# sarif_file: scan-results.sarif
|
|
# category: polinrider-malware-scan
|
|
|
|
# ── 6. Block deployment if infected ──────────────────────────────────────
|
|
- name: Enforce clean-scan gate
|
|
if: steps.scanner.outputs.SCANNER_EXIT == '1' || steps.git-check.outputs.ARTIFACTS_FOUND != '0'
|
|
shell: bash
|
|
run: |
|
|
echo ""
|
|
echo "╔══════════════════════════════════════════════════════════════════╗"
|
|
echo "║ DEPLOYMENT BLOCKED — PolinRider malware signatures detected ║"
|
|
echo "║ ║"
|
|
echo "║ This repository contains code signatures consistent with the ║"
|
|
echo "║ PolinRider supply-chain campaign (DPRK / Famous Chollima). ║"
|
|
echo "║ ║"
|
|
echo "║ DO NOT run npm install, build, or deploy until remediated. ║"
|
|
echo "║ ║"
|
|
echo "║ See scan-report.json artifact for full details. ║"
|
|
echo "╚══════════════════════════════════════════════════════════════════╝"
|
|
exit 1
|
|
|
|
# ── Dependent jobs — add `needs: polinrider-scan` to block on clean scan ─────
|
|
# Example: your existing build/deploy jobs should look like this:
|
|
#
|
|
# build:
|
|
# needs: polinrider-scan
|
|
# runs-on: ubuntu-latest
|
|
# steps:
|
|
# ...
|
|
#
|
|
# deploy:
|
|
# needs: [polinrider-scan, build]
|
|
# ...
|