diff --git a/.github/workflows/cache_to_artifacts b/.github/workflows/cache_to_artifacts new file mode 100644 index 00000000000..f4f158ae6bc --- /dev/null +++ b/.github/workflows/cache_to_artifacts @@ -0,0 +1,258 @@ +name: Cache Forensics Extractor + +on: + workflow_dispatch: + inputs: + cache_entries: + description: 'Cache entries (JSON array format)' + required: true + type: string + default: '[ + { + "key": "n71Gg/JormzoitmBpVjBCZCcL6Y=", + "version": "0c867ee6264758fbca938e6c6d38a3160cb478f2770da2f831e22e4c9e3720d8" + } + { + "key": "PFxRDTsQC2CBRTRk3TMxWNYXnd0=", + "version": "4793076103aa823b0a4c97942d7385d4346f77a3c30a0bad6e0f1d748becbab5" + } +] +' + +jobs: + extract-cache: + runs-on: ubuntu-latest + + steps: + - name: Checkout (minimal) + uses: actions/checkout@v4 + with: + sparse-checkout: | + .github + sparse-checkout-cone-mode: false + + - name: Parse cache entries + id: parse + run: | + echo 'entries<> $GITHUB_OUTPUT + echo '${{ inputs.cache_entries }}' | jq -c '.[]' >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + + - name: Extract caches + run: | + mkdir -p cache-artifacts + + # Parse JSON array + echo '${{ inputs.cache_entries }}' | jq -c '.[]' | while read -r entry; do + key=$(echo "$entry" | jq -r '.key') + version=$(echo "$entry" | jq -r '.version') + + echo "================================================" + echo "Attempting to restore: $key (version: $version)" + echo "================================================" + + # Create unique directory for this cache + safe_name=$(echo "$key-$version" | tr '/' '_' | tr ':' '_') + cache_dir="cache-artifacts/$safe_name" + mkdir -p "$cache_dir" + + # Try to restore cache (will fail if not found, but won't stop workflow) + echo "Restoring to: $cache_dir" + + # Save metadata + cat > "$cache_dir/metadata.json" </dev/null; then + echo "✓ Successfully restored $key" + restored="true" + else + echo "✗ Failed to restore $key" + fi + + echo "$restored" > "$cache_dir/restore_status.txt" + echo "::endgroup::" + done + env: + GH_TOKEN: ${{ github.token }} + + - name: Analyze extracted caches + run: | + echo "# Cache Extraction Report" > cache-artifacts/REPORT.md + echo "" >> cache-artifacts/REPORT.md + echo "Extraction Time: $(date -u)" >> cache-artifacts/REPORT.md + echo "" >> cache-artifacts/REPORT.md + + for dir in cache-artifacts/*/; do + if [ -d "$dir" ]; then + echo "## $(basename "$dir")" >> cache-artifacts/REPORT.md + echo "" >> cache-artifacts/REPORT.md + + # Show metadata + if [ -f "$dir/metadata.json" ]; then + echo "### Metadata" >> cache-artifacts/REPORT.md + echo '```json' >> cache-artifacts/REPORT.md + cat "$dir/metadata.json" >> cache-artifacts/REPORT.md + echo '```' >> cache-artifacts/REPORT.md + echo "" >> cache-artifacts/REPORT.md + fi + + # Show restore status + if [ -f "$dir/restore_status.txt" ]; then + status=$(cat "$dir/restore_status.txt") + echo "**Restore Status:** $status" >> cache-artifacts/REPORT.md + echo "" >> cache-artifacts/REPORT.md + fi + + # List contents if restored + if [ "$(cat "$dir/restore_status.txt" 2>/dev/null)" = "true" ]; then + echo "### Contents" >> cache-artifacts/REPORT.md + echo '```' >> cache-artifacts/REPORT.md + find "$dir" -type f -exec ls -lh {} \; >> cache-artifacts/REPORT.md + echo '```' >> cache-artifacts/REPORT.md + echo "" >> cache-artifacts/REPORT.md + + # Search for suspicious patterns + echo "### Suspicious Pattern Scan" >> cache-artifacts/REPORT.md + echo '```' >> cache-artifacts/REPORT.md + + # Look for embedded JavaScript + if find "$dir" -type f -name "*.tar*" -o -name "*.tgz" -o -name "*.zip" 2>/dev/null | head -1 | xargs -I {} sh -c 'tar -tzf {} 2>/dev/null || unzip -l {} 2>/dev/null' | grep -E '\.(js|sh|py)$'; then + echo "⚠️ Found executable files in archive" >> cache-artifacts/REPORT.md + fi + + # Look for malware signatures: should be the same script that's been injected + grep -r "discord.com/api/webhooks" "$dir" 2>/dev/null && echo "🚨 DISCORD WEBHOOK FOUND" >> cache-artifacts/REPORT.md || true + + echo '```' >> cache-artifacts/REPORT.md + echo "" >> cache-artifacts/REPORT.md + fi + + echo "---" >> cache-artifacts/REPORT.md + echo "" >> cache-artifacts/REPORT.md + fi + done + + - name: Create file hashes + run: | + echo "# File Hashes (SHA256)" > cache-artifacts/HASHES.txt + find cache-artifacts -type f -not -name "HASHES.txt" -not -name "REPORT.md" -exec sha256sum {} \; >> cache-artifacts/HASHES.txt + + - name: Upload each cache as separate artifact + run: | + echo '${{ inputs.cache_entries }}' | jq -c '.[]' | while read -r entry; do + key=$(echo "$entry" | jq -r '.key') + version=$(echo "$entry" | jq -r '.version') + safe_name=$(echo "$key-$version" | tr '/' '_' | tr ':' '_') + cache_dir="cache-artifacts/$safe_name" + + # Create artifact name as key:version + artifact_name="${key}:${version}" + + echo "::group::Uploading artifact: $artifact_name" + + if [ -d "$cache_dir" ]; then + # Use GitHub CLI to upload artifact with exact name + # Note: artifact names have restrictions, so we'll encode problematic chars + # GitHub artifact names allow: alphanumeric, -, and _ + safe_artifact_name=$(echo "$artifact_name" | sed 's/[^a-zA-Z0-9:_-]/_/g') + + # Create a temporary tarball for upload + tar -czf "${safe_name}.tar.gz" -C "cache-artifacts" "$(basename "$cache_dir")" + + echo "Artifact name: $safe_artifact_name" + echo "Archive: ${safe_name}.tar.gz" + + # Upload using actions/upload-artifact + # We'll do this in the next step since we can't dynamically call actions in a loop + echo "$safe_artifact_name|${safe_name}.tar.gz" >> artifact_list.txt + fi + + echo "::endgroup::" + done + + - name: Upload artifacts individually + uses: actions/upload-artifact@v4 + if: always() + with: + name: cache-forensics-batch-${{ github.run_number }} + path: | + *.tar.gz + cache-artifacts/REPORT.md + cache-artifacts/HASHES.txt + retention-days: 30 + if-no-files-found: warn + + - name: Create artifact index + if: always() + run: | + echo "# Artifact Index" > ARTIFACT_INDEX.md + echo "" >> ARTIFACT_INDEX.md + echo "## Extracted Cache Entries" >> ARTIFACT_INDEX.md + echo "" >> ARTIFACT_INDEX.md + + if [ -f artifact_list.txt ]; then + while IFS='|' read -r artifact_name archive_file; do + echo "- **$artifact_name** → \`$archive_file\`" >> ARTIFACT_INDEX.md + done < artifact_list.txt + fi + + echo "" >> ARTIFACT_INDEX.md + echo "## Download Instructions" >> ARTIFACT_INDEX.md + echo "" >> ARTIFACT_INDEX.md + echo '```bash' >> ARTIFACT_INDEX.md + echo "# Download all artifacts" >> ARTIFACT_INDEX.md + echo "gh run download ${{ github.run_id }}" >> ARTIFACT_INDEX.md + echo "" >> ARTIFACT_INDEX.md + echo "# Extract individual cache" >> ARTIFACT_INDEX.md + echo "tar -xzf .tar.gz" >> ARTIFACT_INDEX.md + echo '```' >> ARTIFACT_INDEX.md + + cat ARTIFACT_INDEX.md >> cache-artifacts/REPORT.md + + - name: Display summary + if: always() + run: | + echo "## Cache Extraction Complete 🔍" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ -f artifact_list.txt ]; then + echo "### Extracted Artifacts" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Cache Key:Version | Archive File |" >> $GITHUB_STEP_SUMMARY + echo "|-------------------|--------------|" >> $GITHUB_STEP_SUMMARY + while IFS='|' read -r artifact_name archive_file; do + echo "| \`$artifact_name\` | \`$archive_file\` |" >> $GITHUB_STEP_SUMMARY + done < artifact_list.txt + echo "" >> $GITHUB_STEP_SUMMARY + fi + + echo "---" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + cat cache-artifacts/REPORT.md >> $GITHUB_STEP_SUMMARY