Skip to content

Instantly share code, notes, and snippets.

@jmingov
Created April 29, 2026 07:13
Show Gist options
  • Select an option

  • Save jmingov/3bdfc1d0d4686466e2454961fce5625f to your computer and use it in GitHub Desktop.

Select an option

Save jmingov/3bdfc1d0d4686466e2454961fce5625f to your computer and use it in GitHub Desktop.
"""
GitHub Org Scanner
------------------
Scans all repos in one or more GitHub organizations and extracts signals about
tech stack, databases, messaging, file transfer, integrations, security,
infrastructure, and documentation coverage.
Usage:
pip install PyGithub pandas
export GITHUB_TOKEN=...
python github_org_scan.py --org org-one --org org-two
python github_org_scan.py --orgs org-one,org-two
Output:
org_scan.csv — one row per repo, all signals
org_scan_summary — printed to stdout
"""
import argparse
import os
import re
import csv
from datetime import datetime, timezone
import time
# ── config ────────────────────────────────────────────────────────────────────
DEFAULT_TOKEN = os.environ.get("GITHUB_TOKEN", "your-github-token")
DEFAULT_ORGS = os.environ.get("GITHUB_ORGS") or os.environ.get("GITHUB_ORG", "their-org-name")
DEFAULT_OUTPUT = os.environ.get("GITHUB_ORG_SCAN_OUTPUT", "org_scan.csv")
DEFAULT_LIMIT = None
# ── helpers ───────────────────────────────────────────────────────────────────
def parse_args():
parser = argparse.ArgumentParser(description="Scan GitHub organizations for technology and risk signals.")
parser.add_argument("--token", default=DEFAULT_TOKEN, help="GitHub token. Defaults to GITHUB_TOKEN.")
parser.add_argument(
"--org",
action="append",
help="GitHub organization. Repeat for multiple orgs. Defaults to GITHUB_ORGS or GITHUB_ORG.",
)
parser.add_argument(
"--orgs",
default=None,
help="Comma-separated GitHub organizations. Defaults to GITHUB_ORGS or GITHUB_ORG.",
)
parser.add_argument("--output", default=DEFAULT_OUTPUT, help="CSV output path.")
parser.add_argument("--limit", type=int, default=DEFAULT_LIMIT, help="Scan only the first N repos per org.")
return parser.parse_args()
def selected_orgs(args):
orgs = []
if args.org:
orgs.extend(args.org)
if args.orgs:
orgs.extend(args.orgs.split(","))
if not orgs and DEFAULT_ORGS:
orgs.extend(DEFAULT_ORGS.split(","))
return [org.strip() for org in orgs if org.strip()]
def add_evidence(row, signal, source):
if source:
row["_evidence"].append(f"{signal}:{source}")
def finalize_evidence(row):
row["evidence"] = "; ".join(dict.fromkeys(row.pop("_evidence", [])))
def first_matching_file(files, predicate):
return next((f for f in files if predicate(f)), "")
def safe_get(repo, path):
try:
return repo.get_contents(path).decoded_content.decode(errors="ignore")
except Exception as e:
print(f" warning: could not read {path}: {e}")
return ""
def get_file_tree(repo):
return [f.path for f in repo.get_git_tree(
repo.default_branch, recursive=True
).tree]
def extract_hosts(text, pattern):
return list(set(re.findall(pattern, text, re.IGNORECASE)))
def check_libs(text, libs):
return any(lib.lower() in text.lower() for lib in libs)
def config_files(files):
return [f for f in files if any(f.endswith(ext) for ext in [
"application.properties",
"application.yml",
"application.yaml",
"bootstrap.properties",
"bootstrap.yml",
])]
def rate_limit_wait(g):
rl = g.get_rate_limit().core
if rl.remaining < 50:
wait = max(0, int((rl.reset - datetime.now(timezone.utc)).total_seconds())) + 5
print(f" rate limit: waiting {wait}s...")
time.sleep(wait)
# ── fields ────────────────────────────────────────────────────────────────────
FIELDS = [
# identity
"org", "repo", "archived", "last_commit", "last_commit_days_ago",
"default_branch", "languages", "error", "evidence",
# app type
"has_pom", "is_spring_boot", "spring_boot_version",
"is_spring_mvc", "is_j2ee", "is_dotnet", "is_nodejs",
"is_python", "is_other",
# databases
"has_oracle", "has_mssql", "has_postgres",
"has_mysql", "has_mariadb",
"has_mongo", "has_redis", "has_elasticsearch",
# messaging
"has_kafka", "has_rabbitmq", "has_activemq", "has_jms",
"kafka_topics",
# file transfer
"has_ftp", "has_sftp", "ftp_hosts", "sftp_hosts",
# external integrations
"has_soap", "has_rest_client", "has_graphql",
"has_sap", "sap_hosts", "sap_clients", "sap_connection_type",
"has_salesforce", "salesforce_instance", "salesforce_connection_type",
"has_mulesoft",
"external_urls",
# security signals
"has_jwt", "has_oauth2", "has_spring_security", "has_basic_auth",
"has_ldap", "has_ssl_config", "has_vault",
# session / exposure
"has_cors_config", "has_csp", "has_secure_cookie",
# crypto usage (what algorithms/libs are present)
"has_md5", "has_sha1", "has_sha256",
"has_bcrypt", "has_aes", "has_rsa",
# infra / deployment
"has_dockerfile", "has_k8s", "has_helm", "has_openshift",
"has_terraform", "has_ansible",
# docs signals
"has_readme", "has_openapi", "has_asyncapi",
"has_changelog", "has_docs_folder",
# scheduled jobs
"has_cron", "has_batch", "cron_expressions",
# quality signals
"has_tests", "has_sonar", "has_ci",
]
# ── main scan ─────────────────────────────────────────────────────────────────
args = parse_args()
TOKEN = args.token
ORG_NAMES = selected_orgs(args)
OUTPUT = args.output
LIMIT = args.limit
if TOKEN == "your-github-token" or not ORG_NAMES or ORG_NAMES == ["their-org-name"]:
raise SystemExit("Set GITHUB_TOKEN and GITHUB_ORGS, or pass --token and --org/--orgs.")
from github import Github
g = Github(TOKEN, per_page=100)
results = []
for org_name in ORG_NAMES:
try:
org = g.get_organization(org_name)
except Exception as e:
print(f"warning: could not access org {org_name}: {e}")
continue
for i, repo in enumerate(org.get_repos()):
if LIMIT and i >= LIMIT:
break
rate_limit_wait(g)
print(f"scanning: {org_name}/{repo.name}")
row = {f: False for f in FIELDS}
row.update({
"org": org_name,
"repo": repo.name,
"archived": repo.archived,
"last_commit": repo.pushed_at,
"last_commit_days_ago": (
datetime.now(timezone.utc) - repo.pushed_at
).days if repo.pushed_at else None,
"default_branch": repo.default_branch,
"languages": ", ".join(repo.get_languages().keys()),
"error": "",
"evidence": "",
"_evidence": [],
"kafka_topics": "",
"ftp_hosts": "",
"sftp_hosts": "",
"external_urls": "",
"cron_expressions": "",
"sap_hosts": "",
"sap_clients": "",
"sap_connection_type": "",
"salesforce_instance": "",
"salesforce_connection_type": "",
"spring_boot_version": "",
"has_cors_config": False,
"has_csp": False,
"has_secure_cookie": False,
"has_md5": False,
"has_sha1": False,
"has_sha256": False,
"has_bcrypt": False,
"has_aes": False,
"has_rsa": False,
})
if repo.archived:
finalize_evidence(row)
results.append(row)
continue
try:
files = get_file_tree(repo)
# ── infra / deployment ────────────────────────────────────────────
row["has_dockerfile"] = any("Dockerfile" in f for f in files)
row["has_k8s"] = any("k8s/" in f for f in files)
row["has_helm"] = any("helm/" in f or "Chart.yaml" in f for f in files)
row["has_openshift"] = any("openshift/" in f for f in files)
row["has_terraform"] = any(f.endswith(".tf") for f in files)
row["has_ansible"] = any("playbook" in f or "ansible" in f for f in files)
add_evidence(row, "has_dockerfile", first_matching_file(files, lambda f: "Dockerfile" in f))
add_evidence(row, "has_k8s", first_matching_file(files, lambda f: "k8s/" in f))
add_evidence(row, "has_helm", first_matching_file(files, lambda f: "helm/" in f or "Chart.yaml" in f))
add_evidence(row, "has_openshift", first_matching_file(files, lambda f: "openshift/" in f))
add_evidence(row, "has_terraform", first_matching_file(files, lambda f: f.endswith(".tf")))
add_evidence(row, "has_ansible", first_matching_file(files, lambda f: "playbook" in f or "ansible" in f))
# ── docs signals ──────────────────────────────────────────────────
row["has_readme"] = any(f.upper() == "README.MD" for f in files)
row["has_openapi"] = any("openapi" in f.lower() or "swagger" in f.lower() for f in files)
row["has_asyncapi"] = any("asyncapi" in f.lower() for f in files)
row["has_changelog"] = any("CHANGELOG" in f.upper() for f in files)
row["has_docs_folder"] = any(f.startswith("docs/") for f in files)
add_evidence(row, "has_readme", first_matching_file(files, lambda f: f.upper() == "README.MD"))
add_evidence(row, "has_openapi", first_matching_file(files, lambda f: "openapi" in f.lower() or "swagger" in f.lower()))
add_evidence(row, "has_asyncapi", first_matching_file(files, lambda f: "asyncapi" in f.lower()))
add_evidence(row, "has_changelog", first_matching_file(files, lambda f: "CHANGELOG" in f.upper()))
add_evidence(row, "has_docs_folder", first_matching_file(files, lambda f: f.startswith("docs/")))
# ── language / framework detection ────────────────────────────────
row["is_dotnet"] = any(f.endswith(".csproj") or f.endswith(".sln") for f in files)
row["is_nodejs"] = "package.json" in files
row["is_python"] = any(f in files for f in [
"setup.py", "pyproject.toml", "requirements.txt"
])
add_evidence(row, "is_dotnet", first_matching_file(files, lambda f: f.endswith(".csproj") or f.endswith(".sln")))
add_evidence(row, "is_nodejs", first_matching_file(files, lambda f: f == "package.json"))
add_evidence(row, "is_python", first_matching_file(files, lambda f: f in ["setup.py", "pyproject.toml", "requirements.txt"]))
# ── quality signals ───────────────────────────────────────────────
row["has_tests"] = any("test" in f.lower() for f in files)
row["has_sonar"] = any("sonar" in f.lower() for f in files)
row["has_ci"] = any(
f.startswith(".github/workflows") or
"Jenkinsfile" in f or
".gitlab-ci" in f
for f in files
)
add_evidence(row, "has_tests", first_matching_file(files, lambda f: "test" in f.lower()))
add_evidence(row, "has_sonar", first_matching_file(files, lambda f: "sonar" in f.lower()))
add_evidence(row, "has_ci", first_matching_file(files, lambda f: (
f.startswith(".github/workflows") or "Jenkinsfile" in f or ".gitlab-ci" in f
)))
# ── pom.xml analysis ──────────────────────────────────────────────
pom_files = [f for f in files if f == "pom.xml" or f.endswith("/pom.xml")]
if pom_files:
pom = "\n".join(filter(None, (safe_get(repo, f) for f in pom_files)))
row["has_pom"] = bool(pom)
pom_source = ", ".join(pom_files[:5])
if len(pom_files) > 5:
pom_source += f", +{len(pom_files) - 5} more"
add_evidence(row, "has_pom", pom_source if pom else "")
if pom:
# app type
row["is_spring_boot"] = "spring-boot-starter" in pom
row["is_spring_mvc"] = "spring-webmvc" in pom and "spring-boot" not in pom
row["is_j2ee"] = check_libs(pom, ["javax.ejb", "javaee", "jakarta.ejb"])
# spring boot version
v = re.search(r"spring-boot[^<]*<version>([\d\.]+)", pom)
if v:
row["spring_boot_version"] = v.group(1)
# databases
row["has_oracle"] = check_libs(pom, ["ojdbc", "oracle-jdbc"])
row["has_mssql"] = check_libs(pom, ["mssql", "sqlserver"])
row["has_postgres"] = check_libs(pom, ["postgresql"])
row["has_mysql"] = check_libs(pom, ["mysql-connector", "mysql-connector-j"])
row["has_mariadb"] = check_libs(pom, ["mariadb-java-client", "mariadb"])
row["has_mongo"] = check_libs(pom, ["mongodb", "spring-data-mongodb"])
row["has_redis"] = check_libs(pom, ["redis", "lettuce", "jedis"])
row["has_elasticsearch"] = check_libs(pom, ["elasticsearch", "spring-data-elasticsearch"])
# messaging
row["has_kafka"] = check_libs(pom, ["kafka"])
row["has_rabbitmq"] = check_libs(pom, ["rabbitmq", "amqp"])
row["has_activemq"] = check_libs(pom, ["activemq"])
row["has_jms"] = check_libs(pom, ["javax.jms", "jakarta.jms"])
# file transfer
row["has_ftp"] = check_libs(pom, ["commons-net", "ftp4j", "edtftpj"])
row["has_sftp"] = check_libs(pom, ["jsch", "sshj", "apache-sshd", "ganymed"])
# integrations
row["has_soap"] = check_libs(pom, ["jaxws", "cxf", "axis", "wsdl"])
row["has_rest_client"] = check_libs(pom, ["feign", "retrofit", "okhttp", "resttemplate"])
row["has_graphql"] = check_libs(pom, ["graphql"])
row["has_sap"] = check_libs(pom, ["sap", "sapjco", "sapidoc"])
row["has_salesforce"] = check_libs(pom, ["salesforce", "force.com"])
row["has_mulesoft"] = check_libs(pom, ["mule"])
# security
row["has_jwt"] = check_libs(pom, ["jjwt", "nimbus-jose", "java-jwt"])
row["has_oauth2"] = check_libs(pom, ["oauth2", "spring-security-oauth", "spring-boot-starter-oauth2"])
row["has_spring_security"] = check_libs(pom, ["spring-security-core", "spring-boot-starter-security"])
row["has_ldap"] = check_libs(pom, ["ldap", "spring-ldap", "spring-security-ldap"])
row["has_basic_auth"] = check_libs(pom, ["spring-security-core", "spring-boot-starter-security"])
row["has_vault"] = check_libs(pom, ["vault"])
# crypto libraries in pom
row["has_bcrypt"] = check_libs(pom, ["bcrypt", "jbcrypt"])
row["has_aes"] = check_libs(pom, ["bouncycastle", "bc-fips"])
row["has_rsa"] = check_libs(pom, ["bouncycastle", "bc-fips", "nimbus"])
# batch / scheduled
row["has_batch"] = check_libs(pom, ["spring-batch"])
for signal in [
"is_spring_boot", "is_spring_mvc", "is_j2ee",
"has_oracle", "has_mssql", "has_postgres",
"has_mysql", "has_mariadb", "has_mongo",
"has_redis", "has_elasticsearch",
"has_kafka", "has_rabbitmq", "has_activemq", "has_jms",
"has_ftp", "has_sftp",
"has_soap", "has_rest_client", "has_graphql",
"has_sap", "has_salesforce", "has_mulesoft",
"has_jwt", "has_oauth2", "has_spring_security",
"has_ldap", "has_basic_auth", "has_vault",
"has_bcrypt", "has_aes", "has_rsa", "has_batch",
]:
if row[signal]:
add_evidence(row, signal, pom_source)
# ── config file analysis ──────────────────────────────────────────
cfg_files = config_files(files)
all_config = ""
for cf in cfg_files[:5]: # cap at 5 config files
all_config += safe_get(repo, cf)
cfg_source = ", ".join(cfg_files[:5])
if len(cfg_files) > 5:
cfg_source += f", +{len(cfg_files) - 5} more"
if all_config:
# kafka topics
topics = re.findall(r"(?:topic|topics)\s*[=:]\s*([\w\.\-]+)", all_config)
row["kafka_topics"] = ", ".join(set(topics))
# ftp/sftp hosts
row["ftp_hosts"] = ", ".join(extract_hosts(
all_config, r"ftp://([a-zA-Z0-9\.\-]+)"))
row["sftp_hosts"] = ", ".join(extract_hosts(
all_config, r"sftp://([a-zA-Z0-9\.\-]+)"))
# external URLs (filter out internal/localhost)
urls = extract_hosts(all_config, r"https?://([a-zA-Z0-9\.\-]+\.[a-zA-Z]{2,})")
external = [u for u in urls if not any(
kw in u for kw in ["localhost", "internal", "svc.cluster", "127.0.0.1"]
)]
row["external_urls"] = ", ".join(set(external[:10])) # cap at 10
# cron expressions
crons = re.findall(r"\d[\d\s\*\-\,\/]{8,}\d", all_config)
row["cron_expressions"] = ", ".join(set(crons[:5]))
row["has_cron"] = bool(crons)
# ssl / vault
row["has_ssl_config"] = "ssl" in all_config.lower() or "tls" in all_config.lower()
row["has_vault"] = row["has_vault"] or "vault" in all_config.lower()
# basic auth signals in config
basic_auth_signals = [
"basic-auth", "basicauth",
"security.user.password", # Spring Boot default basic auth
"spring.security.user.password", # Spring Boot 2.x+
"Authorization: Basic",
"httpBasic",
]
row["has_basic_auth"] = row["has_basic_auth"] or check_libs(all_config, basic_auth_signals)
# SAP connection details
sap_hosts = re.findall(r"(?:ashost|mshost|gwhost|sap\.host|sap\.server)\s*[=:]\s*([\w\.\-]+)", all_config, re.IGNORECASE)
sap_clients = re.findall(r"(?:sap\.client|jco\.client\.client|client)\s*[=:]\s*(\d{3})", all_config, re.IGNORECASE)
sap_conn_types = []
if check_libs(all_config, ["jco.", "sapjco", "JCoDestination", "JCoFunction"]):
sap_conn_types.append("JCo/RFC")
if check_libs(all_config, ["idoc", "IDocDocument", "IDocFactory"]):
sap_conn_types.append("IDoc")
if check_libs(all_config, ["/sap/opu/odata", "OData"]):
sap_conn_types.append("OData")
if check_libs(all_config, ["bapi", "BAPI_"]):
sap_conn_types.append("BAPI")
row["sap_hosts"] = ", ".join(set(sap_hosts))
row["sap_clients"] = ", ".join(set(sap_clients))
row["sap_connection_type"] = ", ".join(sap_conn_types)
row["has_sap"] = row["has_sap"] or bool(sap_hosts or sap_clients or sap_conn_types)
# Salesforce connection details
sf_instances = re.findall(r"([\w\-]+\.salesforce\.com|[\w\-]+\.force\.com)", all_config, re.IGNORECASE)
sf_conn_types = []
if check_libs(all_config, ["RestTemplate", "HttpClient", "WebClient"]) and check_libs(all_config, ["salesforce", "force.com"]):
sf_conn_types.append("REST")
if check_libs(all_config, ["soap", "enterprise.wsdl", "partner.wsdl"]):
sf_conn_types.append("SOAP")
if check_libs(all_config, ["Platform Event", "PushTopic", "CometD", "Streaming"]):
sf_conn_types.append("Streaming/Events")
if check_libs(all_config, ["Bulk API", "bulk/v"]):
sf_conn_types.append("Bulk API")
if check_libs(all_config, ["connected app", "client_id", "client_secret"]) and check_libs(all_config, ["salesforce", "force.com"]):
sf_conn_types.append("OAuth/Connected App")
row["salesforce_instance"] = ", ".join(set(sf_instances))
row["salesforce_connection_type"] = ", ".join(sf_conn_types)
row["has_salesforce"] = row["has_salesforce"] or bool(sf_instances or sf_conn_types)
# session / exposure
row["has_secure_cookie"] = check_libs(all_config, [
"cookie.secure=true", "setSecure(true)",
"server.servlet.session.cookie.secure=true",
])
row["has_cors_config"] = check_libs(all_config, [
"cors", "CrossOrigin", "allowedOrigins",
"Access-Control-Allow-Origin",
])
row["has_csp"] = check_libs(all_config, [
"Content-Security-Policy", "contentSecurityPolicy",
])
# crypto usage in config/code
row["has_md5"] = check_libs(all_config, [
"MD5", "MessageDigest.getInstance(\"MD5\")",
"DigestUtils.md5",
])
row["has_sha1"] = check_libs(all_config, [
"SHA-1", "SHA1", "MessageDigest.getInstance(\"SHA-1\")",
"DigestUtils.sha1",
])
row["has_sha256"] = check_libs(all_config, [
"SHA-256", "SHA256", "MessageDigest.getInstance(\"SHA-256\")",
"DigestUtils.sha256",
])
row["has_bcrypt"] = row["has_bcrypt"] or check_libs(all_config, [
"BCrypt", "bcrypt", "BCryptPasswordEncoder",
])
row["has_aes"] = row["has_aes"] or check_libs(all_config, [
"AES", "AES/CBC", "AES/GCM", "Cipher.getInstance(\"AES",
])
row["has_rsa"] = row["has_rsa"] or check_libs(all_config, [
"RSA", "Cipher.getInstance(\"RSA", "KeyPairGenerator",
])
for signal in [
"kafka_topics", "ftp_hosts", "sftp_hosts", "external_urls",
"cron_expressions", "has_cron", "has_ssl_config", "has_vault",
"has_basic_auth", "sap_hosts", "sap_clients",
"sap_connection_type", "has_sap", "salesforce_instance",
"salesforce_connection_type", "has_salesforce",
"has_secure_cookie", "has_cors_config", "has_csp",
"has_md5", "has_sha1", "has_sha256",
"has_bcrypt", "has_aes", "has_rsa",
]:
if row[signal]:
add_evidence(row, signal, cfg_source)
except Exception as e:
row["error"] = str(e)
finalize_evidence(row)
results.append(row)
# ── write CSV ─────────────────────────────────────────────────────────────────
with open(OUTPUT, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=FIELDS)
writer.writeheader()
writer.writerows(results)
print(f"\n✓ saved to {OUTPUT}\n")
# ── summary ───────────────────────────────────────────────────────────────────
import pandas as pd
df = pd.read_csv(OUTPUT)
active = df[df.archived != True]
stale = active[active.last_commit_days_ago > 365]
by_org = df.groupby("org").size().to_dict()
by_org_summary = "\n".join(f" {org}: {count:>6}" for org, count in by_org.items())
print(f"""
═══════════════════════════════════════
ORG SCAN SUMMARY — {", ".join(ORG_NAMES)}
═══════════════════════════════════════
Total repos: {len(df):>6}
Active (not archived): {len(active):>6}
Stale (>1yr no commit): {len(stale):>6}
REPOS BY ORG
{by_org_summary}
APP TYPE
Spring Boot: {active.is_spring_boot.sum():>6}
Spring MVC: {active.is_spring_mvc.sum():>6}
J2EE: {active.is_j2ee.sum():>6}
.NET: {active.is_dotnet.sum():>6}
Node.js: {active.is_nodejs.sum():>6}
Python: {active.is_python.sum():>6}
DATABASES
Oracle: {active.has_oracle.sum():>6}
MSSQL: {active.has_mssql.sum():>6}
PostgreSQL: {active.has_postgres.sum():>6}
MySQL: {active.has_mysql.sum():>6}
MariaDB: {active.has_mariadb.sum():>6}
MongoDB: {active.has_mongo.sum():>6}
Redis: {active.has_redis.sum():>6}
Elasticsearch: {active.has_elasticsearch.sum():>6}
MESSAGING
Kafka: {active.has_kafka.sum():>6}
RabbitMQ: {active.has_rabbitmq.sum():>6}
ActiveMQ: {active.has_activemq.sum():>6}
JMS: {active.has_jms.sum():>6}
FILE TRANSFER
FTP: {active.has_ftp.sum():>6}
SFTP: {active.has_sftp.sum():>6}
INTEGRATIONS
SAP: {active.has_sap.sum():>6}
JCo/RFC: {active[active.sap_connection_type.str.contains("JCo", na=False)].shape[0]:>6}
IDoc: {active[active.sap_connection_type.str.contains("IDoc", na=False)].shape[0]:>6}
OData: {active[active.sap_connection_type.str.contains("OData", na=False)].shape[0]:>6}
BAPI: {active[active.sap_connection_type.str.contains("BAPI", na=False)].shape[0]:>6}
Salesforce: {active.has_salesforce.sum():>6}
REST: {active[active.salesforce_connection_type.str.contains("REST", na=False)].shape[0]:>6}
SOAP: {active[active.salesforce_connection_type.str.contains("SOAP", na=False)].shape[0]:>6}
Streaming/Events: {active[active.salesforce_connection_type.str.contains("Streaming", na=False)].shape[0]:>6}
Bulk API: {active[active.salesforce_connection_type.str.contains("Bulk", na=False)].shape[0]:>6}
SOAP/WSDL: {active.has_soap.sum():>6}
GraphQL: {active.has_graphql.sum():>6}
MuleSoft: {active.has_mulesoft.sum():>6}
SECURITY
JWT: {active.has_jwt.sum():>6}
OAuth2: {active.has_oauth2.sum():>6}
Spring Security: {active.has_spring_security.sum():>6}
Basic Auth: {active.has_basic_auth.sum():>6}
LDAP: {active.has_ldap.sum():>6}
Vault: {active.has_vault.sum():>6}
SESSION / EXPOSURE
Secure cookie: {active.has_secure_cookie.sum():>6}
CORS configured: {active.has_cors_config.sum():>6}
CSP configured: {active.has_csp.sum():>6}
CRYPTO USAGE
MD5: {active.has_md5.sum():>6}
SHA-1: {active.has_sha1.sum():>6}
SHA-256: {active.has_sha256.sum():>6}
BCrypt: {active.has_bcrypt.sum():>6}
AES: {active.has_aes.sum():>6}
RSA: {active.has_rsa.sum():>6}
INFRA
Dockerfile: {active.has_dockerfile.sum():>6}
Kubernetes/k8s: {active.has_k8s.sum():>6}
Helm: {active.has_helm.sum():>6}
OpenShift: {active.has_openshift.sum():>6}
Terraform: {active.has_terraform.sum():>6}
Ansible: {active.has_ansible.sum():>6}
DOCS COVERAGE
Has README: {active.has_readme.sum():>6}
Has OpenAPI: {active.has_openapi.sum():>6}
Has AsyncAPI: {active.has_asyncapi.sum():>6}
Has Changelog: {active.has_changelog.sum():>6}
Has CI: {active.has_ci.sum():>6}
Has Tests: {active.has_tests.sum():>6}
═══════════════════════════════════════
""")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment