Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 258 additions & 0 deletions .github/workflows/cache_to_artifacts
Original file line number Diff line number Diff line change
@@ -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<<EOF' >> $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" <<EOF
{
"key": "$key",
"version": "$version",
"requested_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"runner_os": "$RUNNER_OS",
"runner_arch": "$RUNNER_ARCH"
}
EOF
done

- name: Restore cache entries
id: restore
continue-on-error: true
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"

# Use actions/cache restore with specific key
echo "::group::Restoring $key"

# Try direct cache restore using GitHub API
# Note: actions/cache doesn't support version parameter directly
# So we'll use the cache key pattern matching

restored="false"
if gh cache restore "$cache_dir" --key "$key" 2>/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 <archive-name>.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
Loading