Central security scanning infrastructure for Learning Tapestry. Provides vulnerability management via DefectDojo and reusable GitHub Actions workflows for automated security scanning.
- DefectDojo Access
- Developer Guide: Adding Security Scanning
- Understanding Scan Results
- Available Workflows
- Architecture
- Admin Guide
Production URL: https://infosec-scanning.learningtapestry.com
- Go to https://infosec-scanning.learningtapestry.com
- Username:
admin - Password: In Bitwarden
- Log in to DefectDojo
- Click your username (top right) → API v2 Key
- Or go directly to: https://infosec-scanning.learningtapestry.com/api/key-v2
Add this file to any repo to enable automatic security scanning:
# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
jobs:
semgrep:
name: Semgrep SAST
uses: learningtapestry/infosec-mgr/.github/workflows/semgrep.yml@main
secrets: inheritThat's it. On every push and PR, Semgrep will:
- Scan your code for security vulnerabilities
- Upload findings to DefectDojo
- Deduplicate results (no spam from repeated scans)
Semgrep automatically detects your language and applies relevant rules:
- JavaScript/TypeScript: XSS, injection, insecure dependencies
- Python: SQL injection, command injection, hardcoded secrets
- Ruby: Mass assignment, command injection, SQL injection
- Go: Race conditions, injection vulnerabilities
- Java: OWASP Top 10 vulnerabilities
- And many more...
| Behavior | Description |
|---|---|
| Non-blocking | Scans run independently - they won't fail your builds or block merges |
| Deduplicated | Same finding won't be reported twice; only NEW issues appear |
| Auto-creates product | Your repo is automatically added to DefectDojo if it doesn't exist |
| PR scanning | Scans run on PRs so you can see issues before merging |
To scan your AWS infrastructure for security misconfigurations:
# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [main, master]
schedule:
# Weekly scan recommended for cloud infrastructure
- cron: '0 9 * * 1'
jobs:
scoutsuite:
name: AWS Cloud Security
uses: learningtapestry/infosec-mgr/.github/workflows/scoutsuite.yml@main
secrets:
DEFECTDOJO_URL: ${{ secrets.DEFECTDOJO_URL }}
DEFECTDOJO_TOKEN: ${{ secrets.DEFECTDOJO_TOKEN }}
SCOUTSUITE_AWS_ACCESS_KEY_ID: ${{ secrets.SCOUTSUITE_AWS_ACCESS_KEY_ID }}
SCOUTSUITE_AWS_SECRET_ACCESS_KEY: ${{ secrets.SCOUTSUITE_AWS_SECRET_ACCESS_KEY }}Required Setup:
- Create a dedicated IAM user with
SecurityAuditandViewOnlyAccesspolicies (do NOT reuse Terraform/deployment credentials) - Add
SCOUTSUITE_AWS_ACCESS_KEY_IDandSCOUTSUITE_AWS_SECRET_ACCESS_KEYas repository-level secrets
ScoutSuite checks for:
- Overly permissive IAM policies
- Public S3 buckets
- Unencrypted resources (EBS, RDS, S3)
- Security group misconfigurations
- CloudTrail/logging gaps
- And 100+ other AWS security best practices
To also scan for vulnerable dependencies:
# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
jobs:
semgrep:
name: SAST
uses: learningtapestry/infosec-mgr/.github/workflows/semgrep.yml@main
secrets: inherit
trivy:
name: Dependencies
uses: learningtapestry/infosec-mgr/.github/workflows/trivy.yml@main
with:
scan_type: 'fs'
secrets: inheritFor comprehensive scanning with both tools:
# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
jobs:
security:
uses: learningtapestry/infosec-mgr/.github/workflows/full-security-scan.yml@main
secrets: inheritBy default, your repo name becomes the product name in DefectDojo. To customize:
jobs:
semgrep:
uses: learningtapestry/infosec-mgr/.github/workflows/semgrep.yml@main
with:
product_name: "My Custom Product Name"
secrets: inherit- Go to https://infosec-scanning.learningtapestry.com
- Click Findings in the left sidebar
- Filter by your product name (your repo name)
| Severity | Action Required |
|---|---|
| Critical | Fix immediately - active exploitation risk |
| High | Fix before next release |
| Medium | Fix when convenient |
| Low | Consider fixing, low risk |
| Info | Informational only |
Mark as False Positive:
- Open the finding
- Click Close Finding
- Select "False Positive" as the reason
- Add a note explaining why
Mark as Risk Accepted:
- Open the finding
- Click Accept Risk
- Add justification and expiration date
Track Remediation:
- Open the finding
- Add notes about your fix
- Link to PR/commit if applicable
- Finding auto-closes when scanner no longer detects it
Reality check: Initial security scans are overwhelming. A first-time ScoutSuite scan of an AWS account might return 300+ findings. This is normal and doesn't mean your infrastructure is insecure - many findings are:
- Expensive for small projects (CloudTrail, AWS Config, VPC Flow Logs)
- Outdated security practices (password expiration - no longer recommended by NIST)
- AWS defaults that are fine (default NACLs when using security groups)
- Noise that needs investigation (default security groups with rules)
Triage Strategy:
| Category | Action | Example |
|---|---|---|
| Real security issues | Fix immediately | SSH open to 0.0.0.0/0, no MFA |
| Outdated practices | Mark as out-of-scope with note | Password expiration requirements |
| Cost-deferred | Mark as out-of-scope, enable when budget allows | CloudTrail, Flow Logs |
| Needs investigation | Research, then fix or document | Default security group rules |
Using Claude Code for Triage:
Claude Code (claude.ai/code) can significantly accelerate findings triage:
# Example: Have Claude analyze and categorize findings
claude "Read the ScoutSuite findings from DefectDojo API and categorize them
into: real security issues to fix, outdated practices to ignore,
cost-deferred items, and items needing investigation.
Create a policy document explaining each decision."Claude Code can:
- Query DefectDojo API to read all findings
- Categorize findings based on your organization's risk tolerance
- Update findings in bulk (add notes, mark as out-of-scope)
- Create ScoutSuite rulesets to prevent future noise
- Generate documentation explaining triage decisions
- Fix actual security issues (update Terraform, IAM policies, etc.)
See INFOSEC-FINDINGS.md for an example of AI-assisted triage output.
| Workflow | Purpose | When to Use |
|---|---|---|
semgrep.yml |
Static code analysis (SAST) | Every repo - finds code vulnerabilities |
trivy.yml |
Dependency & container scanning | Repos with dependencies or containers |
scoutsuite.yml |
AWS cloud security scanning | Repos that deploy AWS infrastructure |
full-security-scan.yml |
All scans combined | Comprehensive security check |
semgrep.yml:
with:
product_name: 'optional-custom-name' # Default: repo name
product_type_name: 'Web Application' # DefectDojo product type
semgrep_config: 'auto' # Semgrep ruleset (auto recommended)trivy.yml:
with:
product_name: 'optional-custom-name'
scan_type: 'fs' # 'fs' (filesystem), 'image', or 'repo'
image_ref: 'myimage:tag' # Required if scan_type is 'image'scoutsuite.yml:
with:
product_name: 'optional-custom-name'
product_type_name: 'Web Application' # Must match existing product type
aws_region: 'us-east-1' # AWS region to scan
secrets:
SCOUTSUITE_AWS_ACCESS_KEY_ID: ${{ secrets.SCOUTSUITE_AWS_ACCESS_KEY_ID }}
SCOUTSUITE_AWS_SECRET_ACCESS_KEY: ${{ secrets.SCOUTSUITE_AWS_SECRET_ACCESS_KEY }}┌─────────────────────────────────────────────────────────────────────────────┐
│ GitHub Organization │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ app-1 │ │ app-2 │ │ frontend │ │ api │ │
│ │ │ │ │ │ │ │ │ │
│ │ security.yml│ │ security.yml│ │ security.yml│ │ security.yml│ │
│ │ (6 lines) │ │ (6 lines) │ │ (6 lines) │ │ (6 lines) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │ │
│ └────────────────┴────────────────┴────────────────┘ │
│ │ │
│ uses: learningtapestry/ │
│ infosec-mgr/.github/workflows/semgrep.yml@main │
│ │ │
│ ┌─────────────────────────┴─────────────────────────┐ │
│ │ infosec-mgr (this repo) │ │
│ │ .github/workflows/ │ │
│ │ ├── semgrep.yml (reusable) │ │
│ │ ├── trivy.yml (reusable) │ │
│ │ ├── full-security-scan.yml │ │
│ │ └── self-scan.yml (dogfood) │ │
│ └───────────────────────────────────────────────────┘ │
│ │ │
│ Org Secrets: DEFECTDOJO_URL, DEFECTDOJO_TOKEN │
│ │ │
└───────────────────────────────────┼─────────────────────────────────────────┘
│ HTTPS API
▼
┌───────────────────────────────────────────────────────────────────────────────┐
│ AWS Account (866795125297) │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ EC2 (t3.medium) - 54.86.136.184 │ │
│ │ infosec-scanning.learningtapestry.com │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Docker Compose Stack │ │ │
│ │ │ ├── nginx (ports 80, 443) │ │ │
│ │ │ ├── uwsgi (DefectDojo application) │ │ │
│ │ │ ├── postgres (database) │ │ │
│ │ │ ├── valkey (cache/queue) │ │ │
│ │ │ ├── celerybeat (scheduled tasks) │ │ │
│ │ │ └── celeryworker (async processing) │ │ │
│ │ └────────────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ S3 Bucket │ Daily pg_dump backups │
│ │ (backups) │ 90-day retention │
│ └─────────────────┘ │
└───────────────────────────────────────────────────────────────────────────────┘
| Component | Details |
|---|---|
| EC2 Instance | t3.medium, Amazon Linux 2023 |
| Elastic IP | 54.86.136.184 |
| Domain | infosec-scanning.learningtapestry.com |
| SSL | Let's Encrypt (auto-renews) |
| Database | PostgreSQL (Docker volume) |
| Backups | Daily to S3, 90-day retention |
ssh -i ~/.ssh/infosec-key.pem [email protected]# View logs
cd /opt/defectdojo/repo && docker compose logs -f
# Restart DefectDojo
cd /opt/defectdojo/repo && docker compose restart
# Manual backup
sudo /opt/defectdojo/backup.sh
# Check backup status
ls -la /opt/defectdojo/backups/
# View backup in S3
aws s3 ls s3://infosec-mgr-backups-866795125297/backups/
# Update DefectDojo
cd /opt/defectdojo/repo && git pull && docker compose pull && docker compose up -d| Workflow | Trigger | Purpose |
|---|---|---|
deploy-infra.yml |
Manual (workflow_dispatch) | Create/update AWS infrastructure via Terraform |
deploy-app.yml |
Push to main | Deploy application updates to EC2 |
destroy-infra.yml |
Manual | Tear down infrastructure (keeps EIP & S3) |
Organization Level:
| Secret | Purpose |
|---|---|
DEFECTDOJO_URL |
https://infosec-scanning.learningtapestry.com |
DEFECTDOJO_TOKEN |
API token for scan uploads |
Initial Setup: After deploying DefectDojo, you must copy the API token and URL to GitHub organization secrets. Get the token from DefectDojo: click your username (top right) → API v2 Key, or visit
/api/key-v2. Then add bothDEFECTDOJO_URLandDEFECTDOJO_TOKENas organization-level secrets in GitHub (Settings → Secrets and variables → Actions → Organization secrets).
Repository Level (for this repo's deployments):
| Secret | Purpose |
|---|---|
AWS_ACCESS_KEY_ID |
Terraform deployments |
AWS_SECRET_ACCESS_KEY |
Terraform deployments |
EC2_SSH_PRIVATE_KEY |
App deployments |
EC2_ELASTIC_IP |
App deployment target |
EIP_ALLOCATION_ID |
Terraform EIP management |
SCOUTSUITE_AWS_ACCESS_KEY_ID |
ScoutSuite cloud scanning (read-only) |
SCOUTSUITE_AWS_SECRET_ACCESS_KEY |
ScoutSuite cloud scanning (read-only) |
Repository Level (for repos using ScoutSuite workflow):
| Secret | Purpose |
|---|---|
SCOUTSUITE_AWS_ACCESS_KEY_ID |
Dedicated read-only IAM user for ScoutSuite |
SCOUTSUITE_AWS_SECRET_ACCESS_KEY |
Dedicated read-only IAM user for ScoutSuite |
Security Note: Create a separate IAM user for ScoutSuite with only
SecurityAuditandViewOnlyAccesspolicies. Do NOT reuse Terraform or deployment credentials - ScoutSuite should have read-only access to audit your infrastructure without being able to modify it or access sensitive data.
| Resource | Monthly Cost |
|---|---|
| EC2 t3.medium | ~$30 |
| EBS 30GB | ~$3 |
| S3 backups | ~$1 |
| Total | ~$35/month |
git clone https://github.com/learningtapestry/infosec-mgr.git
cd infosec-mgr
docker compose up -d
# Get admin password
docker compose logs initializer | grep "Admin password"
# Access locally
open http://localhost:8080infosec-mgr/
├── .github/
│ └── workflows/
│ ├── semgrep.yml # Reusable: SAST scanning
│ ├── trivy.yml # Reusable: dependency scanning
│ ├── scoutsuite.yml # Reusable: AWS cloud security scanning
│ ├── full-security-scan.yml # Reusable: all scans
│ ├── self-scan.yml # Dogfood: scan this repo
│ ├── deploy-infra.yml # Terraform deployment
│ ├── deploy-app.yml # Application deployment
│ └── destroy-infra.yml # Infrastructure teardown
│
├── terraform/ # AWS infrastructure as code
│ ├── main.tf
│ ├── variables.tf
│ ├── ec2.tf
│ ├── vpc.tf
│ ├── s3.tf # Backup bucket + lifecycle
│ ├── alerts.tf # SNS topic for backup alerts
│ ├── iam.tf # IAM roles and policies
│ └── user-data.sh # EC2 bootstrap script
│
├── scripts/
│ ├── backup.sh # Database backup script
│ └── backup-verify.sh # Backup restore verification
│
├── docker/
│ ├── extra_settings/
│ │ └── local_settings.py # DefectDojo config-as-code
│ └── extra_fixtures/
│ ├── extra_001_product_types.json
│ └── extra_002_products.json
│
├── tests/ # Playwright E2E tests
│
├── .scoutsuite/
│ └── lt-ruleset.json # LT custom ScoutSuite ruleset (excludes noise)
│
├── docker-compose.yml # DefectDojo deployment
├── CLAUDE.md # AI assistant instructions
├── INFOSEC-FINDINGS.md # Security findings triage report
└── README.md # This file
DefectDojo supports Slack notifications for security findings. Notifications are OPT-IN by default - no alerts are sent unless explicitly configured.
-
Create a Slack App:
- Go to https://api.slack.com/apps
- Click "Create New App" → "From scratch"
- Name it "DefectDojo" and select your workspace
-
Enable Incoming Webhooks:
- In your app settings, go to "Incoming Webhooks"
- Toggle "Activate Incoming Webhooks" to On
- Click "Add New Webhook to Workspace"
- Select the channel for security notifications
- Copy the webhook URL
-
Configure DefectDojo:
- Log in as admin
- Go to ⚙️ System Settings (gear icon, top right)
- Paste the webhook URL in "Slack Webhook URL"
- Save
Users must opt-in to receive notifications:
- Navigate to the Product you want notifications for
- Click Notifications tab
- Check "Slack" for the events you want alerts on
- Save
| Event | Description |
|---|---|
| Product added | New product created |
| Engagement added | New engagement started |
| Test added | New test created |
| Scan added | New scan uploaded |
| SLA breach | Finding exceeds SLA threshold |
| Risk acceptance expiration | Accepted risk is expiring |
Automated daily backups with restore verification.
| Job | Time (UTC) | Purpose |
|---|---|---|
backup.sh |
3:00 AM | Create backup, upload to S3 |
backup-verify.sh |
5:00 AM | Download, restore to test DB, validate |
| Tier | Location | Retention |
|---|---|---|
| Daily | s3://infosec-mgr-backups-*/backups/daily/ |
30 days |
| Monthly | s3://infosec-mgr-backups-*/backups/monthly/ |
6 months |
Backup failures trigger SNS alerts to [email protected]. Confirm the SNS subscription when you receive the AWS confirmation email.
# Trigger manual backup
sudo /opt/defectdojo/backup.sh
# Run backup verification
sudo /opt/defectdojo/backup-verify.sh
# View backup logs
tail -f /var/log/defectdojo-backup.log
tail -f /var/log/defectdojo-backup-verify.log- Check that org secrets
DEFECTDOJO_URLandDEFECTDOJO_TOKENare set - Verify the token hasn't expired (regenerate at
/api/key-v2) - Check the workflow logs for API errors
- Try resetting password via Django admin:
ssh -i ~/.ssh/infosec-key.pem [email protected] cd /opt/defectdojo/repo docker compose exec uwsgi python manage.py changepassword admin
- Check container health:
docker compose ps docker compose logs uwsgi
- Restart services:
docker compose restart
- Workflow changes affect ALL repos using them - test thoroughly
- This repo dogfoods itself - check
self-scan.ymlpasses - Update this README when adding new features
Based on DefectDojo, licensed under BSD-3-Clause.