Skip to content

Security

Automated AWS Audits That Run Themselves and Land in S3

TL;DR: I built an AWS Lambda function that audits your infrastructure on a schedule and exports detailed compliance reports to S3 -- no manual spreadsheet wrangling required.

The Problem

AWS audits are one of those tasks everyone knows they should do but nobody enjoys. Security teams ask for a list of public S3 buckets, unencrypted volumes, overly permissive IAM policies, and unused resources. The typical response is someone spending half a day clicking through the console, pasting screenshots into a spreadsheet, and emailing it around. By the time the report is finished, the infrastructure has already drifted. I needed something that runs automatically and produces a consistent, timestamped report.

What I Built

aws-audit-reporter is a Python Lambda function triggered by EventBridge on a configurable schedule. It queries AWS APIs across multiple services, evaluates resources against a set of compliance checks, and writes structured JSON and human-readable HTML reports to an S3 bucket.

  • Multi-service coverage -- audits EC2, S3, IAM, RDS, VPC, Lambda, EBS, and CloudTrail configurations
  • Compliance checks -- flags public buckets, unencrypted volumes, root account usage, security groups with 0.0.0.0/0, unused Elastic IPs, and more
  • Scheduled execution -- EventBridge cron triggers the Lambda daily, weekly, or on-demand
  • S3 report storage -- reports are versioned and organized by date, making it trivial to track compliance over time
  • SNS notifications -- sends a summary alert when critical findings are detected
  • Lightweight deployment -- a single Lambda function with no infrastructure beyond the function itself, an S3 bucket, and an EventBridge rule

Tech Stack

Python, AWS Lambda, Boto3, AWS EventBridge, S3, SNS, IAM, CloudFormation

Usage

# Deploy with CloudFormation
aws cloudformation deploy \
  --template-file template.yml \
  --stack-name aws-audit-reporter \
  --capabilities CAPABILITY_IAM

# Trigger manually
aws lambda invoke --function-name aws-audit-reporter output.json

# Reports appear in s3://your-audit-bucket/reports/YYYY-MM-DD/

Results

The first automated run flagged three S3 buckets with public ACLs and two security groups allowing SSH from anywhere -- findings that had been sitting in the account for months. Weekly reports now land in S3 without anyone lifting a finger, and the SNS integration means critical findings trigger a Slack alert within minutes. Audit prep that used to take a full day now takes zero.


GitHub: https://github.com/basel5001/aws-audit-reporter

One GitHub Action to Scan Every Container You Ship

TL;DR: I built a composite GitHub Action that runs Trivy, Grype, and Syft against your container images in a single step -- producing vulnerability reports and a full SBOM without any pipeline boilerplate.

The Problem

Container security scanning is non-negotiable, but configuring it properly is tedious. Most teams bolt on a single scanner and call it done. The problem? No single tool catches everything. Trivy excels at OS-level CVEs, Grype is strong on application dependencies, and Syft generates the SBOM you need for compliance. Running all three means maintaining three separate workflow steps, three sets of configs, and three output formats. Multiply that by a dozen repositories and you have a maintenance nightmare.

What I Built

container-security-scanner is a reusable GitHub Action that wraps Trivy, Grype, and Syft into a single, configurable step. Point it at an image (or a Dockerfile), and it runs all three scanners in sequence, merges the results, and fails the build if any vulnerability exceeds your severity threshold.

  • Triple-scanner coverage -- Trivy for OS and language vulnerabilities, Grype for dependency-level scanning, Syft for SBOM generation in SPDX and CycloneDX formats
  • Configurable severity gates -- set your threshold to CRITICAL, HIGH, or MEDIUM; the action exits non-zero if anything exceeds it
  • Unified SARIF output -- results are uploaded to GitHub's Security tab via code scanning, so vulnerabilities appear alongside your code
  • SBOM as an artifact -- the generated Software Bill of Materials is attached to the workflow run for download or forwarding to your compliance tooling
  • Minimal config -- add four lines to your workflow and you are scanning

Tech Stack

GitHub Actions (composite action), Trivy, Grype, Syft, SARIF, Docker

Usage

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: basel5001/container-security-scanner@main
        with:
          image: myapp:latest
          severity-threshold: HIGH
          generate-sbom: true

Results

Integrating this action across my repositories caught 14 high-severity CVEs in base images during the first week -- vulnerabilities that had been sitting in production undetected. SBOM generation now runs automatically on every release, and the compliance team pulls artifacts directly from GitHub without asking engineering. The unified SARIF view means developers see container vulnerabilities in the same place they see CodeQL findings, reducing context switching.


GitHub: https://github.com/basel5001/container-security-scanner

A GitHub Action That Catches Technical Debt Before It Merges

