diff --git a/.github/workflows/malware-scan.yml b/.github/workflows/malware-scan.yml
deleted file mode 100644
index 8e05957ae..000000000
--- a/.github/workflows/malware-scan.yml
+++ /dev/null
@@ -1,241 +0,0 @@
-name: Malware & Obfuscation Scan
-
-on:
- push:
- branches: ["**"]
- pull_request:
- branches: ["**"]
- # Allow manual triggering for ad-hoc full scans
- workflow_dispatch:
- inputs:
- scan_path:
- description: "Sub-directory to scan (leave blank for full repo)"
- required: false
- default: "."
-
-# Prevent concurrent scans on the same ref from stepping on each other
-concurrency:
- group: malware-scan-${{ github.ref }}
- cancel-in-progress: true
-
-permissions:
- contents: read
- # Needed if you later add GitHub Code Scanning / SARIF upload
- security-events: write
-
-jobs:
- malware-scan:
- name: Scan for malicious / obfuscated code
- runs-on: self-hosted
- timeout-minutes: 15
-
- steps:
- # ── 1. Checkout ────────────────────────────────────────────────────────
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- # Full history lets the scanner see every file, not just the diff.
- # For very large repos you can set fetch-depth: 1 to speed things up,
- # but you may miss injected files in unchanged paths.
- fetch-depth: 0
-
- # ── 2. Setup Node ──────────────────────────────────────────────────────
- - name: Set up Node.js
- uses: actions/setup-node@v4
- with:
- node-version: "20"
-
- # ── 3. Install scanner ─────────────────────────────────────────────────
- # The scanner is pure Node.js stdlib — no npm install needed.
- # We just copy the script into a known location inside the runner.
- - name: Install scanner script
- run: |
- mkdir -p "$RUNNER_TOOL_CACHE/malware-scanner"
- cp .github/scripts/scan-malware.js \
- "$RUNNER_TOOL_CACHE/malware-scanner/scan-malware.js"
- chmod +x "$RUNNER_TOOL_CACHE/malware-scanner/scan-malware.js"
-
- # ── 4. Run the scanner ─────────────────────────────────────────────────
- - name: Run malware scanner
- id: scan
- env:
- SCAN_JSON_OUT: ${{ runner.temp }}/scan-results.json
- run: |
- SCAN_PATH="${{ github.event.inputs.scan_path || '.' }}"
- echo "Scanning path: $SCAN_PATH"
- echo "────────────────────────────────────────"
-
- node "$RUNNER_TOOL_CACHE/malware-scanner/scan-malware.js" "$SCAN_PATH"
- # The script exits 0 (clean), 1 (threats found), or 2 (internal error).
- # We want the step to "succeed" so the upload-artifact step always runs,
- # but we'll fail the job in the gate step below.
- continue-on-error: true
-
- # ── 5. Upload JSON report as artifact (always, even on failure) ────────
- # Retained so the full per-file, per-rule detail is always downloadable.
- # The Telegram message below links directly to the Actions run where
- # this artifact appears.
- - name: Upload scan report artifact
- if: always()
- uses: actions/upload-artifact@v4
- with:
- name: malware-scan-report-${{ github.sha }}
- path: ${{ runner.temp }}/scan-results.json
- retention-days: 90
- if-no-files-found: ignore
-
- # ── 6. Send Telegram notification (always, even on failure) ──────────────
- # Requires two repository secrets:
- # TELEGRAM_BOT_TOKEN — from @BotFather (format: 123456:ABC-xxx)
- # TELEGRAM_CHAT_ID — target chat/channel ID (format: -100xxxxxxxxxx)
- #
- # Intentionally short — plain HTML mode, no code spans, no snippets.
- # All special characters that would break MarkdownV2 are avoided entirely.
- # Full details are in the artifact linked via the Actions run URL.
- - name: Send Telegram notification
- if: always()
- env:
- TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
- TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
- SCAN_JSON: ${{ runner.temp }}/scan-results.json
- GH_REPO: ${{ github.repository }}
- GH_SHA: ${{ github.sha }}
- GH_REF: ${{ github.ref_name }}
- GH_ACTOR: ${{ github.actor }}
- GH_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
- run: |
- node - << 'EOF'
- const fs = require('fs');
- const https = require('https');
-
- const token = process.env.TELEGRAM_BOT_TOKEN;
- const chatId = process.env.TELEGRAM_CHAT_ID;
- const repo = process.env.GH_REPO;
- const sha = process.env.GH_SHA.slice(0, 7);
- const ref = process.env.GH_REF;
- const actor = process.env.GH_ACTOR;
- const runUrl = process.env.GH_RUN_URL;
-
- // HTML-escape only the four characters HTML cares about.
- // Using HTML parse_mode means code snippets, file paths, and rule IDs
- // with special characters can never break the parser.
- const h = s => String(s)
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"');
-
- let findings = [];
- try {
- findings = JSON.parse(fs.readFileSync(process.env.SCAN_JSON, 'utf8'));
- } catch { /* missing file = clean run or scanner error */ }
-
- const counts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
- for (const f of findings) counts[f.severity] = (counts[f.severity] || 0) + 1;
-
- const isClean = findings.length === 0;
-
- // ── Unique affected files ──────────────────────────────────────────
- const affectedFiles = [...new Set(findings.map(f => f.file))];
-
- // ── Build a short, fixed-size message ─────────────────────────────
- // No snippets, no descriptions — just counts, affected files, and a
- // direct link to the artifact. Stays well under 500 chars.
- let lines = [];
-
- if (isClean) {
- lines.push('✅ Malware Scan — Clean');
- } else {
- lines.push('🚨 Malware Scan — THREATS DETECTED');
- }
-
- lines.push('');
- lines.push(`Repo: ${h(repo)}`);
- lines.push(`Branch: ${h(ref)} Commit: ${h(sha)}`);
- lines.push(`Actor: ${h(actor)}`);
-
- if (!isClean) {
- lines.push('');
- lines.push(
- `Findings: ` +
- `🔴 ${counts.CRITICAL} CRITICAL ` +
- `🟠 ${counts.HIGH} HIGH ` +
- `🟡 ${counts.MEDIUM} MEDIUM ` +
- `⚪ ${counts.LOW} LOW`
- );
- lines.push('');
- lines.push(`Affected files (${affectedFiles.length}):`);
- // Cap at 10 files to keep the message short
- const shown = affectedFiles.slice(0, 10);
- for (const f of shown) lines.push(` • ${h(f)}`);
- if (affectedFiles.length > 10) {
- lines.push(` • … and ${affectedFiles.length - 10} more`);
- }
- }
-
- lines.push('');
- lines.push(`📋 View full run & download report artifact`);
-
- const text = lines.join('\n');
-
- // ── Send via Bot API (HTML parse mode) ────────────────────────────
- const body = JSON.stringify({
- chat_id: chatId,
- text,
- parse_mode: 'HTML',
- disable_web_page_preview: true,
- });
-
- const options = {
- hostname: 'api.telegram.org',
- path: `/bot${token}/sendMessage`,
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Content-Length': Buffer.byteLength(body),
- },
- };
-
- const req = https.request(options, res => {
- let data = '';
- res.on('data', chunk => data += chunk);
- res.on('end', () => {
- const parsed = JSON.parse(data);
- if (!parsed.ok) {
- console.error('Telegram API error:', JSON.stringify(parsed));
- process.exit(1);
- }
- console.log('Telegram notification sent successfully.');
- });
- });
- req.on('error', err => {
- console.error('Request failed:', err.message);
- process.exit(1);
- });
- req.write(body);
- req.end();
- EOF
-
- # ── 7. Gate — fail the workflow if threats were found ──────────────────
- # Runs after the Telegram step so the alert always fires first.
- - name: Fail workflow if threats detected
- if: steps.scan.outcome == 'failure'
- run: |
- echo "::error::⛔ Malicious or highly-suspicious code patterns were detected."
- echo "::error::Check your Telegram channel for the summary."
- echo "::error::Download the 'malware-scan-report' artifact for full details."
- echo "::error::Do NOT merge or deploy this branch until findings are reviewed."
- exit 1
-
- # ── 7. (Optional) Diff-only scan on PRs for faster feedback ───────────
- # Uncomment this block if you want a second, faster pass that only
- # checks the files changed in the PR diff.
- #
- # - name: Diff-only scan (PR only)
- # if: github.event_name == 'pull_request'
- # env:
- # SCAN_JSON_OUT: ${{ runner.temp }}/scan-results-diff.json
- # run: |
- # git diff --name-only origin/${{ github.base_ref }}...HEAD \
- # | grep -E '\.(js|cjs|mjs|ts|tsx|jsx)$' \
- # | xargs -I{} node "$RUNNER_TOOL_CACHE/malware-scanner/scan-malware.js" {}