Security scanning tools exist on a spectrum from "runs on your laptop in 10 seconds" to "requires a six-month enterprise procurement process." Most teams oscillate between the two extremes, skipping the productive middle ground. That middle ground — automated, opinionated, integrated into CI — is where the real security posture improvement happens.
This article covers the patterns we use to scan at the GitHub-organisation level: not just one repository, but all of them, on a schedule, with structured output that feeds into a remediation workflow.
The OWASP Top 10 as a scan ruleset
The OWASP Top 10 is not a checklist — it's a risk taxonomy. Each category maps to a set of detectable code and configuration patterns. Not all of them are automatable, but the most common ones are:
| OWASP Category | What we scan for |
|---|---|
| A01: Broken Access Control | S3 buckets with public ACLs, missing IAM conditions, no resource policies |
| A02: Cryptographic Failures | Hardcoded secrets, HTTP (not HTTPS) endpoints, weak cipher configs |
| A03: Injection | String concatenation in SQL queries, shell=True in subprocess calls |
| A05: Security Misconfiguration | Debug mode enabled, default credentials, missing security headers |
| A06: Vulnerable Components | Dependencies with known CVEs, outdated major versions |
| A09: Logging Failures | Missing audit logging, print() in production, no structured log format |
Scanning at org scale with the GitHub API
The GitHub API's /orgs/{org}/repos endpoint returns all repositories in an
organisation (paginated). With an authenticated PAT (5,000 requests/hour), you can
enumerate all repos, fetch their file trees, and download specific files for analysis —
all within the free tier of the GitHub API.
# Enumerate all repos in an org and scan each
import asyncio
from github_client import GitHubClient
from scanner import scan_repository
async def scan_org(org: str, pat: str) -> list[dict]:
client = GitHubClient(pat)
repos = client.list_org_repos(org) # handles pagination
# Scan up to 10 repos concurrently
semaphore = asyncio.Semaphore(10)
async def scan_one(repo):
async with semaphore:
return await scan_repository(
repo_url=f"https://github.com/{org}/{repo['name']}",
scan_id=generate_scan_id(),
)
return await asyncio.gather(*[scan_one(r) for r in repos])
Secret detection: the highest-value scan
Hardcoded credentials are the single highest-return security scan. Automated scanners harvest leaked credentials from GitHub within minutes of a commit — there are bots running continuously watching for AWS key patterns, GitHub tokens, Stripe keys, and more.
The patterns to scan for:
AKIA[0-9A-Z]{16}— AWS Access Key IDghp_[a-zA-Z0-9]{36}— GitHub Personal Access Token- Variable names:
password\s*=\s*["'][^"']{8,}(any hardcoded password assignment) sk_live_[0-9a-zA-Z]{24}— Stripe live key- Private key PEM headers:
-----BEGIN (RSA |EC )?PRIVATE KEY-----
IaC security scanning
Infrastructure as Code files (Terraform, CloudFormation, Pulumi) are often more security-critical than application code, but receive less scrutiny. Common patterns:
Public S3 buckets
Any Terraform resource with acl = "public-read" or
block_public_acls = false without explicit data classification sign-off.
Default should be private. Public delivery should use CloudFront OAC, not open ACLs.
Unbounded Lambda concurrency
A Lambda with no reserved_concurrent_executions set can exhaust your
account's total concurrency (default: 1000). On Free Tier, unexpected traffic
can consume your monthly compute allocation before you notice.
Missing encryption
DynamoDB tables, S3 buckets, and SQS queues without server-side encryption enabled. SSE-S3 (AES-256) is free and should be the default for all storage resources.
Integrating into CI/CD
The most effective position for security scanning is as a PR check — before merge, not after. The workflow:
- PR opened → scanner runs on changed files only (fast, targeted)
- Critical/High findings → PR check fails, merge blocked
- Medium/Low findings → PR check warns, merge allowed (with annotation)
- Weekly full-repo scan → catches findings in unchanged files
This separation — fast targeted scan on PR, full scan on schedule — keeps CI times under 60 seconds while still catching the full range of findings over time.
What the ticketyboo scanner checks
The scanner checks every repository against five categories. Security findings include hardcoded credentials, public S3 configuration, and missing security policies. Each finding includes a severity rating, the affected file, and a concrete remediation recommendation.