Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
21faabf
Create outputs.tf
manishvishwakarma89 May 19, 2026
6613eff
build and testing code-quality-check.yml
manishvishwakarma89 May 20, 2026
bbb97aa
Create secret-scan.yml
manishvishwakarma89 May 20, 2026
c7e7d12
Create dependency-scan.yml
manishvishwakarma89 May 20, 2026
438bcc9
Create docker-lint.yml
manishvishwakarma89 May 20, 2026
d782154
Create devsecops-pipeline.yml
manishvishwakarma89 May 20, 2026
4425041
Create image-scan.yml
manishvishwakarma89 May 20, 2026
74d0365
Update devsecops-pipeline.yml
manishvishwakarma89 May 20, 2026
6b4c582
Create tests.yml
manishvishwakarma89 May 20, 2026
6b62387
Create deploy-to-server.yml
manishvishwakarma89 May 20, 2026
169b100
Create docker-build-push.yml
manishvishwakarma89 May 20, 2026
43a2dc6
Create playbook.yml
manishvishwakarma89 May 20, 2026
0b7cd18
Update code-quality-check.yml
manishvishwakarma89 May 20, 2026
135d33d
Update code-quality-check.yml
manishvishwakarma89 May 20, 2026
eef392f
Update go-version
manishvishwakarma89 May 20, 2026
918f30d
Update code-quality-check.yml
manishvishwakarma89 May 20, 2026
5af7b19
Update large go-version
manishvishwakarma89 May 20, 2026
ea1538a
Update code-quality
manishvishwakarma89 May 20, 2026
faaa4dd
Update code-quality-check.yml
manishvishwakarma89 May 20, 2026
75573fc
Update go.mod
manishvishwakarma89 May 20, 2026
f71a036
Updated ci registry pipeline
manishvishwakarma89 May 20, 2026
5fce991
Update code-quality-check.yml
manishvishwakarma89 May 20, 2026
3f19b5b
Updated go-lang
manishvishwakarma89 May 20, 2026
a8d254b
Update go lang dependencies
manishvishwakarma89 May 20, 2026
59d4e1b
Update go matrix
manishvishwakarma89 May 21, 2026
4424b28
Update code-quality-check.yml
manishvishwakarma89 May 21, 2026
8c7103e
Update go-lint
manishvishwakarma89 May 21, 2026
d2f8b75
Update go.mod
manishvishwakarma89 May 21, 2026
71f6604
Update code-quality-check.yml
manishvishwakarma89 May 21, 2026
2fcf285
Update code-quality-check.yml
manishvishwakarma89 May 21, 2026
472dd2c
Update code-quality-check.yml
manishvishwakarma89 May 21, 2026
fdd9a5d
Update go.mod
manishvishwakarma89 May 21, 2026
854381c
Update main.go
manishvishwakarma89 May 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .github/workflows/code-quality-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: Go Code Quality

on:
workflow_call:

jobs:
code-quality:
runs-on: ubuntu-latest

strategy:
fail-fast: false

matrix:
go-version: ["1.21", "1.22", "1.23"]

steps:
# ==============================
# Checkout Repository
# ==============================

- name: Checkout Code
uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all workflow action refs are SHA-pinned (40 hex chars)
rg -n '^\s*uses:\s*[^@]+@' .github/workflows \
| rg -n -v '@[0-9a-fA-F]{40}$'

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 495


Pin all GitHub Actions to commit SHAs.

Lines 22, 29, and 81 use tag refs (@v4, @v5, @v6) instead of immutable commit SHAs. This is a supply-chain hardening gap and will fail strict pinning policies.

Suggested patch
-      - name: Checkout Code
-        uses: actions/checkout@v4
+      - name: Checkout Code
+        uses: actions/checkout@<full-length-commit-sha>

-      - name: Setup Go ${{ matrix.go-version }}
-        uses: actions/setup-go@v5
+      - name: Setup Go ${{ matrix.go-version }}
+        uses: actions/setup-go@<full-length-commit-sha>
         with:
           go-version: ${{ matrix.go-version }}

       - name: Run golangci-lint
-        uses: golangci/golangci-lint-action@v6
+        uses: golangci/golangci-lint-action@<full-length-commit-sha>
         with:
           version: latest
🧰 Tools
🪛 zizmor (1.25.2)