TL;DR: I built a GitHub Action that scans pull requests for technical debt patterns -- TODOs, FIXMEs, empty catch blocks, hardcoded secrets, and oversized files -- and blocks the merge until they are addressed.

The Problem

Technical debt accumulates silently. A developer drops a TODO: fix this later comment and moves on. Someone writes an empty catch block during a quick prototype. A hardcoded API key sneaks into a config file. Individually, none of these are catastrophic. Collectively, they erode codebase quality over months until the team is spending more time fighting the code than building features. Code reviews catch some of it, but reviewers are human -- they miss things, especially under time pressure.

What I Built

debtless is a GitHub Action that runs on every pull request and automatically scans the diff for common technical debt patterns. It annotates the PR with inline comments pointing to each finding and can be configured to block the merge if the debt exceeds a threshold.

  • Pattern detection -- scans for TODO, FIXME, HACK, XXX, WORKAROUND, and custom markers
  • Empty catch blocks -- detects catch blocks with no error handling across JavaScript, TypeScript, Python, Java, and Go
  • Hardcoded secrets -- flags patterns that look like API keys, tokens, passwords, and connection strings
  • Large file detection -- warns when files exceed a configurable line count, catching monolith classes before they grow
  • Inline PR annotations -- findings appear as review comments directly on the changed lines, not buried in logs
  • Configurable thresholds -- set a maximum debt score per PR; exceed it and the check fails
  • Trend tracking -- outputs a debt score over time so you can measure whether the codebase is getting better or worse

Tech Stack

GitHub Actions, TypeScript, GitHub Checks API, Regular Expressions, YAML Configuration

Usage

