Skip to content
This repository was archived by the owner on Mar 9, 2026. It is now read-only.

Commit 741b219

Browse files
committed
security: fix CI pipeline, harden Dockerfile, remove tracked .env files
- fix(ci): resolve SARIF upload failure (missing output-file in grype scan-action@v6) - feat(ci): add Gitleaks secret scanning as Stage 1 (fail-fast before build) - fix(ci): upgrade codeql-action upload-sarif v3 -> v4 (v3 deprecated Dec 2026) - fix(ci): pin trivy-action from @master to @0.30.0 (supply-chain risk mitigation) - fix(docker): upgrade node:16-alpine (EOL) to node:20-alpine LTS - fix(docker): run container as non-root user (principle of least privilege) - fix(docker): use npm ci --omit=dev for reproducible production builds - security: untrack backend/.env and frontend/.env from git (credential exposure) - chore: add .env.example templates for backend and frontend - chore: add .env and .env.* to .gitignore - docs: rewrite README with architecture, security controls table, setup guide
1 parent b4d16ce commit 741b219

8 files changed

Lines changed: 181 additions & 31 deletions

File tree

.github/workflows/deploy.yaml

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,26 @@ jobs:
2222
uses: actions/checkout@v4
2323

2424
# -------------------------------------------------------------------------
25-
# STAGE 1 — Infrastructure-as-Code Security Scan (tfsec)
25+
# STAGE 1 — Secret Detection
26+
# -------------------------------------------------------------------------
27+
# Gitleaks scans every commit in the push for hardcoded secrets, API keys,
28+
# and credentials before any other step runs. Fail-fast: no point building
29+
# an image that already contains a committed secret.
30+
- name: Run Gitleaks secret scan
31+
uses: gitleaks/gitleaks-action@v2
32+
env:
33+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34+
35+
# -------------------------------------------------------------------------
36+
# STAGE 2 — Infrastructure-as-Code Security Scan (tfsec)
2637
# -------------------------------------------------------------------------
2738
- name: Run tfsec IaC scan
2839
uses: aquasecurity/tfsec-action@v1.0.0
2940
with:
3041
working_directory: terraform
3142

3243
# -------------------------------------------------------------------------
33-
# STAGE 2 — Build the Docker image
44+
# STAGE 3 — Build the Docker image
3445
# -------------------------------------------------------------------------
3546
- name: Configure AWS credentials
3647
uses: aws-actions/configure-aws-credentials@v4
@@ -53,11 +64,11 @@ jobs:
5364
echo "image_name=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
5465
5566
# -------------------------------------------------------------------------
56-
# STAGE 3 — Enterprise Container Vulnerability Scanning
67+
# STAGE 4 — Enterprise Container Vulnerability Scanning (Defence-in-Depth)
5768
# -------------------------------------------------------------------------
58-
# Anchore Grype: an enterprise-grade, open-source vulnerability scanner
59-
# used widely in regulated industries. It scans container images against
60-
# NVD, GitHub Security Advisories, and vendor-specific feeds.
69+
# Anchore Grype: enterprise-grade open-source vulnerability scanner used
70+
# widely in regulated industries. Scans against NVD, GitHub Security
71+
# Advisories, and vendor-specific feeds.
6172
# This step FAILS THE BUILD if any HIGH or CRITICAL CVEs are found.
6273
- name: Run Anchore Grype vulnerability scanner
6374
id: grype-scan
@@ -67,19 +78,21 @@ jobs:
6778
fail-build: true
6879
severity-cutoff: high
6980
output-format: sarif
81+
output-file: grype-results.sarif
7082

71-
# Upload SARIF results to GitHub Security tab for visibility
83+
# Upload SARIF results to GitHub Security tab for centralised visibility
7284
- name: Upload Grype SARIF report to GitHub Security
73-
uses: github/codeql-action/upload-sarif@v3
85+
uses: github/codeql-action/upload-sarif@v4
7486
if: always()
7587
with:
76-
sarif_file: ${{ steps.grype-scan.outputs.sarif }}
88+
sarif_file: grype-results.sarif
7789