[error] 22-22: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/code-quality-check.yml at line 22, Replace the floating
action tags with immutable commit SHAs for all "uses:" entries currently
referenced by tags (e.g., actions/checkout@v4, actions/setup-node@v5,
actions/cache@v6) so the workflow pins exact commits; locate each "uses:" line
in the workflow and swap the tag (e.g., `@v4/`@v5/@v6) with the corresponding full
commit SHA from the action's GitHub repo, update any README/maintainer note if
present to document the pinned SHAs, and run a quick local workflow lint to
ensure the YAML remains valid.


Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify checkout steps explicitly disable credential persistence
rg -n -C2 'uses:\s*actions/checkout@' .github/workflows
rg -n 'persist-credentials:\s*false' .github/workflows

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 945


🏁 Script executed:

cat -n .github/workflows/code-quality-check.yml | sed -n '15,30p'

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 596


🏁 Script executed:

cat -n .github/workflows/cd-k8s.yml | sed -n '14,22p'

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 391


🌐 Web query:

GitHub actions checkout persist-credentials security best practice

💡 Result:

Best-practice answer (security): set persist-credentials: false on actions/checkout unless you specifically need later steps to run authenticated git commands using the checkout token. Why: actions/checkout can persist the auth token in the local git config so scripts can run authenticated git commands, and the token is removed during post-job cleanup; opting out via persist-credentials: false prevents that persistence. [1][2] Recommended pattern - Always harden checkout steps: - uses: actions/checkout@v4 (or newer) - with: persist-credentials: false This ensures the token is not persisted to the repository’s git configuration for subsequent steps. [1][2] When you might keep it enabled - Only when you truly need later steps to run authenticated git operations without manually reconfiguring credentials (for example, actions that assume the persisted credentials are available after checkout). [1][3] Additional defense-in-depth (separate from persist-credentials) - Constrain GITHUB_TOKEN permissions (e.g., use a permissions block / least privilege) so that even if credentials were exposed, the blast radius is reduced. [4] Concrete example - name: Checkout uses: actions/checkout@v4 with: persist-credentials: false This is the explicit remediation repeatedly called out by the checkout action maintainers’ guidance and security discussions. [2][5]

Citations:


Add persist-credentials: false to disable credential persistence.

The actions/checkout@v4 step should set persist-credentials: false to prevent the GITHUB_TOKEN from being left in the local git config for subsequent steps, reducing the window of exposure if a step is compromised.

Suggested patch
      - name: Checkout Code
        uses: actions/checkout@v4
+       with:
+         persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Checkout Code
uses: actions/checkout@v4
- name: Checkout Code
uses: actions/checkout@v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 21-26: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 22-22: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/code-quality-check.yml around lines 21 - 23, The Checkout
Code step (name: "Checkout Code", uses: actions/checkout@v4) currently leaves
credentials persisted; update the step to add persist-credentials: false so the
GITHUB_TOKEN is not written to the local git config for later steps, ensuring
the checkout step explicitly disables credential persistence by adding that key
under the same step.

# ==============================
# Setup Go Environment
# ==============================

- name: Setup Go ${{ matrix.go-version }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}

# ==============================
# Verify Go Installation
# ==============================

- name: Verify Go Version
run: go version

# ==============================
# Verify go.mod Exists
# ==============================


- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: backend/go.mod
cache: true

# ==============================
# Install Dependencies
# ==============================

- name: Install Dependencies
run: |
go mod tidy
go mod download

# ==============================
# Format Check
# ==============================

- name: Run gofmt
run: |
test -z "$(gofmt -l .)"

# ==============================
# Static Analysis
# ==============================

- name: Run go vet
run: |
go vet ./...

# ==============================
# GolangCI Lint
# ==============================

- name: Run golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: latest

# ==============================
# Run Unit Tests
# ==============================

- name: Run Tests
run: |
go test ./... -v
97 changes: 97 additions & 0 deletions .github/workflows/dependency-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: Dependency Vulnerability Scan

on:
workflow_call:

jobs:
dependency-scan:
runs-on: ubuntu-latest

strategy:
fail-fast: false

matrix:
go-version: ["1.21", "1.22", "1.23"]

steps:

# ==============================
# Checkout Repository
# ==============================

- name: Checkout Code
uses: actions/checkout@v4
Comment on lines +22 to +23

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/dependency-scan.yml

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 3075


Pin actions to commit SHAs and disable checkout token persistence.

All uses: statements use version tags (v4, v5, 0.24.0) instead of commit SHAs, which leaves a supply-chain gap that zizmor flags. Additionally, actions/checkout@v4 persists the GITHUB_TOKEN to git config by default, but since all subsequent steps only read from the repository (never push), this token is unnecessary and should be disabled via persist-credentials: false.

