Claude 24f816b6a3
Consolidate 22 sibling repos into layered organism structure
Place useful parts of the surrounding repos into sica-fondt by layer, per the
body model (Ada = membrane; brain/endocrine/capabilities/knowledge non-Ada):

- brain/        LLM reasoning + providers (dapr, hermes, MoMoA)
- capabilities/ REPRAG sidecars: hermes tools/skills, dapr tools, parallel
                dispatch, A51 channels, and the OSINT cluster
- knowledge/    LORAG corpus: 754 cyber-skills, agency personas, secure-coding,
                MITRE ATT&CK data
- reference/    defensive threat-reference (C3, shhbruh doc) + AdaYaml parser

License handling: AGPL sources (worldosint, advanced_evolution, mercury,
Reticulum) and GPL DeTTECT are SPEC-only clean-room/port descriptions — no
copyleft code copied. MIT/Apache/data parts copied as working trees.

Safety: shhbruh escape/persistence material and C3 covert-C2 kept as reference
only, not wired into the running organism. See CONSOLIDATION.md.

https://claude.ai/code/session_01UehUqEXXJJCsHoA4voCU5c
2026-06-10 06:53:01 +00:00

175 lines
6.0 KiB
Python

#!/usr/bin/env python3
"""
GitLab DevSecOps Pipeline Security Report Generator
Queries GitLab API to aggregate security scanning results
across projects and generate compliance reports.
"""
import json
import os
import sys
import urllib.request
import urllib.error
from datetime import datetime
from collections import defaultdict
def gitlab_api_get(url: str, token: str) -> list | dict:
headers = {"PRIVATE-TOKEN": token, "Content-Type": "application/json"}
results = []
page = 1
while True:
sep = "&" if "?" in url else "?"
paginated = f"{url}{sep}per_page=100&page={page}"
req = urllib.request.Request(paginated, headers=headers)
try:
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read().decode())
if isinstance(data, list):
if not data:
break
results.extend(data)
page += 1
else:
return data
except urllib.error.HTTPError as e:
print(f"HTTP {e.code}: {e.read().decode()}")
break
return results
def get_group_projects(base_url: str, token: str, group_id: str) -> list:
url = f"{base_url}/api/v4/groups/{group_id}/projects?include_subgroups=true"
return gitlab_api_get(url, token)
def get_project_vulnerabilities(base_url: str, token: str, project_id: int) -> list:
url = f"{base_url}/api/v4/projects/{project_id}/vulnerabilities?state=detected"
return gitlab_api_get(url, token)
def get_pipeline_jobs(base_url: str, token: str, project_id: int, pipeline_id: int) -> list:
url = f"{base_url}/api/v4/projects/{project_id}/pipelines/{pipeline_id}/jobs"
return gitlab_api_get(url, token)
def check_security_scanners(jobs: list) -> dict:
scanner_names = {
"sast": False,
"secret_detection": False,
"dependency_scanning": False,
"container_scanning": False,
"dast": False,
"license_scanning": False,
}
for job in jobs:
name = job.get("name", "").lower()
for scanner in scanner_names:
if scanner.replace("_", "-") in name or scanner in name:
scanner_names[scanner] = True
return scanner_names
def generate_report(base_url: str, token: str, group_id: str) -> dict:
projects = get_group_projects(base_url, token, group_id)
report = {
"gitlab_instance": base_url,
"group_id": group_id,
"generated_at": datetime.utcnow().isoformat() + "Z",
"total_projects": len(projects),
"scanner_coverage": defaultdict(int),
"severity_totals": defaultdict(int),
"project_details": [],
}
for project in projects:
pid = project["id"]
name = project["path_with_namespace"]
vulns = get_project_vulnerabilities(base_url, token, pid)
severity_counts = defaultdict(int)
scanner_counts = defaultdict(int)
for v in vulns:
severity_counts[v.get("severity", "unknown")] += 1
scanner_counts[v.get("scanner", {}).get("name", "unknown")] += 1
report["severity_totals"][v.get("severity", "unknown")] += 1
pipelines = gitlab_api_get(
f"{base_url}/api/v4/projects/{pid}/pipelines?per_page=1&status=success", token
)
scanners_enabled = {}
if pipelines and isinstance(pipelines, list):
jobs = get_pipeline_jobs(base_url, token, pid, pipelines[0]["id"])
scanners_enabled = check_security_scanners(jobs)
for scanner, enabled in scanners_enabled.items():
if enabled:
report["scanner_coverage"][scanner] += 1
report["project_details"].append({
"project": name,
"open_vulnerabilities": len(vulns),
"by_severity": dict(severity_counts),
"by_scanner": dict(scanner_counts),
"scanners_enabled": scanners_enabled,
})
report["scanner_coverage"] = dict(report["scanner_coverage"])
report["severity_totals"] = dict(report["severity_totals"])
return report
def print_report(report: dict) -> None:
print(f"\n{'='*65}")
print(f"GitLab DevSecOps Security Report")
print(f"Instance: {report['gitlab_instance']}")
print(f"Generated: {report['generated_at']}")
print(f"{'='*65}")
print(f"\nTotal Projects: {report['total_projects']}")
print(f"\nScanner Coverage:")
for scanner, count in sorted(report["scanner_coverage"].items()):
pct = count / report["total_projects"] * 100 if report["total_projects"] > 0 else 0
print(f" {scanner:25s}: {count:3d}/{report['total_projects']} ({pct:.0f}%)")
print(f"\nVulnerability Summary:")
for sev in ["critical", "high", "medium", "low", "info", "unknown"]:
count = report["severity_totals"].get(sev, 0)
if count > 0:
print(f" {sev.upper():12s}: {count}")
total = sum(report["severity_totals"].values())
print(f" {'TOTAL':12s}: {total}")
print(f"\nTop 10 Projects by Open Vulnerabilities:")
sorted_projects = sorted(
report["project_details"], key=lambda p: p["open_vulnerabilities"], reverse=True
)
for p in sorted_projects[:10]:
print(f" {p['project']:45s} | Vulns: {p['open_vulnerabilities']}")
def main():
token = os.environ.get("GITLAB_TOKEN")
base_url = os.environ.get("GITLAB_URL", "https://gitlab.com")
group_id = os.environ.get("GITLAB_GROUP_ID")
if not token:
print("Error: GITLAB_TOKEN environment variable required")
sys.exit(1)
if not group_id:
print("Error: GITLAB_GROUP_ID environment variable required")
sys.exit(1)
report = generate_report(base_url, token, group_id)
print_report(report)
output = f"gitlab_devsecops_report_{datetime.utcnow().strftime('%Y%m%d')}.json"
with open(output, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"\nReport saved to: {output}")
if __name__ == "__main__":
main()