78-
# Keep Trivy as a complementary scanner for defense-in-depth.
90+
# Trivy as a complementary scanner for defence-in-depth.
7991
# Two scanners catch more CVEs than one — different scanners use
8092
# different vulnerability databases and detection heuristics.
81-
- name: Run Trivy vulnerability scanner (defense-in-depth)
82-
uses: aquasecurity/trivy-action@master
93+
# Pinned to a specific release tag (not @master) to prevent supply-chain risk.
94+
- name: Run Trivy vulnerability scanner (defence-in-depth)
95+
uses: aquasecurity/trivy-action@0.30.0
8396
with:
8497
image-ref: ${{ steps.build-image.outputs.image_name }}
8598
format: 'table'
@@ -88,7 +101,7 @@ jobs:
88101
severity: 'CRITICAL,HIGH'
89102

90103
# -------------------------------------------------------------------------
91-
# STAGE 4 — Push to ECR (only runs if all scans pass)
104+
# STAGE 5 — Push to ECR (only runs if ALL scans pass)
92105
# -------------------------------------------------------------------------
93106
- name: Push image to Amazon ECR
94107
env:

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ dist/
3131
dist
3232

3333
# Environment files (comprehensive coverage)
34-
34+
.env
35+
.env.*
36+
!.env.example
3537
*token.json*
3638
*credentials.json*
3739

Dockerfile

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
11
# --- Stage 1: Build the application ---
2-
FROM node:16-alpine AS builder
2+
# node:20-alpine — LTS, actively patched, minimal attack surface vs full image
3+
FROM node:20-alpine AS builder
34
WORKDIR /app
45

5-
# Look inside the 'frontend' subfolder for the package files
6+
# Install dependencies first (layer caching — only re-runs if package files change)
67
COPY frontend/package*.json ./
7-
RUN npm install
8+
RUN npm ci --omit=dev
89

9-
# Look inside the 'frontend' subfolder for the rest of the app code
10+
# Copy the rest of the source and build
1011
COPY frontend/. .
12+
RUN npm run build
1113

12-
# --- Stage 2: Create the final, smaller image ---
13-
FROM node:16-alpine
14+
# --- Stage 2: Create the final, minimal production image ---
15+
# Separate build and runtime stages so build tools never ship in the final image
16+
FROM node:20-alpine
1417
WORKDIR /app
18+
19+
# Run as a non-root user — principle of least privilege
20+
# An attacker who escapes the app cannot own the container as root
21+
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
22+
1523
COPY --from=builder /app .
1624

25+
# Own the files by our non-root user
26+
RUN chown -R appuser:appgroup /app
27+
USER appuser
28+
1729
EXPOSE 8080
1830
CMD [ "npm", "start" ]