Also applies to: 29-32, 77-79, 91-93

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 22-27: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 23-23: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/dependency-scan.yml around lines 22 - 23, Replace all
GitHub Action uses: entries that reference version tags (e.g.,
actions/checkout@v4, actions/setup-node@v5, docker/build-push-action@0.24.0)
with their corresponding commit SHAs to remove supply-chain ambiguity, and for
the checkout step(s) (the job step using actions/checkout) add
persist-credentials: false so the GITHUB_TOKEN is not written to git config
since we only read the repo; update the other occurrences called out (the steps
around lines 29-32, 77-79, 91-93) similarly to use pinned SHAs and, where they
are checkout steps, set persist-credentials: false.


# ==============================
# Setup Go Environment
# ==============================

- name: Setup Go ${{ matrix.go-version }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}

# ==============================
# Verify Go Installation
# ==============================

- name: Verify Go Version
run: go version

# ==============================
# Verify go.mod Exists
# ==============================

- name: Check go.mod
run: |
if [ ! -f go.mod ]; then
echo "go.mod file not found!"
exit 1
fi

# ==============================
# Install Dependencies
# ==============================

- name: Install Dependencies
run: |
go mod tidy
go mod download

# ==============================
# Run govulncheck
# ==============================

- name: Install govulncheck
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest

- name: Run Dependency Vulnerability Scan
run: |
govulncheck ./...

# ==============================
# Run Trivy Filesystem Scan
# ==============================

- name: Run Trivy Filesystem Scan
uses: aquasecurity/trivy-action@0.24.0
with:
scan-type: fs
scan-ref: .
format: table
exit-code: 1
ignore-unfixed: true
severity: CRITICAL,HIGH

# ==============================
# Upload Trivy Report (Optional)
# ==============================

- name: Upload Trivy Report
if: always()
uses: actions/upload-artifact@v4
with:
name: trivy-dependency-report
path: trivy-results.sarif
if-no-files-found: ignore
Comment on lines +77 to +97

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

trivy-action aquasecurity format table output files

💡 Result:

To generate a table-formatted output file using aquasecurity/trivy-action, you must configure both the format and output inputs within your workflow step [1][2][3]. By default, trivy-action outputs the results to the job logs in a human-readable table format [4][5]. To capture this output into a file instead of (or in addition to) seeing it in the logs, set the format input to table and provide a file path via the output input [1][2]. Example workflow configuration: - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@v0.36.0 with: image-ref: 'your-image:tag' format: 'table' output: 'trivy-results.txt' Because the table format uses characters that may not render correctly in GitHub's markdown-based Step Summaries or PR comments, it is a common practice to wrap the file content in a markdown code block if you intend to post it to a PR or summary [6][7]. For example, when posting the results as a comment: - name: Format Trivy Scan Result run: | if [ -s trivy-results.txt ]; then echo -e "## Vulnerability Scan Results\n\n```\n$(cat trivy-results.txt)\n```" > formatted-trivy-result.md fi This ensures that the table remains aligned and readable within the GitHub interface [6][7].

Citations:


🌐 Web query:

trivy-action@0.24.0 SARIF format output filename

💡 Result:

In trivy-action, including version 0.24.0, the SARIF format output filename is not determined by a hardcoded default; rather, it is specified by the user using the output input parameter [1][2][3]. To generate a SARIF report and save it to a specific filename, you must explicitly configure the format and output parameters within your workflow step [3][4]: - format: 'sarif' - output: 'filename.sarif' Common usage involves defining a filename, such as trivy-results.sarif, and then referencing that same filename in subsequent steps, such as when uploading to the GitHub Security tab via the codeql-action [1][5][6]. Example configuration: - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@0.24.0 with: scan-type: 'fs' format: 'sarif' output: 'trivy-results.sarif' - name: Upload Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 with: sarif_file: 'trivy-results.sarif' Please note that for trivy-action versions (including 0.24.0), there is an input parameter called limit-severities-for-sarif [2][3][4]. By default, this is false, meaning SARIF output will include all detected vulnerabilities regardless of the configured severity filters [5][7][3]. If you wish to enforce your severity filters in the SARIF output, you must set limit-severities-for-sarif: true [2][3].

Citations:


Trivy scan with format: table will not produce the trivy-results.sarif file.

The scan is configured to output in table format without specifying an output parameter. This causes Trivy to write only to the job logs, not to any file. Lines 95–96 attempt to upload trivy-results.sarif, but since no file is created, the upload step silently no-ops due to if-no-files-found: ignore. Scan findings are never preserved as artifacts.

To fix, set format: sarif and specify the output filename:

