Skip to content

Planning implementation steps for core modules #67

Planning implementation steps for core modules

Planning implementation steps for core modules #67

name: "Maintenance: Workflow Guardrails [Observe]"
# Rechenaufwand-Score: R=3 (K=3, L=3, N=2) | last-calibrated: 2026-08-25
# Trigger policy: repo framework score calibration for workflow cost controls.
# Observe-mode governance checks for workflow safety and hygiene.
# Default behavior is non-blocking: findings are reported in summary + artifact.
# Optional strict mode can fail the job on high-severity findings.
on:
pull_request:
branches:
- develop
- community
- enterprise
- hyperscaler
- military
- minimal
paths:
- '.github/workflows/**'
schedule:
- cron: '15 4 * * 1'
workflow_dispatch:
inputs:
enforce_mode:
description: 'Fail workflow on high-severity findings'
required: false
default: false
type: boolean
permissions:
contents: read
concurrency:
group: ci-maintenance-workflow-guardrails-observe-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
workflow-guardrails:
name: Workflow Guardrails Observe
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Analyze workflow policies
id: analyze
shell: bash
run: |
python3 - <<'PY'
import json
import re
from pathlib import Path
try:
import yaml
except Exception:
import subprocess, sys
# act / Debian 12 uses PEP 668 externally-managed Python by default.
# Use the explicit break-system-packages flag so the job remains
# runnable in local Docker-based emulation as well as GitHub runners.
subprocess.check_call([
sys.executable,
'-m', 'pip', 'install', '--quiet', '--break-system-packages', 'pyyaml'
])
import yaml
root = Path('.github/workflows')
files = sorted([p for p in root.glob('*.y*ml') if p.is_file()])
sha40 = re.compile(r'^[0-9a-f]{40}$')
findings = []
def add(path, severity, rule, message):
findings.append({
'file': str(path).replace('\\', '/'),
'severity': severity,
'rule': rule,
'message': message,
})
def has_write_perm(node):
if not isinstance(node, dict):
return False
for _, v in node.items():
if isinstance(v, str) and v.strip().endswith('write'):
return True
return False
for wf in files:
text = wf.read_text(encoding='utf-8', errors='replace')
try:
doc = yaml.safe_load(text)
except Exception as exc:
add(wf, 'HIGH', 'yaml-parse', f'YAML parse error: {exc}')
continue
if not isinstance(doc, dict):
add(wf, 'HIGH', 'root-mapping', 'Workflow root is not a mapping')
continue
top_perm = doc.get('permissions')
if top_perm is None:
add(wf, 'MEDIUM', 'permissions-missing', 'Missing top-level permissions block')
elif isinstance(top_perm, dict) and has_write_perm(top_perm):
add(wf, 'MEDIUM', 'permissions-top-write', 'Top-level permissions include write scope(s); prefer job-level write')
trigger = doc.get('on')
has_pr_target = False
if isinstance(trigger, dict):
has_pr_target = 'pull_request_target' in trigger
elif isinstance(trigger, list):
has_pr_target = 'pull_request_target' in trigger
elif isinstance(trigger, str):
has_pr_target = trigger == 'pull_request_target'
if has_pr_target:
# Escalate if any write permission appears in top-level or jobs.
write_detected = False
if isinstance(top_perm, dict) and has_write_perm(top_perm):
write_detected = True
jobs = doc.get('jobs', {})
if isinstance(jobs, dict):
for _, job in jobs.items():
if isinstance(job, dict) and has_write_perm(job.get('permissions', {})):
write_detected = True
break
if write_detected:
add(wf, 'HIGH', 'pull-request-target-write', 'pull_request_target with write permissions requires explicit threat model review')
if 'continue-on-error: true' in text:
add(wf, 'LOW', 'continue-on-error', 'continue-on-error: true detected; ensure this is intentional and documented')
for line in text.splitlines():
s = line.strip()
if not s.startswith('uses:'):
continue
spec = s.split('uses:', 1)[1].strip()
if '@' not in spec:
add(wf, 'MEDIUM', 'uses-version-missing', f'Action reference without version: {spec}')
continue
action, ref = spec.split('@', 1)
action = action.strip()
ref = ref.strip()
if action.startswith('./'):
continue
if action.startswith('actions/') or action.startswith('github/'):
continue
if not sha40.fullmatch(ref):
add(wf, 'HIGH', 'third-party-unpinned', f'Third-party action not pinned to full SHA: {spec}')
severity_rank = {'HIGH': 3, 'MEDIUM': 2, 'LOW': 1}
findings.sort(key=lambda x: (-severity_rank[x['severity']], x['file'], x['rule']))
counts = {
'HIGH': sum(1 for f in findings if f['severity'] == 'HIGH'),
'MEDIUM': sum(1 for f in findings if f['severity'] == 'MEDIUM'),
'LOW': sum(1 for f in findings if f['severity'] == 'LOW'),
}
md = []
md.append('# Workflow Guardrails Observe Report')
md.append('')
md.append(f"Scanned files: {len(files)}")
md.append(f"Findings: HIGH={counts['HIGH']}, MEDIUM={counts['MEDIUM']}, LOW={counts['LOW']}")
md.append('')
md.append('| Severity | File | Rule | Message |')
md.append('|---|---|---|---|')
if findings:
for f in findings:
md.append(f"| {f['severity']} | `{f['file']}` | `{f['rule']}` | {f['message']} |")
else:
md.append('| OK | - | - | No findings |')
out_dir = Path('reports/workflow-guardrails')
out_dir.mkdir(parents=True, exist_ok=True)
(out_dir / 'workflow_guardrails_report.md').write_text('\n'.join(md) + '\n', encoding='utf-8')
(out_dir / 'workflow_guardrails_findings.json').write_text(
json.dumps({'counts': counts, 'findings': findings}, indent=2), encoding='utf-8'
)
github_output = Path(__import__('os').environ['GITHUB_OUTPUT'])
with github_output.open('a', encoding='utf-8') as fh:
fh.write(f"high_count={counts['HIGH']}\n")
fh.write(f"medium_count={counts['MEDIUM']}\n")
fh.write(f"low_count={counts['LOW']}\n")
summary = Path(__import__('os').environ['GITHUB_STEP_SUMMARY'])
with summary.open('a', encoding='utf-8') as fh:
fh.write('## Workflow Guardrails (Observe)\n\n')
fh.write(f"- Files scanned: {len(files)}\n")
fh.write(f"- HIGH: {counts['HIGH']}\n")
fh.write(f"- MEDIUM: {counts['MEDIUM']}\n")
fh.write(f"- LOW: {counts['LOW']}\n\n")
fh.write('Detailed report is uploaded as artifact.\n')
PY
- name: Upload guardrails report artifact
if: ${{ !env.ACT }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: workflow-guardrails-report-${{ github.run_id }}
path: reports/workflow-guardrails/
retention-days: 30
- name: Enforce mode (optional)
if: inputs.enforce_mode && steps.analyze.outputs.high_count != '0'
run: |
echo "::error::Workflow guardrails detected HIGH findings in enforce mode"
exit 1