README.md

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,109 @@
1-
# Here are your Instructions
1+
# Cyber Airways — Secure Cloud-Native Application
2+
3+
A demonstration of security engineering best practices applied to a modern web application, built as a portfolio piece showcasing a **shift-left security** approach across the full software delivery lifecycle.
4+
5+
## Architecture Overview
6+
7+
```
8+
┌──────────────────────────────────────────┐
9+
│ GitHub Actions CI/CD │
10+
│ │
11+
│ [Secret Scan] → [IaC Scan] → [Build] │
12+
│ → [Grype + Trivy] → [Push ECR] │
13+
└──────────────────────────────────────────┘
14+
│ │
15+
▼ ▼
16+
┌─────────────┐ ┌──────────────────┐
17+
│ Terraform │ │ Amazon ECS/ECR │
18+
│ (eu-west-2)│ │ (eu-west-2) │
19+
└─────────────┘ └──────────────────┘
20+
21+
22+
┌──────────────────────────────────────┐
23+
│ VPC → Subnets → ECR → CloudTrail │
24+
│ (KMS encrypted, S3 hardened) │
25+
└──────────────────────────────────────┘
26+
```
27+
28+
## Security Controls
29+
30+
### CI/CD Pipeline (`.github/workflows/deploy.yaml`)
31+
32+
| Stage | Tool | Purpose |
33+
|-------|------|---------|
34+
| **1. Secret Detection** | Gitleaks | Catches hardcoded credentials before they reach the registry |
35+
| **2. IaC Scanning** | tfsec | Finds Terraform misconfigurations before infrastructure is deployed |
36+
| **3. Container Scan** | Anchore Grype | Primary CVE scanner — fails build on HIGH/CRITICAL findings |
37+
| **3. Container Scan** | Aqua Trivy | Secondary CVE scanner — defence-in-depth, different DB sources |
38+
| **4. SARIF Upload** | GitHub Security | Centralised vulnerability dashboard in the Security tab |
39+
40+
### Infrastructure (Terraform — `terraform/`)
41+
42+
- **VPC** with public subnets across two AZs (`eu-west-2a`, `eu-west-2b`)
43+
- **ECR** with `IMMUTABLE` image tags and scanning on push
44+
- **CloudTrail** — multi-region, global service events, log file validation enabled
45+
- **KMS** — dedicated key for CloudTrail log encryption (`enable_key_rotation = true`)
46+
- **S3** — versioning enabled, all public access blocked, TLS enforced via bucket policy
47+
48+
### Application
49+
50+
- **Frontend**: React + Microsoft Entra ID (MSAL) authentication
51+
- **Backend**: Python FastAPI with Pydantic validation
52+
- **Container**: Node 20 LTS (non-root user, multi-stage build, no dev dependencies in image)
53+
54+
## Prerequisites
55+
56+
| Tool | Version |
57+
|------|---------|
58+
| Node.js | 20 LTS |
59+
| Python | 3.11+ |
60+
| Terraform | >= 1.0 |
61+
| AWS CLI | v2 |
62+
63+
## Local Setup
64+
65+
### Backend
66+
```bash
67+
cd backend
68+
cp .env.example .env # Fill in your values
69+
pip install -r requirements.txt
70+
uvicorn server:main --reload --port 8080
71+
```
72+
73+
### Frontend
74+
```bash
75+
cd frontend
76+
cp .env.example .env # Fill in your Entra ID values
77+
npm install
78+
npm start
79+
```
80+
81+
### Infrastructure
82+
```bash
83+
cd terraform
84+
terraform init
85+
terraform plan
86+
terraform apply
87+
```
88+
89+
## GitHub Actions — Required Secrets
90+
91+
Add these in **Settings → Secrets and variables → Actions**:
92+
93+
| Secret | Description |
94+
|--------|-------------|
95+
| `AWS_ACCESS_KEY_ID` | IAM user access key with ECR push permissions |
96+
| `AWS_SECRET_ACCESS_KEY` | Corresponding secret key |
97+
98+
## Security Notes
99+
100+
- `.env` files are intentionally excluded from version control (see `.gitignore`)
101+
- Use `.env.example` as a template — never commit real credentials
102+
- `CORS_ORIGINS` in production should be set to specific origins, not `*`
103+
- All actions are pinned to specific versions to prevent supply-chain attacks
104+
105+
## Relevant Security Frameworks
106+
107+
- **NIST CSF**: Identify → Protect → Detect (CloudTrail + SIEM-ready logs)
108+
- **CIS Benchmarks**: S3 hardening, KMS key rotation, ECR immutable tags
109+
- **OWASP**: Input validation (Pydantic), CORS policy, dependency scanning

backend/.env

Lines changed: 0 additions & 3 deletions
This file was deleted.

backend/.env.example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copy this file to .env and fill in your values.
2+
# NEVER commit .env to source control.
3+
4+
# MongoDB connection string
5+
MONGO_URL="mongodb://localhost:27017"
6+
7+
# Database name
8+
DB_NAME="cyber_airways"
9+
10+
# Comma-separated list of allowed CORS origins (do NOT use * in production)
11+
# Example: https://your-frontend.example.com,https://staging.example.com
12+
CORS_ORIGINS="http://localhost:3000"

frontend/.env

Lines changed: 0 additions & 7 deletions
This file was deleted.

frontend/.env.example

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copy this file to .env and fill in your values.
2+
# NEVER commit .env to source control.
3+
4+
# Backend API base URL
5+
REACT_APP_BACKEND_URL=http://localhost:8080
6+
7+
# WebSocket port (for CRA dev server behind a proxy)
8+
WDS_SOCKET_PORT=443
9+
10+
# Microsoft Entra ID (Azure AD) — from your App Registration in the Azure Portal
11+
REACT_APP_ENTRA_CLIENT_ID=YOUR_CLIENT_ID_HERE
12+
REACT_APP_ENTRA_AUTHORITY=https://login.microsoftonline.com/YOUR_TENANT_ID_HERE
13+
REACT_APP_ENTRA_REDIRECT_URI=http://localhost:3000

0 commit comments

Comments
 (0)