Proposed fix
       - name: Run Trivy Filesystem Scan
         uses: aquasecurity/trivy-action@0.24.0
         with:
           scan-type: fs
           scan-ref: .
-          format: table
+          format: sarif
+          output: trivy-results.sarif
           exit-code: 1
           ignore-unfixed: true
           severity: CRITICAL,HIGH
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Run Trivy Filesystem Scan
uses: aquasecurity/trivy-action@0.24.0
with:
scan-type: fs
scan-ref: .
format: table
exit-code: 1
ignore-unfixed: true
severity: CRITICAL,HIGH
# ==============================
# Upload Trivy Report (Optional)
# ==============================
- name: Upload Trivy Report
if: always()
uses: actions/upload-artifact@v4
with:
name: trivy-dependency-report
path: trivy-results.sarif
if-no-files-found: ignore
- name: Run Trivy Filesystem Scan
uses: aquasecurity/trivy-action@0.24.0
with:
scan-type: fs
scan-ref: .
format: sarif
output: trivy-results.sarif
exit-code: 1
ignore-unfixed: true
severity: CRITICAL,HIGH
# ==============================
# Upload Trivy Report (Optional)
# ==============================
- name: Upload Trivy Report
if: always()
uses: actions/upload-artifact@v4
with:
name: trivy-dependency-report
path: trivy-results.sarif
if-no-files-found: ignore
🧰 Tools
🪛 zizmor (1.25.2)

[error] 78-78: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 93-93: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/dependency-scan.yml around lines 77 - 97, The Trivy scan
step ("Run Trivy Filesystem Scan") currently uses format: table and no output,
so it doesn't generate trivy-results.sarif for the "Upload Trivy Report" step;
change the scan step to use format: sarif and add an output parameter to write
the SARIF to trivy-results.sarif (e.g., set output: trivy-results.sarif) so the
"Upload Trivy Report" upload step can find and persist the report; ensure only
the scan step settings (format and output filename) and any related path
references are updated, leaving the "Upload Trivy Report" step intact.

52 changes: 52 additions & 0 deletions .github/workflows/deploy-to-server.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# this will deploy the safe / secure / tested app to Prod server
name: Deploy To Server

on:
workflow_call:


jobs:
deploy:
runs-on: ubuntu-latest
env:
DOCKERHUB_USER: ${{ vars.DOCKERHUB_USER }}
DOCKER_TAG: ${{ github.sha }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: SSH to Prod server & create app folder
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_SSH_HOST }}
username: ${{ secrets.EC2_SSH_USER }}
key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
script: |
echo "Installing required packages & folders"
sudo apt-get update && sudo apt-get install docker.io docker-compose-v2 -y
sudo usermod -aG docker $USER
mkdir -p devops

- name: Copy the docker-compose file
uses: appleboy/scp-action@v1
with:
host: ${{ secrets.EC2_SSH_HOST }}
username: ${{ secrets.EC2_SSH_USER }}
key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
source: docker-compose.yml
target: ~/devops

- name: SSH to Prod server & run the app
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_SSH_HOST }}
username: ${{ secrets.EC2_SSH_USER }}
key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
script: |
echo "Running App"

export DOCKERHUB_USER=${{ vars.DOCKERHUB_USER }}
export DOCKER_TAG=${{ github.sha }}
echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login --username ${{ vars.DOCKERHUB_USER }} --password-stdin
cd ~/devops && docker compose down && docker compose up -d --build --force-recreate --pull always
Comment on lines +31 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the full deploy-to-server.yml workflow
cat -n .github/workflows/deploy-to-server.yml

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 2456


🏁 Script executed:

# Check if docker-compose.yml exists and examine its content
if [ -f docker-compose.yml ]; then
  echo "=== docker-compose.yml ===" 
  cat -n docker-compose.yml
else
  echo "docker-compose.yml not found in root"
  # Search for docker-compose files
  find . -name "docker-compose*.yml" -o -name "docker-compose*.yaml" | head -20
fi

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 1733


🏁 Script executed:

# Check for Docker build workflows in the repo
find .github/workflows -type f \( -name "*.yml" -o -name "*.yaml" \) | head -20

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 505


Remove --build flag—the server doesn't have the build contexts.

The docker-compose.yml defines build: ./backend and build: ./frontend, but the workflow only copies the compose file itself. When Docker Compose runs with --build, it will fail trying to locate those directories on the server. Since images are already built in CI and pushed to DockerHub (per docker-build-push.yml), use --pull always to fetch pre-built images instead.

