Skip to content

Commit 33a00a9

Browse files
authored
Create entropy-beauty-scan.yml (#7966)
* Create entropy-scan.yml Found this tool by sheer luck? Let's see what it reveals! * Create compute-entropy.py make it beautiful * Create entropy-beauty-scan.yml the yml for entropy + beauty scanning * Delete .github/workflows/entropy-scan.yml delete the previous half-finished scanner * Update entropy-beauty-scan.yml try fixing merge issue postback
1 parent 68e695d commit 33a00a9

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env python3
2+
import sys, math, json, subprocess
3+
from collections import Counter
4+
from pathlib import Path
5+
6+
def shannon_entropy(text: str) -> float:
7+
if not text or len(text) < 10:
8+
return 0.0
9+
freq = Counter(text)
10+
probs = [count / len(text) for count in freq.values()]
11+
return -sum(p * math.log2(p) for p in probs if p > 0)
12+
13+
# Get changed files safely for pull_request events
14+
changed_files = []
15+
try:
16+
# GitHub provides github.event.pull_request.base.sha and head.sha in the context
17+
base_sha = subprocess.check_output(['git', 'rev-parse', 'origin/${{ github.base_ref }}'], text=True).strip()
18+
changed_files = subprocess.check_output(
19+
['git', 'diff', '--name-only', base_sha, 'HEAD'], text=True
20+
).splitlines()
21+
except subprocess.CalledProcessError:
22+
# Fallback for first-time PRs or edge cases: use the merge-base or just files in HEAD
23+
try:
24+
changed_files = subprocess.check_output(
25+
['git', 'diff', '--name-only', 'HEAD~1', 'HEAD'], text=True
26+
).splitlines()
27+
except subprocess.CalledProcessError:
28+
# Last resort: all files in the repo
29+
changed_files = subprocess.check_output(['git', 'ls-files'], text=True).splitlines()
30+
31+
results = []
32+
total_ent = 0.0
33+
count = 0
34+
35+
for f in changed_files:
36+
path = Path(f.strip())
37+
if not path.exists() or path.suffix in {'.png', '.jpg', '.gif', '.bin', '.lock', '.exe', '.dll', '.so'}:
38+
continue
39+
try:
40+
content = path.read_text(encoding='utf-8', errors='ignore')
41+
ent = shannon_entropy(content)
42+
results.append(f"{f}: {ent:.3f}")
43+
total_ent += ent
44+
count += 1
45+
except Exception:
46+
pass
47+
48+
avg = round(total_ent / count, 3) if count > 0 else 0.0
49+
50+
verdict = (
51+
"✅ Mid-4 beauty detected (thoughtful human code!)" if 4.3 <= avg <= 4.7 else
52+
"⚠️ Consider review — entropy outside sweet spot" if avg > 0 else
53+
"No source files changed"
54+
)
55+
56+
with open('/tmp/beauty.json', 'w') as f:
57+
json.dump({
58+
"average_entropy": avg,
59+
"verdict": verdict,
60+
"files": results[:20]
61+
}, f, indent=2)
62+
63+
print(f"Average entropy: {avg}")
64+
print(verdict)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Entropy Beauty + TruffleHog Scan
2+
3+
on: [push, pull_request, release]
4+
5+
permissions:
6+
contents: read
7+
pull-requests: write
8+
issues: write # must be at workflow level for push/merge events
9+
10+
jobs:
11+
scan:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout code (full history)
15+
uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
18+
19+
- name: Run TruffleHog
20+
uses: trufflesecurity/trufflehog@main
21+
with:
22+
path: .
23+
extra_args: --results=verified,unknown --filter-entropy=3.5 --json
24+
25+
- name: Compute mid-4 beauty entropy
26+
run: python .github/workflows/compute-entropy.py
27+
28+
- name: Post summary comment (PR only)
29+
if: github.event_name == 'pull_request'
30+
uses: actions/github-script@v7
31+
with:
32+
github-token: ${{ secrets.GITHUB_TOKEN }}
33+
script: |
34+
const fs = require('fs');
35+
36+
// Read TruffleHog output — it prints one JSON object per line (NDJSON)
37+
let findings = [];
38+
if (fs.existsSync('trufflehog.json')) {
39+
try {
40+
const lines = fs.readFileSync('trufflehog.json', 'utf8').trim().split('\n');
41+
findings = lines.map(line => {
42+
try { return JSON.parse(line); } catch(e) { return null; }
43+
}).filter(Boolean);
44+
} catch(e) {}
45+
} else {
46+
// Fallback: the action also logs to GITHUB_STEP_SUMMARY, but we use the file from the Python step
47+
console.log("No trufflehog.json found, using empty findings");
48+
}
49+
50+
const beauty = JSON.parse(fs.readFileSync('/tmp/beauty.json', 'utf8'));
51+
52+
let body = `## 🐷 TruffleHog + Entropy Beauty Scan\n\n`;
53+
body += `**Average entropy of changed code:** ${beauty.average_entropy} bits/char\n`;
54+
body += `**Verdict:** ${beauty.verdict}\n\n`;
55+
56+
if (beauty.files && beauty.files.length) {
57+
body += `**Changed files entropy:**\n\`\`\`\n${beauty.files.join('\n')}\n\`\`\`\n\n`;
58+
}
59+
60+
if (findings.length > 0) {
61+
body += `⚠️ **TruffleHog found ${findings.length} potential issue(s)**\n`;
62+
} else {
63+
body += `✅ No secrets or suspicious high-entropy strings found.\n`;
64+
}
65+
66+
body += `\n*Mid-4 beauty heuristic in action — powered by our entropy chats! 😊*`;
67+
68+
await github.rest.issues.createComment({
69+
owner: context.repo.owner,
70+
repo: context.repo.repo,
71+
issue_number: context.issue.number,
72+
body: body
73+
});

0 commit comments

Comments
 (0)