# .github/workflows/debtless.yml
name: Debt Check
on: [pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: basel5001/debtless@main
        with:
          max-debt-score: 10
          fail-on-secrets: true
          ignore-paths: "vendor/,node_modules/"

Results

After enabling debtless on three active repositories, the team resolved 47 TODO comments and 8 empty catch blocks within the first two weeks -- not because anyone mandated a cleanup sprint, but because the action surfaced them during normal PR reviews. The hardcoded-secrets check caught two AWS access keys that had been committed in test fixtures. Debt score trending showed a 30% reduction in new debt items per sprint after the first month.


GitHub: https://github.com/basel5001/debtless

One GitHub Action to Run Four Security Scanners and an AI Risk Analyst

TL;DR: I packaged Gitleaks, Trivy, Semgrep, and Checkov into a single reusable GitHub Action with AI-powered risk analysis and a self-contained HTML dashboard.

The Problem

Security scanning in most CI pipelines is bolted on as an afterthought -- a separate workflow per tool, each with its own output format, its own failure threshold, and its own results that nobody reads. Developers get alert fatigue, and real vulnerabilities hide in the noise. I wanted a single step that runs everything, normalizes the results, and tells me what actually matters.

What I Built

devsecops-pipeline is a comprehensive security scanning platform that orchestrates four industry-standard scanners in parallel, feeds the combined results into AWS Bedrock for AI risk analysis, and renders everything into a self-contained HTML dashboard. It ships as a reusable GitHub Action, a Docker image, and a standalone CLI.

  • 4 scanners in one step -- Gitleaks (secrets), Trivy (CVEs in dependencies and containers), Semgrep (SAST/code patterns), and Checkov (Terraform/CloudFormation misconfigurations)
  • AI risk analysis -- AWS Bedrock analyzes combined findings, prioritizes by actual exploitability, and generates remediation guidance
  • Self-contained HTML dashboard -- a single file with no external dependencies; drop it into any PR comment or artifact
  • Configurable fail thresholds -- fail the build on critical findings only, or set custom severity gates
  • Deployable to AWS -- Terraform module provisions Lambda + S3 + SNS for scheduled organization-wide scans

Tech Stack

Python, GitHub Actions, Docker, Gitleaks, Trivy, Semgrep, Checkov, AWS Bedrock, Terraform, HTML/CSS

Usage

# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: basel5001/devsecops-pipeline@v1
        with:
          path: '.'
          fail-on-critical: 'true'
          ai-analysis: 'true'

Or scan locally with Docker:

SCAN_PATH=/path/to/repo docker compose up --build
# Results appear in ./reports/

Results

Four tools, one workflow step, one dashboard. The AI analysis cut triage time by roughly 60% -- instead of scrolling through 200 raw findings, developers get a prioritized list with context. Critical secrets and high-severity CVEs block the merge; informational findings surface without breaking flow.


GitHub: https://github.com/basel5001/devsecops-pipeline

Bootstrapping an AWS Organization with Terraform

TL;DR: I built a Terraform-managed AWS landing zone that provisions an entire Organization -- OUs, accounts, SCPs, SSO, GuardDuty, Security Hub, and an account vending machine -- from a single terraform apply.

The Problem

Starting a new AWS Organization the right way involves dozens of manual steps: create OUs, attach SCPs, enable security services in every account, set up SSO, configure networking, establish CloudTrail. Do it by hand and you'll forget something. Do it with ClickOps and there's no audit trail. Every account you add later needs the same baseline applied, and drift is silent.

What I Built

multi-cloud-landing-zone is a Terraform codebase that bootstraps an AWS Organization with enterprise-grade security guardrails. It creates the OU hierarchy, attaches service control policies, enables security services across all accounts, configures IAM Identity Center (SSO), provisions shared networking, and includes an account vending machine that creates new accounts with a full baseline.

  • Service Control Policies -- region restriction (us-east-1 + eu-west-1), root account denial in member accounts, mandatory encryption for S3/EBS/RDS
  • Security services everywhere -- GuardDuty, Security Hub (CIS benchmarks), AWS Config, IAM Access Analyzer, and Macie enabled across all accounts with centralized findings
  • IAM Identity Center (SSO) -- four permission sets (Admin, ReadOnly, Developer, SecurityAudit) federated across all accounts
  • Account vending machine -- add an account definition to terraform.tfvars and the pipeline creates it with baseline IAM roles, CloudTrail, VPC, and Config rules
  • Shared networking -- Transit Gateway, centralized VPC flow logs, and private Route53 hosted zones

Tech Stack

Terraform, AWS Organizations, AWS IAM Identity Center, GuardDuty, Security Hub, AWS Config, SCPs, Transit Gateway, Python (policy tests)

Usage

# terraform.tfvars -- add a new account
vended_accounts = {
  team-alpha-dev = {
    email = "aws+alpha-dev@company.com"
    ou    = "Workloads"
    tags  = { Team = "alpha", Environment = "dev" }
  }
}
# Bootstrap the entire organization
cp .env.example .env && source .env
make init && make plan && make apply

# Validate SCPs
make test-policies

Results

A new AWS account goes from zero to fully baselined in under 15 minutes -- CloudTrail enabled, GuardDuty active, VPC provisioned, SSO configured. SCPs enforce encryption and region restrictions at the organizational level, so individual teams can't bypass them. The entire org structure is version-controlled: every change goes through a PR, gets reviewed, and is applied via CI.


GitHub: https://github.com/basel5001/multi-cloud-landing-zone

Deploying HashiCorp Vault as Code on AWS

TL;DR: I deployed a production-grade HashiCorp Vault cluster on AWS ECS with KMS auto-unseal, DynamoDB HA backend, dynamic secrets, and Kubernetes CSI integration -- entirely managed as Terraform.

The Problem

Secrets management is one of those things that's easy to get wrong and painful to fix. Environment variables in .env files, hardcoded API keys in config, shared credentials in a team password manager -- I've seen all of it. Setting up Vault properly involves dozens of moving parts: storage backend, unseal mechanism, TLS, audit logging, auth methods, secret engines. I wanted to codify the entire deployment so it's reproducible, auditable, and doesn't require a Vault expert to maintain.

What I Built

secret-vault is a complete Vault deployment on AWS, defined entirely in Terraform. Vault runs on ECS Fargate behind an ALB with TLS 1.3, stores data in DynamoDB with point-in-time recovery, and auto-unseals using AWS KMS. Secret engines, auth methods, and policies are all configured as code.

  • KMS auto-unseal -- no manual unseal keys to manage; Vault recovers automatically after restarts or scaling events
  • Dynamic secrets -- AWS IAM credentials and PostgreSQL database credentials are generated on-demand with automatic TTL-based revocation
  • Kubernetes CSI integration -- pods mount secrets as files via the Vault CSI Provider; no application code changes required
  • AppRole auth -- machine-to-machine authentication for CI/CD pipelines and automated tooling
  • Automated backups -- Raft snapshots shipped to S3 with KMS encryption on a schedule

Tech Stack

Terraform, HashiCorp Vault, AWS (ECS Fargate, KMS, DynamoDB, ALB, CloudWatch, S3), Docker, Kubernetes CSI

Usage

# Local development
make dev
# Access Vault UI at http://localhost:8200/ui (token: root)

# Production deployment
cp .env.example .env && source .env
make init && make plan && make apply

After deployment, applications retrieve secrets via the API or CSI driver:

# Read a static secret
vault kv get secret/my-app/config

# Generate dynamic AWS credentials
vault read aws/creds/deploy-role

# K8s CSI: mount secrets as files
# (SecretProviderClass references Vault path)

Results

Zero hardcoded secrets in application code or CI configs. Dynamic AWS credentials rotate automatically, eliminating long-lived IAM keys. The Kubernetes CSI integration means applications consume secrets as mounted files without any Vault SDK dependency. The entire stack rebuilds from scratch with terraform apply in under 10 minutes.


GitHub: https://github.com/basel5001/secret-vault