Proposed fix
-                 cd ~/devops && docker compose down && docker compose up -d --build --force-recreate --pull always
+                 cd ~/devops && docker compose down && docker compose up -d --force-recreate --pull always
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Copy the docker-compose file
uses: appleboy/scp-action@v1
with:
host: ${{ secrets.EC2_SSH_HOST }}
username: ${{ secrets.EC2_SSH_USER }}
key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
source: docker-compose.yml
target: ~/devops
- name: SSH to Prod server & run the app
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_SSH_HOST }}
username: ${{ secrets.EC2_SSH_USER }}
key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
script: |
echo "Running App"
export DOCKERHUB_USER=${{ vars.DOCKERHUB_USER }}
export DOCKER_TAG=${{ github.sha }}
echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login --username ${{ vars.DOCKERHUB_USER }} --password-stdin
cd ~/devops && docker compose down && docker compose up -d --build --force-recreate --pull always
- name: Copy the docker-compose file
uses: appleboy/scp-action@v1
with:
host: ${{ secrets.EC2_SSH_HOST }}
username: ${{ secrets.EC2_SSH_USER }}
key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
source: docker-compose.yml
target: ~/devops
- name: SSH to Prod server & run the app
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.EC2_SSH_HOST }}
username: ${{ secrets.EC2_SSH_USER }}
key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
script: |
echo "Running App"
export DOCKERHUB_USER=${{ vars.DOCKERHUB_USER }}
export DOCKER_TAG=${{ github.sha }}
echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login --username ${{ vars.DOCKERHUB_USER }} --password-stdin
cd ~/devops && docker compose down && docker compose up -d --force-recreate --pull always
🧰 Tools
🪛 zizmor (1.25.2)

[info] 49-49: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[info] 51-51: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 32-32: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 41-41: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy-to-server.yml around lines 31 - 52, In the "SSH to
Prod server & run the app" step, remove the --build flag from the docker compose
up command in the script (the line currently running cd ~/devops && docker
compose down && docker compose up -d --build --force-recreate --pull always) so
Compose doesn't try to build local contexts; keep --pull always (and
--force-recreate, -d) so the server pulls the pre-built images pushed by CI and
recreates containers from those images.

46 changes: 46 additions & 0 deletions .github/workflows/devsecops-pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: DevSecOps End To End Pipeline

on:
push:
branches:
- main
- feat/dev

jobs:
Comment on lines +3 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add explicit workflow permissions to enforce least privilege.

Without a permissions: block, token scope is left to repo/org defaults, which is weaker than explicit least-privilege.

Suggested patch
 on: 
     push:
         branches: 
           - main
           - feat/dev
+
+permissions:
+    contents: read
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:
push:
branches:
- main
- feat/dev
jobs:
on:
push:
branches:
- main
- feat/dev
permissions:
contents: read
jobs:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/devsecops-pipeline.yml around lines 3 - 9, Add an explicit
top-level GitHub Actions permissions block (placed at the workflow root
alongside the existing "on: push" and before "jobs:") that enumerates only the
token scopes your pipeline needs; update the workflow to replace implicit
repo/org defaults with least-privilege entries (for example set contents: read
and any other minimal scopes required by your jobs such as id-token: write only
if using OIDC, or checks: write if you create checks), and ensure the
permissions list is kept minimal and aligned to the actions run in the jobs
section.

# CI (Continous Integration with Security Scanning)
code-quality:
uses: ./.github/workflows/code-quality-check.yml

secrets-scan:
uses: ./.github/workflows/secret-scan.yml
secrets: inherit

dependency-scan:
uses: ./.github/workflows/dependency-scan.yml

docker-scan:
uses: ./.github/workflows/docker-lint.yml

# Build once the security scans and tests are complete
build:
needs: [code-quality, secrets-scan, dependency-scan, docker-scan]
uses: ./.github/workflows/docker-build-push.yml
secrets: inherit

tests:
uses: ./.github/workflows/tests.yml

# Image scan
trivy:
needs: [build]
uses: ./.github/workflows/image-scan.yml
secrets: inherit


# CD : Deploy to Production
deploy:
needs: [trivy]
uses: ./.github/workflows/deploy-to-server.yml
Comment on lines +24 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Gate build/deploy on tests to prevent shipping unverified code.

tests is not in the dependency chain for build/trivy/deploy, so deployment can proceed even if tests fail.

Suggested patch
     # Build once the security scans and tests are complete
     build:
