Files
evandrodevbr c17192c9fe feat: vendorizar as 10 skills do pilar de seguranca em skills/
Copia local (nao fork, nao submodule) de safedeps, sca-audit,
secrets-scan, code-review-security, api-security-review,
web-security-review, cve-triage, patch-prioritization,
dependency-scanning e hermaguard -- commit exato de origem e licenca
de cada uma em skills/NOTICE.md. Elimina o passo de clone externo
pro pilar de seguranca do wf-security-review: git clone deste repo
ja traz tudo funcional.

Corrige tambem o comando de instalacao pro Claude Code nos dois
READMEs e no docs/SKILLS.md: Claude Code exige um diretorio por
skill (skills/<nome>/SKILL.md), nao aceita os .md soltos que o
comando antigo copiava -- confirmado testando a instalacao real.
2026-08-24 00:32:20 -03:00

222 lines
7.8 KiB
YAML

name: HermaGuard Code Review
description: Run adversarial code review on pull requests
inputs:
block_on:
description: Minimum severity to block merge (CRITICAL, HIGH, or never)
default: 'CRITICAL'
comment_mode:
description: How to post findings (inline on diff lines, or summary comment)
default: 'inline'
min_severity:
description: Minimum severity to report (LOW reports everything)
default: 'MEDIUM'
max_files:
description: Skip if more than this many files changed
default: '50'
skip_patterns:
description: File patterns to skip (newline-separated)
default: '*.test.*\n*.spec.*\n*.md\n*.json\n*.yaml'
hermaguard_args:
description: Additional arguments for hermaguard
default: ''
prescan_enabled:
description: Run deterministic pre-scan before LLM review
default: 'true'
runs:
using: composite
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install HermaGuard tools
shell: bash
run: |
# Install hermaguard-prescan (deterministic layer)
if [ -f tools/hermaguard-prescan/hermaguard-prescan ]; then
chmod +x tools/hermaguard-prescan/hermaguard-prescan
sudo ln -sf $(pwd)/tools/hermaguard-prescan/hermaguard-prescan /usr/local/bin/hermaguard-prescan
fi
# Install optional linters for prescan
pip install bandit ruff semgrep 2>/dev/null || true
npm install -g eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin 2>/dev/null || true
- name: Get changed files
id: files
shell: bash
run: |
# Get files changed in this PR vs base branch
BASE_SHA="${{ github.event.pull_request.base.sha }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
if [ -z "$BASE_SHA" ]; then
# Not a PR event — use push range
BASE_SHA="${{ github.event.before }}"
HEAD_SHA="${{ github.event.after }}"
fi
if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then
# First commit or unknown base — use HEAD~1
BASE_SHA="HEAD~1"
fi
CHANGED=$(git diff --name-only $BASE_SHA $HEAD_SHA -- '*.ts' '*.tsx' '*.js' '*.py' '*.go' '*.rs' '*.java' 2>/dev/null)
# Apply skip patterns
SKIP="${{ inputs.skip_patterns }}"
FILTERED=""
for file in $CHANGED; do
SKIP_FILE=0
for pattern in $(echo "$SKIP" | tr '\n' ' '); do
if [[ "$file" == $pattern ]]; then
SKIP_FILE=1
break
fi
done
if [ $SKIP_FILE -eq 0 ]; then
FILTERED="$FILTERED $file"
fi
done
FILTERED=$(echo "$FILTERED" | xargs) # trim
FILE_COUNT=$(echo "$FILTERED" | wc -w)
echo "files=${FILTERED}" >> $GITHUB_OUTPUT
echo "count=${FILE_COUNT}" >> $GITHUB_OUTPUT
if [ -z "$FILTERED" ]; then
echo "No code files changed. Skipping HermaGuard."
elif [ "$FILE_COUNT" -gt "${{ inputs.max_files }}" ]; then
echo "Too many files ($FILE_COUNT > ${{ inputs.max_files }}). Skipping HermaGuard."
fi
- name: Run HermaGuard Pre-Scan
if: ${{ steps.files.outputs.count > 0 && steps.files.outputs.count <= inputs.max_files && inputs.prescan_enabled == 'true' }}
id: prescan
shell: bash
run: |
mkdir -p /tmp/hermaguard
echo "Running pre-scan on ${{ steps.files.outputs.count }} files..."
hermaguard-prescan --files "${{ steps.files.outputs.files }}" \
--output /tmp/hermaguard/prescan.json 2>&1 || true
if [ -f /tmp/hermaguard/prescan.json ]; then
echo "Pre-scan complete."
else
echo "Pre-scan unavailable."
fi
- name: Run HermaGuard
if: ${{ steps.files.outputs.count > 0 && steps.files.outputs.count <= inputs.max_files }}
id: hermaguard
shell: bash
run: |
mkdir -p /tmp/hermaguard
echo "Running HermaGuard review..."
# hermaguard is invoked via claude/codex as a skill
# This step depends on the deployment environment
# For CI, we use the json output mode
echo "NOTE: Full HermaGuard LLM review requires an active Claude/Codex session."
echo "See: https://github.com/Sahil-SS9/hermaguard for setup instructions."
echo "Pre-scan output available at /tmp/hermaguard/prescan.json"
- name: Post findings as PR comments
if: ${{ steps.files.outputs.count > 0 && always() }}
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
let body = '';
// Use pre-scan output as the primary structured findings in CI
// Full HermaGuard (LLM review) runs interactively; CI uses pre-scan
try {
const prescan = JSON.parse(fs.readFileSync('/tmp/hermaguard/prescan.json', 'utf8'));
const stats = prescan.stats || {};
body = `## 🔍 HermaGuard Pre-Scan
**${stats.total_findings || 0} findings** from ${stats.tools_run?.join(', ') || 'no tools'}
`;
if (prescan.findings && prescan.findings.length > 0) {
// Group by severity
const groups = {};
for (const f of prescan.findings) {
const sev = f.severity || 'LOW';
if (!groups[sev]) groups[sev] = [];
if (groups[sev].length < 10) { // cap per severity
groups[sev].push(`- \`${f.file}:${f.line}\` — **${f.tool}** ${f.message}`);
}
}
for (const [sev, items] of Object.entries(groups)) {
body += `### ${sev}\n${items.join('\n')}\n\n`;
}
if (prescan.findings.length > 30) {
body += `\n*${prescan.findings.length - 30} more findings truncated. See full output.*\n`;
}
} else {
body += '✅ No deterministic issues found.\n';
}
body += `\n> Run \`/hermaguard\` for full adversarial LLM review.`;
} catch (e) {
body = `## 🔍 HermaGuard
Pre-scan unavailable: ${e.message}
Run \`/hermaguard\` locally for adversarial review.`;
}
// Post comment
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
- name: Block on critical findings (optional)
if: ${{ inputs.block_on != 'never' && steps.prescan.outcome == 'success' && always() }}
shell: bash
run: |
if [ -f /tmp/hermaguard/prescan.json ]; then
CRITICAL=$(python3 -c "
import json
with open('/tmp/hermaguard/prescan.json') as f:
data = json.load(f)
crit = sum(1 for f in data.get('findings',[]) if f.get('severity') == 'CRITICAL')
print(crit)
")
if [ "$CRITICAL" -gt 0 ] && [ "${{ inputs.block_on }}" == "CRITICAL" ]; then
echo "BLOCKING: ${CRITICAL} CRITICAL findings detected."
exit 1
fi
HIGH_COUNT=$(python3 -c "
import json
with open('/tmp/hermaguard/prescan.json') as f:
data = json.load(f)
crit = sum(1 for f in data.get('findings',[]) if f.get('severity') in ('CRITICAL','HIGH'))
print(crit)
")
if [ "$HIGH_COUNT" -gt 0 ] && [ "${{ inputs.block_on }}" == "HIGH" ]; then
echo "BLOCKING: ${HIGH_COUNT} CRITICAL+HIGH findings detected."
exit 1
fi
fi
echo "No blocking findings."