-        needs: [code-quality, secrets-scan, dependency-scan, docker-scan]
+        needs: [code-quality, secrets-scan, dependency-scan, docker-scan, tests]
         uses: ./.github/workflows/docker-build-push.yml
         secrets: inherit
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Build once the security scans and tests are complete
build:
needs: [code-quality, secrets-scan, dependency-scan, docker-scan]
uses: ./.github/workflows/docker-build-push.yml
secrets: inherit
tests:
uses: ./.github/workflows/tests.yml
# Image scan
trivy:
needs: [build]
uses: ./.github/workflows/image-scan.yml
secrets: inherit
# CD : Deploy to Production
deploy:
needs: [trivy]
uses: ./.github/workflows/deploy-to-server.yml
# Build once the security scans and tests are complete
build:
needs: [code-quality, secrets-scan, dependency-scan, docker-scan, tests]
uses: ./.github/workflows/docker-build-push.yml
secrets: inherit
tests:
uses: ./.github/workflows/tests.yml
# Image scan
trivy:
needs: [build]
uses: ./.github/workflows/image-scan.yml
secrets: inherit
# CD : Deploy to Production
deploy:
needs: [trivy]
uses: ./.github/workflows/deploy-to-server.yml
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 25-28: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)


[warning] 30-33: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)


[warning] 34-40: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)


[warning] 27-27: secrets unconditionally inherited by called workflow (secrets-inherit): this reusable workflow

(secrets-inherit)


[warning] 36-36: secrets unconditionally inherited by called workflow (secrets-inherit): this reusable workflow

(secrets-inherit)


[warning] 43-43: secrets unconditionally inherited by called workflow (secrets-inherit): this reusable workflow

(secrets-inherit)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/devsecops-pipeline.yml around lines 24 - 43, The pipeline
currently allows build/trivy/deploy to run before the tests job completes;
update the job dependencies so tests are required before building/deploying by
adding the tests job to the build job's needs array (i.e., change build's needs
to include "tests" alongside code-quality, secrets-scan, dependency-scan,
docker-scan) so that build -> trivy -> deploy cannot run unless tests succeed.

secrets: inherit


29 changes: 29 additions & 0 deletions .github/workflows/docker-build-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Docker Build & Push

on:
workflow_call:

jobs:
build-and-push:
runs-on: ubuntu-latest

steps:

- name: Code Checkout
uses: actions/checkout@v4

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build & Push to Docker Hub
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ vars.DOCKERHUB_USER }}/github-actions-app:${{ github.ref_name }}
${{ vars.DOCKERHUB_USER }}/github-actions-app:latest
${{ vars.DOCKERHUB_USER }}/github-actions-app:${{ github.sha}}
Comment on lines +21 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

This build job doesn't satisfy the downstream image contract.

The stack added in this PR scans and deploys backend/frontend images, but this workflow builds one root-context image named github-actions-app. Until the repository names, tags, and build contexts are aligned, the scan and deploy stages cannot consume the artifact produced here.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 22-22: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/docker-build-push.yml around lines 21 - 29, The workflow's
"Build & Push to Docker Hub" step creates a single root image
"github-actions-app" which doesn't match downstream consumers; update the
docker/build-push-action@v6 usage to build and push separate images for each
component (e.g., backend and frontend) by invoking the action twice or using
multiple build targets with distinct contexts (./backend and ./frontend), set
per-image tags that downstream expects (use ${{ vars.DOCKERHUB_USER
}}/<repo-name>-backend:${{ github.ref_name }}, ...-frontend:${{ github.ref_name
}} and include :latest and ${{ github.sha }} variants), and ensure push: true so
the scan/deploy stages can reference the exact repository names and tags they
require.

25 changes: 25 additions & 0 deletions .github/workflows/docker-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Docker Lint

on:
workflow_call:
pull_request:

jobs:
docker-lint:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Lint Backend Dockerfile
run: |
docker run --rm \
-v ${{ github.workspace }}/backend/Dockerfile:/Dockerfile \
hadolint/hadolint hadolint --ignore DL3018 /Dockerfile

- name: Lint Frontend Dockerfile
run: |
docker run --rm \
-v ${{ github.workspace }}/frontend/Dockerfile:/Dockerfile \
hadolint/hadolint hadolint --ignore DL3018 /Dockerfile
Comment on lines +12 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, check if the workflow file exists and read its contents
cat -n .github/workflows/docker-lint.yml

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 902


🏁 Script executed:

# Verify GitHub Actions best practices for checkout action
# Search for documentation on persist-credentials and commit SHA pinning
web_search_results=$(curl -s "https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions" 2>/dev/null || echo "offline")
echo "Checking GitHub Actions security practices..."

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 144


🏁 Script executed:

# Check if hadolint provides versioned images
# Look for information on hadolint Docker image tagging
echo "Checking hadolint image availability..."

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 138


🏁 Script executed:

cat -n .github/workflows/docker-lint.yml

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 902


🌐 Web query:

GitHub Actions actions/checkout@v4 commit SHA pinning security best practices

💡 Result:

Pinning GitHub Actions, including actions/checkout@v4, to a full-length commit SHA is considered a critical security best practice for protecting CI/CD pipelines against supply chain attacks [1][2][3]. Security Rationale Using a version tag (like @v4) creates a mutable reference, meaning the code executed can change if the tag is moved, deleted, or if the repository hosting the action is compromised [2][4][3]. A malicious actor who gains access to an action's repository could update a tag to point to a compromised commit, automatically injecting malicious code into every workflow using that tag [5][2][4]. Pinning to a full-length commit SHA provides an immutable reference, ensuring the exact same code is executed every time, which prevents such silent redirection [1][4][3]. Implementation Best Practices - Pin to Full SHA: Always use the full-length commit SHA (e.g., uses: actions/checkout@b4ffde6) [1][3]. - Use Version Comments: When pinning to a SHA, include a comment indicating the corresponding version tag for readability and maintainability (e.g., uses: actions/checkout@b4ffde6 # v4.1.1) [5][6]. - Use Automation Tools: Manually updating pinned SHAs is error-prone. Use tools like Dependabot, Renovate, or specialized CLI tools like "ratchet" to automate the process of keeping your pinned SHAs up to date with the latest releases [5][7][2]. - Enforce via Policy: GitHub supports organization and repository-level policies that can mandate SHA pinning, causing workflows to fail if they use unpinned actions [8][3]. Workflow Governance While SHA pinning provides the strongest security guarantee, it requires more maintenance than using tags [7][2]. Organizations often choose to: - Prioritize high-risk workflows: If maintaining pinned SHAs for every single action is impractical, prioritize pinning for high-risk workflows, such as those that handle sensitive secrets, perform deployments, or have broad repository permissions [9][2][10]. - Use Trusted Actions: For actions that are not pinned, rely only on trusted creators (e.g., those with the Verified Creator badge) [3]. Note, however, that even trusted actions are vulnerable to account compromise if not pinned [4][3]. To find the commit SHA for a specific action version, you can navigate to the action's GitHub repository, switch to the desired tag in the branch/tag menu, and copy the full-length commit SHA [1][9]. Alternatively, you can run the workflow once with the tag and check the "Setup Job" section of the workflow execution logs to see the exact SHA that was resolved and downloaded [1].

Citations:


🌐 Web query:

hadolint Docker image versioning tags available

💡 Result:

Hadolint is distributed as a Docker image on Docker Hub and the GitHub Container Registry (ghcr.io), providing various tags to support different use cases and versions [1][2][3]. The available tags generally fall into the following categories: 1. Version-specific tags: These tags correspond to specific releases of Hadolint (e.g., v2.14.0) [1][2]. You can also find variants of these specific versions, such as v2.14.0-alpine or v2.14.0-debian, to suit different base image requirements [1][2]. 2. Latest/Rolling tags: The tag latest provides the most recent stable release [1]. Similar to versioned tags, there are variants such as latest-alpine and latest-debian [1][2][3]. 3. Architecture and commit-specific tags: The registry also includes tags linked to specific Git commit SHAs and CPU architectures (e.g., amd64, arm64), often formatted as -- [1]. Best practices for using these images include: - In CI/CD pipelines, it is recommended to use a specific version tag or pin the image by its digest to ensure consistent build environments and avoid the risks associated with mutable tags like latest [4][5]. - If you require shell access within the container, use the -alpine or -debian variants [2][3][6]. - You can pull images from the primary repository at hadolint/hadolint or from the GitHub Container Registry at ghcr.io/hadolint/hadolint [2][3]. You can view the full, up-to-date list of available tags directly on the official Docker Hub page [1].

Citations:


Pin actions and container images to specific, immutable versions for deterministic and secure workflows.

actions/checkout@v4 uses a mutable tag reference instead of a commit SHA, and neither persist-credentials: false nor explicit hadolint/hadolint image pinning is set. This allows workflow behavior to drift silently if the tag moves, the action is compromised, or the container image is updated. Pin actions/checkout to a full-length commit SHA with a version comment, add persist-credentials: false to limit credential exposure, and pin hadolint/hadolint to a specific version tag (e.g., v2.14.0) or digest.

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 12-13: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 13-13: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/docker-lint.yml around lines 12 - 25, Update the workflow
to pin mutable references: replace uses: actions/checkout@v4 with the full
commit SHA for that action and add the persist-credentials: false input to the
checkout step (refer to the Checkout Code step); also pin the hadolint container
image used in the Lint Backend Dockerfile and Lint Frontend Dockerfile steps to
a specific, immutable tag or digest (e.g., hadolint/hadolint:v2.14.0 or a
digest) instead of the floating hadolint/hadolint reference so the docker run
invocations are deterministic.

47 changes: 47 additions & 0 deletions .github/workflows/image-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Image scanner

on:
workflow_call:
secrets:
DOCKER_USERNAME:
required: true
DOCKER_PASSWORD:
required: true

jobs:
image-scanner:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
# FIXED: Swapped out mismatched names to match the inputs declared above exactly
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
Comment on lines +19 to +24

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

find .github/workflows -name "image-scan.yml" -type f

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 131


🏁 Script executed:

cat -n .github/workflows/image-scan.yml

Repository: LondheShubham153/github-actions-kubernetes-masterclass

Length of output: 1837


Use the declared secret name for Docker Hub login.

The workflow declares DOCKER_PASSWORD as a required secret (line 8), but line 24 references secrets.DOCKER_TOKEN, which is undefined. On workflow_call, this will cause the login step to fail.

Proposed fix
       - name: Login to Docker Hub
         uses: docker/login-action@v3
         with:
           username: ${{ secrets.DOCKER_USERNAME }}
-          password: ${{ secrets.DOCKER_TOKEN }}
+          password: ${{ secrets.DOCKER_PASSWORD }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
# FIXED: Swapped out mismatched names to match the inputs declared above exactly
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
# FIXED: Swapped out mismatched names to match the inputs declared above exactly
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
🧰 Tools
🪛 actionlint (1.7.12)

[error] 24-24: property "docker_token" is not defined in object type {actions_runner_debug: string; actions_step_debug: string; docker_password: string; docker_username: string; github_token: string}

(expression)

🪛 zizmor (1.25.2)

[error] 20-20: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/image-scan.yml around lines 19 - 24, The Docker Hub login
step uses the wrong secret name; update the "Login to Docker Hub" step that
calls docker/login-action@v3 to use the declared secret name for the password
(secrets.DOCKER_PASSWORD) instead of secrets.DOCKER_TOKEN so the password input
matches the workflow_call declaration; ensure the with: password: field
references secrets.DOCKER_PASSWORD while username remains
secrets.DOCKER_USERNAME.


# Note: Removed the manual Trivy installation apt steps since the action below handles it natively

- name: Scan Backend Image
uses: aquasecurity/trivy-action@v0.36.0 # Fixed the version prefix to prevent resolution errors
with:
image-ref: ${{ secrets.DOCKER_USERNAME }}/skillpulse-backend:latest
format: 'table'
exit-code: 1
severity: 'CRITICAL,HIGH'
trivyignores: .trivyignore

- name: Scan Frontend Image
uses: aquasecurity/trivy-action@v0.36.0 # Fixed all version prefix to prevent resolution errors
with:
image-ref: ${{ secrets.DOCKER_USERNAME }}/skillpulse-frontend:latest
format: 'table'
exit-code: 1
severity: 'CRITICAL,HIGH'
Comment on lines +28 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Align the scanned image names with the build workflow output.

This job scans skillpulse-backend and skillpulse-frontend, but .github/workflows/docker-build-push.yml currently publishes ${DOCKERHUB_USER}/github-actions-app:* only. As-is, this workflow will either scan stale images or fail to pull the artifacts built by this PR.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 29-29: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 38-38: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/image-scan.yml around lines 28 - 43, The Trivy scan steps
"Scan Backend Image" and "Scan Frontend Image" reference image refs `${{
secrets.DOCKER_USERNAME }}/skillpulse-backend:latest` and
`/skillpulse-frontend:latest` but the build/push workflow only publishes
`${DOCKERHUB_USER}/github-actions-app:*`; update the image-ref values in the
Trivy steps to match the artifact names/tags produced by the docker-build-push
workflow (e.g., change both image-ref entries to use `${{
secrets.DOCKER_USERNAME }}/github-actions-app:latest` or the exact tag name
emitted by the build), or alternatively modify the build workflow to publish
`skillpulse-backend` and `skillpulse-frontend` images so names align with the
Trivy `image-ref` entries; ensure the change is applied to the two Trivy steps
that use `aquasecurity/trivy-action@v0.36.0`.


- name: Logout from Docker Hub
if: always()
run: docker logout
Loading