Ci/v2 pipeline - #18
Conversation
|
Note Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported. |
📝 WalkthroughWalkthroughThis PR refactors the CI/CD pipeline from three separate environment-specific deployment workflows ( ChangesCI/CD Pipeline Modularization
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with 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.
Inline comments:
In @.github/workflows/_build.yml:
- Around line 3-10: The workflow_dispatch trigger is missing the environment
input used later as inputs.environment; add an environment input under
workflow_dispatch with the same config as workflow_call (description: 'Target
environment — controls which ecosystem config is bundled (dev | staging |
prod)', required: true, type: string) so that the manual trigger provides a
defined inputs.environment value referenced by the build steps; update the
top-level workflow_dispatch block (the workflow trigger section) to include this
input so artifact/file names that use inputs.environment are valid.
In @.github/workflows/_deploy.yml:
- Around line 43-52: Replace the password-based sshpass setup with SSH key-based
auth: remove the "Install sshpass" and "Prepare SSH helper" steps that export
SSHPASS, add an actions/ssh-agent step to load a private key from a secret
(e.g., PRIV_KEY) into the agent, and ensure the subsequent SSH commands (the
remote mkdir, scp, and ssh execution steps) run without sshpass so they
authenticate via the agent/private key; also update documentation or deployment
servers to have the corresponding public key installed in authorized_keys.
- Around line 55-57: Remove the StrictHostKeyChecking=no flags from all SSH and
SCP invocations (the sshpass -e ssh "... mkdir -p /home/${{ secrets.USERNAME
}}/tmp/nestjs" command and the other two remote commands/scp lines) and add a
preceding workflow step that populates ~/.ssh/known_hosts with the target host
key using the secrets.HOST value (e.g., run ssh-keyscan -H ${{ secrets.HOST }}
>> ~/.ssh/known_hosts or equivalent) so host key verification is enforced for
subsequent ssh/scp actions; ensure the new step runs before any sshpass/scp
steps and that secrets.USERNAME and secrets.HOST are still used for remote
operations.
- Around line 55-57: The ssh/scp invocations (e.g., the ssh call using "sshpass
-e ssh" that runs "mkdir -p /home/${{ secrets.USERNAME }}/tmp/nestjs" and the
other two scp/ssh calls) lack timeouts and can hang; update each invocation to
include SSH connection/session timeouts and a bounded overall command timeout:
add SSH options like -o ConnectTimeout=10 -o ConnectionAttempts=2 -o
ServerAliveInterval=15 -o ServerAliveCountMax=2 to the ssh/scp commands, and
wrap the whole invocation with a timeout (e.g., prefix with "timeout 60s") so
the pipeline fails fast on network issues; apply these changes to every
occurrence of "sshpass -e ssh" and "scp -o ..." in the workflow.
In @.github/workflows/_security.yml:
- Around line 3-7: Add an explicit least-privilege permissions block to the
reusable workflow by declaring permissions: contents: read at the top-level of
the workflow (alongside on:), so both jobs (audit and secret-scan) run with
read-only repo access; update the workflow file to include this permissions
declaration to enforce least privilege for the reusable workflow.
- Around line 24-25: The CI step named "Run npm audit" currently invokes "npm
audit --audit-level=high --omit=dev" which skips auditing dev dependencies even
though the workflow installs dev deps; update that step to run a full audit by
removing the "--omit=dev" flag (i.e., run "npm audit --audit-level=high") so the
audit covers the entire dependency tree including dev dependencies and CI-only
tools used in the build.
In @.github/workflows/_test.yml:
- Around line 12-13: Add least-privilege workflow permissions and prevent
checkout from persisting GitHub credentials: at the top-level of the workflow
add a permissions block with contents: read (i.e., permissions: contents: read)
and update the actions/checkout@v4 step (the "Checkout code" step) to include
persist-credentials: false so the runner won't retain the GITHUB_TOKEN for
subsequent commands (reducing token exposure during npm install or other
scripts).
In @.github/workflows/pipeline.yml:
- Around line 115-119: The workflow's manual trigger condition allows
workflow_dispatch from any branch because it only checks inputs.environment;
update the if condition for the deploy jobs (e.g., the block referencing
github.event_name, github.ref, inputs.environment and the deploy-dev job) to
require both inputs.environment == 'dev' AND github.ref == 'refs/heads/dev' for
workflow_dispatch paths (similarly enforce the correct branch for other
environments), so manual deployments only run when the trigger comes from the
matching branch as well as the matching environment input.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: e1648e70-92a1-4fb5-abbd-6c5f8f5b1715
📒 Files selected for processing (10)
.github/workflows/_build.yml.github/workflows/_deploy.yml.github/workflows/_lint.yml.github/workflows/_security.yml.github/workflows/_test.yml.github/workflows/dev-deployment.yaml.github/workflows/lint-build-test.yaml.github/workflows/main-deployment.yaml.github/workflows/pipeline.yml.github/workflows/staging-deployment.yaml
💤 Files with no reviewable changes (4)
- .github/workflows/staging-deployment.yaml
- .github/workflows/lint-build-test.yaml
- .github/workflows/main-deployment.yaml
- .github/workflows/dev-deployment.yaml
| on: | ||
| workflow_dispatch: | ||
| workflow_call: | ||
| inputs: | ||
| environment: | ||
| description: 'Target environment — controls which ecosystem config is bundled (dev | staging | prod)' | ||
| required: true | ||
| type: string |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/_build.ymlRepository: hngprojects/flowbrand-be
Length of output: 2539
workflow_dispatch currently has no environment input, but the workflow requires it.
Lines 20–22 define environment variables using inputs.environment; on manual runs via workflow_dispatch this value is undefined, resulting in invalid filenames and artifact names (e.g., nestjs-.tar.gz, build-), causing failures in lines 49, 52, and 63. Add the environment input to workflow_dispatch with the same configuration as workflow_call.
Suggested change
on:
workflow_dispatch:
+ inputs:
+ environment:
+ description: Target environment
+ required: true
+ type: choice
+ options: [dev, staging, prod]
workflow_call:
inputs:
environment:🤖 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/_build.yml around lines 3 - 10, The workflow_dispatch
trigger is missing the environment input used later as inputs.environment; add
an environment input under workflow_dispatch with the same config as
workflow_call (description: 'Target environment — controls which ecosystem
config is bundled (dev | staging | prod)', required: true, type: string) so that
the manual trigger provides a defined inputs.environment value referenced by the
build steps; update the top-level workflow_dispatch block (the workflow trigger
section) to include this input so artifact/file names that use
inputs.environment are valid.
| - name: Install sshpass | ||
| run: sudo apt-get install -y sshpass | ||
|
|
||
| - name: Prepare SSH helper | ||
| # Write a one-liner wrapper so we don't repeat the sshpass boilerplate. | ||
| # SSHPASS env var is read by sshpass automatically — keeps the password | ||
| # out of the process argument list. | ||
| run: | | ||
| echo "SSHPASS=${{ secrets.PASSWORD }}" >> $GITHUB_ENV | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift
❓ Verification inconclusive
Script executed:
cat -n .github/workflows/_deploy.ymlRepository: hngprojects/flowbrand-be
Repository: hngprojects/flowbrand-be
Exit code: 0
stdout:
1 name: _deploy
2
3 on:
4 workflow_call:
5 inputs:
6 environment:
7 description: 'Target environment (dev | staging | prod)'
8 required: true
9 type: string
10 artifact_name:
11 description: 'GitHub Actions artifact name produced by the build job'
12 required: true
13 type: string
14 tarball:
15 description: 'Tarball filename (e.g. nestjs-dev.tar.gz)'
16 required: true
17 type: string
18 secrets:
19 HOST:
20 required: true
21 USERNAME:
22 required: true
23 PASSWORD:
24 required: true
25
26 env:
27 DEPLOY_DIR: ${{ vars.DEPLOY_DIR }}
28 PM2_ENV: ${{ inputs.environment == 'prod' && 'main' || inputs.environment }}
29 REMOTE_TMP: /home/${{ secrets.USERNAME }}/tmp/nestjs
30
31 jobs:
32 deploy:
33 name: Deploy (${{ inputs.environment }})
34 runs-on: ubuntu-latest
35 environment: ${{ inputs.environment }}
36
37 steps:
38 - name: Download build artifact
39 uses: actions/download-artifact@v4
40 with:
41 name: ${{ inputs.artifact_name }}
42
43 - name: Install sshpass
44 run: sudo apt-get install -y sshpass
45
46 - name: Prepare SSH helper
47 # Write a one-liner wrapper so we don't repeat the sshpass boilerplate.
48 # SSHPASS env var is read by sshpass automatically — keeps the password
49 # out of the process argument list.
50 run: |
51 echo "SSHPASS=${{ secrets.PASSWORD }}" >> $GITHUB_ENV
52
53 - name: Ensure remote tmp directory exists
54 run: |
55 sshpass -e ssh -o StrictHostKeyChecking=no \
56 ${{ secrets.USERNAME }}@${{ secrets.HOST }} \
57 "mkdir -p /home/${{ secrets.USERNAME }}/tmp/nestjs"
58
59 - name: Copy tarball to server
60 run: |
61 echo "══════════════════════════════════════════"
62 echo " Copying ${{ inputs.tarball }} → ${{ secrets.HOST }}:${{ env.REMOTE_TMP }}"
63 echo "══════════════════════════════════════════"
64 sshpass -e scp -o StrictHostKeyChecking=no \
65 ${{ inputs.tarball }} \
66 ${{ secrets.USERNAME }}@${{ secrets.HOST }}:${{ env.REMOTE_TMP }}/
67
68 - name: Deploy on server
69 env:
70 TARBALL: ${{ inputs.tarball }}
71 ENVIRONMENT: ${{ inputs.environment }}
72 run: |
73 sshpass -e ssh -o StrictHostKeyChecking=no \
74 ${{ secrets.USERNAME }}@${{ secrets.HOST }} << REMOTE
75 set -euo pipefail
76
77 DEPLOY_DIR="${{ env.DEPLOY_DIR }}"
78 PM2_ENV="${{ env.PM2_ENV }}"
79 REMOTE_TMP="/home/${{ secrets.USERNAME }}/tmp/nestjs"
80 TARBALL="${{ inputs.tarball }}"
81
82 echo "══════════════════════════════════════════"
83 echo " Environment : ${{ inputs.environment }}"
84 echo " Deploy dir : \${DEPLOY_DIR}"
85 echo " PM2 env : \${PM2_ENV}"
86 echo "══════════════════════════════════════════"
87
88 echo "── Navigating to deploy dir ───────────────"
89 cd "\${DEPLOY_DIR}"
90
91 echo "── Extracting tarball ─────────────────────"
92 tar -xzf "\${REMOTE_TMP}/\${TARBALL}" -C .
93 rm -f "\${REMOTE_TMP}/\${TARBALL}"
94
95 echo "── Moving runtime files out of dist ───────"
96 mv dist/package.json .
97 mv dist/*-ecosystem-config.json .
98 mv dist/deployment.sh .
99
100 echo "── Running deployment script ──────────────"
101 chmod +x deployment.sh
102 ./deployment.sh "\${PM2_ENV}"
103
104 echo "══════════════════════════════════════════"
105 echo " ✅ Deploy complete (${{ inputs.environment }})"
106 echo "══════════════════════════════════════════"
107 REMOTE
Replace password authentication with SSH key-based authentication.
The current password-based approach with sshpass is less secure and harder to rotate. SSH keys are the standard for automated deployments in CI/CD—GitHub Actions natively supports them via secrets and ssh-agent. This would require provisioning the public key on your deployment servers and updating the three SSH commands (mkdir, scp, and ssh) to use key-based auth, but the security improvement justifies the effort.
🤖 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.yml around lines 43 - 52, Replace the
password-based sshpass setup with SSH key-based auth: remove the "Install
sshpass" and "Prepare SSH helper" steps that export SSHPASS, add an
actions/ssh-agent step to load a private key from a secret (e.g., PRIV_KEY) into
the agent, and ensure the subsequent SSH commands (the remote mkdir, scp, and
ssh execution steps) run without sshpass so they authenticate via the
agent/private key; also update documentation or deployment servers to have the
corresponding public key installed in authorized_keys.
| sshpass -e ssh -o StrictHostKeyChecking=no \ | ||
| ${{ secrets.USERNAME }}@${{ secrets.HOST }} \ | ||
| "mkdir -p /home/${{ secrets.USERNAME }}/tmp/nestjs" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /dev/null && git -C . ls-files ".github/workflows/_deploy.yml" 2>/dev/null || find . -name "_deploy.yml" -type f 2>/dev/null | head -5Repository: hngprojects/flowbrand-be
Length of output: 168
🏁 Script executed:
cat -n .github/workflows/_deploy.yml 2>/dev/null || echo "File not found in expected location"Repository: hngprojects/flowbrand-be
Length of output: 4493
Remove StrictHostKeyChecking=no and populate SSH known_hosts to prevent MITM attacks.
SSH host key verification is disabled across all remote operations (lines 55, 64, 73), creating a man-in-the-middle vulnerability. This allows attackers to intercept both artifact transfer (scp) and remote command execution (ssh), which is a release-blocking security risk.
Add a step to populate ~/.ssh/known_hosts with the deployment host's key before SSH operations, then remove the StrictHostKeyChecking=no flag from all three commands:
Suggested change
+ - name: Trust deploy host key
+ run: |
+ mkdir -p ~/.ssh
+ ssh-keyscan -H "${{ secrets.HOST }}" >> ~/.ssh/known_hosts
+
- name: Ensure remote tmp directory exists
run: |
- sshpass -e ssh -o StrictHostKeyChecking=no \
+ sshpass -e ssh \
${{ secrets.USERNAME }}@${{ secrets.HOST }} \
"mkdir -p /home/${{ secrets.USERNAME }}/tmp/nestjs"
- name: Copy tarball to server
run: |
echo "══════════════════════════════════════════"
echo " Copying ${{ inputs.tarball }} → ${{ secrets.HOST }}:${{ env.REMOTE_TMP }}"
echo "══════════════════════════════════════════"
- sshpass -e scp -o StrictHostKeyChecking=no \
+ sshpass -e scp \
${{ inputs.tarball }} \
${{ secrets.USERNAME }}@${{ secrets.HOST }}:${{ env.REMOTE_TMP }}/
- name: Deploy on server
env:
TARBALL: ${{ inputs.tarball }}
ENVIRONMENT: ${{ inputs.environment }}
run: |
- sshpass -e ssh -o StrictHostKeyChecking=no \
+ sshpass -e ssh \
${{ secrets.USERNAME }}@${{ secrets.HOST }} << REMOTE🤖 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.yml around lines 55 - 57, Remove the
StrictHostKeyChecking=no flags from all SSH and SCP invocations (the sshpass -e
ssh "... mkdir -p /home/${{ secrets.USERNAME }}/tmp/nestjs" command and the
other two remote commands/scp lines) and add a preceding workflow step that
populates ~/.ssh/known_hosts with the target host key using the secrets.HOST
value (e.g., run ssh-keyscan -H ${{ secrets.HOST }} >> ~/.ssh/known_hosts or
equivalent) so host key verification is enforced for subsequent ssh/scp actions;
ensure the new step runs before any sshpass/scp steps and that secrets.USERNAME
and secrets.HOST are still used for remote operations.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/_deploy.ymlRepository: hngprojects/flowbrand-be
Length of output: 4493
🌐 Web query:
SSH ConnectTimeout ServerAliveInterval ServerAliveCountMax sshpass timeout options
💡 Result:
The query refers to SSH and sshpass timeout-related options: ConnectTimeout, ServerAliveInterval, ServerAliveCountMax, and sshpass timeout options. OpenSSH ssh client options (from ssh_config(5) and ssh(1) man pages): - ConnectTimeout: Specifies the timeout (in seconds) for connecting to the SSH server, covering TCP connection and initial handshake. Use on command line as ssh -o ConnectTimeout=10 user@host or in ~/.ssh/config [1][2][3][4]. - ServerAliveInterval: Interval (seconds) after which, if no data received from server, ssh sends a message requesting response. Default 0 (disabled) [1][5][6][2]. - ServerAliveCountMax: Number of unanswered server alive messages before disconnecting. Default 3. E.g., with ServerAliveInterval 15, disconnects after ~45s [1][5][6][2]. These can be set in ~/.ssh/config: Host * ConnectTimeout 10 ServerAliveInterval 60 ServerAliveCountMax 3 [7][2]. sshpass has no built-in timeout option; it runs the ssh command non-interactively with password. Use ssh options like ConnectTimeout via sshpass -p pass ssh -o ConnectTimeout=10 user@host, or wrap with timeout(1): timeout 30 sshpass... [8][9][7][10]. For overall command timeout, use the timeout utility from coreutils [7].
Citations:
- 1: https://man7.org/linux/man-pages/man5/ssh_config.5.html
- 2: https://man.openbsd.org/i386/ssh_config
- 3: https://www.daemon-systems.org/man/ssh_config.5.html
- 4: https://github.com/openssh/openssh-portable/blob/b5b405fe/ssh_config.5
- 5: https://unix.stackexchange.com/questions/3026/what-do-options-serveraliveinterval-and-clientaliveinterval-in-sshd-config-d
- 6: https://manpages.ubuntu.com/manpages/focal/man5/ssh_config.5.html
- 7: https://stackoverflow.com/questions/4936807/how-to-set-ssh-timeout
- 8: https://github.com/kevinburke/sshpass/blob/master/sshpass.1
- 9: https://linux.die.net/man/1/sshpass
- 10: http://sshpass.com/
Add SSH/SCP connection timeouts to prevent hung deploy jobs.
These external network calls currently have no connect/session timeout guards, so transient network failures can block runners indefinitely. Add timeout protection to all three ssh/scp invocations:
Suggested change
- sshpass -e ssh -o StrictHostKeyChecking=no \
+ sshpass -e ssh -o StrictHostKeyChecking=no -o ConnectTimeout=30 -o ServerAliveInterval=15 -o ServerAliveCountMax=3 \
${{ secrets.USERNAME }}@${{ secrets.HOST }} \
"mkdir -p /home/${{ secrets.USERNAME }}/tmp/nestjs"
...
- sshpass -e scp -o StrictHostKeyChecking=no \
+ sshpass -e scp -o StrictHostKeyChecking=no -o ConnectTimeout=30 \
${{ inputs.tarball }} \
${{ secrets.USERNAME }}@${{ secrets.HOST }}:${{ env.REMOTE_TMP }}/
...
- sshpass -e ssh -o StrictHostKeyChecking=no \
+ sshpass -e ssh -o StrictHostKeyChecking=no -o ConnectTimeout=30 -o ServerAliveInterval=15 -o ServerAliveCountMax=3 \
${{ secrets.USERNAME }}@${{ secrets.HOST }} << REMOTE🤖 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.yml around lines 55 - 57, The ssh/scp invocations
(e.g., the ssh call using "sshpass -e ssh" that runs "mkdir -p /home/${{
secrets.USERNAME }}/tmp/nestjs" and the other two scp/ssh calls) lack timeouts
and can hang; update each invocation to include SSH connection/session timeouts
and a bounded overall command timeout: add SSH options like -o ConnectTimeout=10
-o ConnectionAttempts=2 -o ServerAliveInterval=15 -o ServerAliveCountMax=2 to
the ssh/scp commands, and wrap the whole invocation with a timeout (e.g., prefix
with "timeout 60s") so the pipeline fails fast on network issues; apply these
changes to every occurrence of "sshpass -e ssh" and "scp -o ..." in the
workflow.
| on: | ||
| workflow_dispatch: | ||
| workflow_call: | ||
|
|
||
| jobs: |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/_security.ymlRepository: hngprojects/flowbrand-be
Length of output: 1181
🏁 Script executed:
rg -r '${{ jobs.workflow_call' --type=yaml .github/workflows/ || rg 'uses:.*_security' --type=yaml .github/workflows/Repository: hngprojects/flowbrand-be
Length of output: 143
🏁 Script executed:
cat -n .github/workflows/pipeline.ymlRepository: hngprojects/flowbrand-be
Length of output: 6666
Add explicit least-privilege permissions to this reusable security workflow.
This workflow lacks explicit permissions declaration and should rely on repository defaults. Since both jobs (audit and secret-scan) only perform read-only operations (repository checkout and scanning), explicitly declaring contents: read enforces the principle of least privilege and is a security best practice for reusable workflows.
Suggested change
on:
workflow_dispatch:
workflow_call:
+
+permissions:
+ contents: read🤖 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/_security.yml around lines 3 - 7, Add an explicit
least-privilege permissions block to the reusable workflow by declaring
permissions: contents: read at the top-level of the workflow (alongside on:), so
both jobs (audit and secret-scan) run with read-only repo access; update the
workflow file to include this permissions declaration to enforce least privilege
for the reusable workflow.
| - name: Run npm audit | ||
| run: npm audit --audit-level=high --omit=dev |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate the security workflow file
find . -name "_security.yml" -o -name "*security*.yml" | head -20Repository: hngprojects/flowbrand-be
Length of output: 102
🏁 Script executed:
# Also check the general workflow directory structure
git ls-files .github/workflows/ | head -20Repository: hngprojects/flowbrand-be
Length of output: 246
🏁 Script executed:
# Let's search for the specific file mentioned
cat -n .github/workflows/_security.yml 2>/dev/null || echo "File not found, searching for it..."Repository: hngprojects/flowbrand-be
Length of output: 1181
🏁 Script executed:
# Let's check what the purpose of this repo is and understand the context better
head -50 package.json 2>/dev/null | cat -nRepository: hngprojects/flowbrand-be
Length of output: 2521
🏁 Script executed:
# Let's also check if there are any comments or documentation explaining the --omit=dev choice
git log --oneline -n 20 -- .github/workflows/_security.yml 2>/dev/null || echo "No git history available"Repository: hngprojects/flowbrand-be
Length of output: 106
🏁 Script executed:
# Check if dev dependencies are used in CI or if this is primarily a library
git ls-files | grep -E "(Dockerfile|docker-compose|tsconfig|jest|lint)" | head -10Repository: hngprojects/flowbrand-be
Length of output: 222
🌐 Web query:
npm audit --omit=dev behavior what does it do
💡 Result:
The npm audit --omit=dev command runs a security audit on your project's dependencies but excludes devDependencies from the audit report. By default, npm audit checks direct dependencies, devDependencies, bundledDependencies, and optionalDependencies (but not peerDependencies) [1][2]. The --omit=dev flag instructs npm to omit "dev" dependency types from the submitted payload to the audit endpoint, so omitted dependency types (like devDependencies) are skipped when generating the vulnerability report [3][4][5]. This means vulnerabilities in devDependencies are not reported, focusing the audit on production dependencies instead [6]. This flag is part of npm's general --omit option, which can also target "optional" or "peer" types, and is preferred over the deprecated --production flag in recent npm versions [6].
Citations:
- 1: https://docs.npmjs.org/auditing-package-dependencies-for-security-vulnerabilities
- 2: https://docs.npmjs.com/auditing-package-dependencies-for-security-vulnerabilities/
- 3: https://docs.npmjs.com/cli/v7/commands/npm-audit
- 4: https://docs.npmjs.com/cli/v9/commands/npm-audit/
- 5: https://www.unpkg.com/npm@10.5.0/docs/output/commands/npm-audit.html
- 6: https://stackoverflow.com/questions/50352474/npm-audit-only-for-production-dependencies
🌐 Web query:
npm audit dev dependencies security concerns CI/CD
💡 Result:
npm audit checks both production dependencies and devDependencies by default for security vulnerabilities.[1][2] In CI/CD pipelines, a key concern with devDependencies is noisy output from "phantom vulnerabilities" that don't affect production runtime, potentially blocking builds unnecessarily.[3] To address this, use npm audit --omit=dev (preferred over deprecated --production) to audit only production dependencies, reducing false alarms while gating on critical/high risks.[3][4][5] However, devDependencies pose real security risks in CI/CD: they execute during builds on servers with access to secrets, code, and deployment credentials, enabling potential exfiltration or tampering if compromised.[6] Supply chain attacks via dev tools can leak production data.[7] Thus, best practices balance both: - Run npm ci (not npm install) in CI for deterministic installs.[3][8] - Audit production deps strictly: npm audit --omit=dev --audit-level=high and fail on high/critical.[3][5][9] - Optionally audit full tree separately or use tools like audit-ci with --skip-dev flag, allowlists, and thresholds.[9][10] - GitHub Actions examples: Use npm-audit-action with production_flag: true or audit-ci to fail builds.[11][12] - Regularly update all deps, review lockfile changes in PRs, and monitor for new advisories.[3][8] npm audit has limits (e.g., misses unknown supply chain attacks[13]), so combine with lockfile scanning and monitoring.[8][14]
Citations:
- 1: https://docs.npmjs.com/auditing-package-dependencies-for-security-vulnerabilities/
- 2: https://docs.npmjs.com/cli/v8/commands/npm-audit/
- 3: https://www.grizzlypeaksoftware.com/library/dependency-security-auditing-and-updating-yaw9380d
- 4: https://stackoverflow.com/questions/50352474/npm-audit-only-for-production-dependencies
- 5: https://dev.to/devactivity/when-npm-audit-fails-lessons-in-cicd-resilience-and-dependency-management-4anp
- 6: https://medium.com/@bhagyarana80/the-surprising-security-risks-hidden-in-your-dev-dependencies-5b8687aa8a2e
- 7: https://dev.to/johalputt/postmortem-supply-chain-attack-via-compromised-npm-package-11-caused-a-production-data-leak-314j
- 8: https://dev.to/vulert_official/npm-package-security-how-to-find-and-fix-vulnerable-dependencies-in-your-nodejs-application-5bj7
- 9: https://github.com/zonkedcode/audit-ci
- 10: https://github.com/IBM/audit-ci
- 11: https://github.com/oke-py/npm-audit-action
- 12: https://github.com/marketplace/actions/npm-audit-action
- 13: https://dev.to/jtorchia/npm-audit-isnt-enough-i-simulated-a-supply-chain-attack-on-my-node-dependencies-and-found-what-2ofc
- 14: https://dev.to/ariauser13/the-supply-chain-security-audit-nobody-told-you-to-run-until-it-was-too-late-4hl0
Audit excludes dev dependencies despite installing them, creating a security blind spot.
Line 22 installs dev dependencies with --include=dev, but line 25 runs npm audit --omit=dev, which skips vulnerability checks on those dependencies. Dev tools execute during CI builds on servers with access to secrets and credentials, making them part of the supply chain attack surface. Run the full audit to cover the complete dependency tree.
Suggested change
- name: Run npm audit
- run: npm audit --audit-level=high --omit=dev
+ run: npm audit --audit-level=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.
| - name: Run npm audit | |
| run: npm audit --audit-level=high --omit=dev | |
| - name: Run npm audit | |
| run: npm audit --audit-level=high |
🤖 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/_security.yml around lines 24 - 25, The CI step named "Run
npm audit" currently invokes "npm audit --audit-level=high --omit=dev" which
skips auditing dev dependencies even though the workflow installs dev deps;
update that step to run a full audit by removing the "--omit=dev" flag (i.e.,
run "npm audit --audit-level=high") so the audit covers the entire dependency
tree including dev dependencies and CI-only tools used in the build.
| - name: Checkout code | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What are the current default GITHUB_TOKENpermissions in GitHub Actions, and is setting explicitpermissionspluspersist-credentials: false recommended for CI jobs that only need repository read access?
💡 Result:
Current default GITHUB_TOKEN permissions (2026-05-10) 1) By default for a new personal-account repository: GITHUB_TOKEN has read access for the contents and packages scopes only. [1] 2) For orgs/repositories where the “restricted” default is selected: the default is “read access for the contents and packages permissions”; GitHub also notes this can be inherited from organization settings. [2] 3) If the “permissive” option is selected (“read and write access for all permissions”), then the default includes write access across scopes. [2] So, the “current default” depends on your repository/org setting; the documentation explicitly calls out contents + packages as the restricted default for new personal repos, and the alternative as read/write for all scopes. [2] Is setting explicit permissions plus persist-credentials: false recommended for CI jobs that only need repository read access? A) Explicit permissions: Yes, as a security best practice. GitHub’s docs recommend granting the GITHUB_TOKEN the least access required and using the workflow/job-level permissions key to do so. [3][4][5] Example for a job that only needs to read the repository: permissions: contents: read packages: read # (optionally set other scopes to none if you know you don't need them) This matches GitHub’s guidance to limit GITHUB_TOKEN access. [3][4][5] Also note: the GITHUB_TOKEN is automatically created per job. [6] B) persist-credentials: false: Often recommended when using actions/checkout, but it is about credential persistence in the runner’s git config, not about GITHUB_TOKEN API permission scopes. GitHub’s docs for GITHUB_TOKEN permissions focus on the permissions key (scopes granted to the token) rather than on checkout credential persistence. [3][4][6] Separate from that, the actions/checkout ecosystem has security-oriented guidance to use persist-credentials: false to avoid persisting credentials in git config after checkout. For example, a GitHub-related PR explicitly states it “prevents GITHUB_TOKEN from persisting in git config after checkout by always emitting persist-credentials: false.” [7] Conclusion: for CI jobs that only need repository read access, it’s recommended to (1) set explicit permissions with least-privilege GITHUB_TOKEN scope (e.g., contents: read and typically packages: read), per GitHub guidance. [3][4][5] Additionally, when you use actions/checkout, setting persist-credentials: false is a sensible hardening measure to reduce how long credentials are persisted in git configuration on the runner, as reflected in security-focused checkout guidance. [7]
Citations:
- 1: https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/
- 2: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository
- 3: https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs/
- 4: https://docs.github.com/en/enterprise-cloud@latest/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token
- 5: https://docs.github.com/actions/reference/authentication-in-a-workflow
- 6: https://docs.github.com/en/actions/concepts/security/github_token
- 7: Always emit
persist-credentials: falsewith actions/checkout github/gh-aw#2641
🏁 Script executed:
cat .github/workflows/_test.ymlRepository: hngprojects/flowbrand-be
Length of output: 537
Harden GitHub token exposure in the test job.
The workflow lacks explicit permissions declaration and the checkout step doesn't disable credential persistence. This increases risk if a dependency script is compromised during npm install.
Add workflow-level permissions: contents: read and configure the checkout step with persist-credentials: false to minimize token exposure and reduce git credential persistence on the runner.
🔐 Suggested hardening patch
name: _test
on:
workflow_dispatch:
workflow_call:
+permissions:
+ contents: read
+
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
+ with:
+ persist-credentials: false🤖 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/_test.yml around lines 12 - 13, Add least-privilege
workflow permissions and prevent checkout from persisting GitHub credentials: at
the top-level of the workflow add a permissions block with contents: read (i.e.,
permissions: contents: read) and update the actions/checkout@v4 step (the
"Checkout code" step) to include persist-credentials: false so the runner won't
retain the GITHUB_TOKEN for subsequent commands (reducing token exposure during
npm install or other scripts).
| if: | | ||
| github.event_name != 'pull_request' && ( | ||
| (github.event_name == 'push' && github.ref == 'refs/heads/dev') || | ||
| (github.event_name == 'workflow_dispatch' && inputs.environment == 'dev') | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/pipeline.yml | head -160Repository: hngprojects/flowbrand-be
Length of output: 6456
🏁 Script executed:
# Check if there are any branch protection or environment rules configured
find . -name '*.yml' -o -name '*.yaml' | head -20Repository: hngprojects/flowbrand-be
Length of output: 474
🌐 Web query:
GitHub Actions workflow_dispatch github.ref context available branch
💡 Result:
Yes, the github.ref context is available in GitHub Actions workflows triggered by workflow_dispatch. It contains the fully-formed ref (e.g., refs/heads/main) of the branch or tag specified when dispatching the workflow.[1][2][3] When manually triggering a workflow via the GitHub UI, CLI (gh workflow run --ref BRANCH), or REST API, you select or provide a ref parameter, which becomes the value of github.ref (and the $GITHUB_REF environment variable) in the workflow run.[4][5][6] The workflow file must exist on the specified branch for the dispatch to succeed, but typically workflows are defined on the default branch.[4][2] github.ref_name provides the short branch or tag name (e.g., main).[1] Example usage in a workflow: if: github.ref == 'refs/heads/main' or run: echo "Branch: ${{ github.ref_name }}" This behavior is consistent across official documentation as of 2026.[1][4][2][3][7]
Citations:
- 1: https://docs.github.com/en/actions/reference/contexts-reference
- 2: https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- 3: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows
- 4: https://docs.github.com/actions/using-workflows/manually-running-a-workflow
- 5: https://stackoverflow.com/questions/70151645/how-can-i-trigger-a-workflow-dispatch-from-the-github-api
- 6: https://oneuptime.com/blog/post/2026-01-25-github-actions-workflow-dispatch/view
- 7: https://docs.github.com/en/actions/reference/variables-reference
Manual deploy conditions lack branch enforcement (unreviewed code can reach production via workflow_dispatch).
The deploy jobs only validate inputs.environment for manual triggers, not the branch. This allows deploying artifacts built from any branch to any environment when triggering workflow_dispatch, contingent only on GitHub Environment approval gates rather than enforced branch restrictions.
Suggested change
deploy-staging:
if: |
github.event_name != 'pull_request' && (
(github.event_name == 'push' && github.ref == 'refs/heads/staging') ||
- (github.event_name == 'workflow_dispatch' && inputs.environment == 'staging')
+ (github.event_name == 'workflow_dispatch' && inputs.environment == 'staging' && github.ref == 'refs/heads/staging')
)
...
deploy-prod:
if: |
github.event_name != 'pull_request' && (
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
- (github.event_name == 'workflow_dispatch' && inputs.environment == 'prod')
+ (github.event_name == 'workflow_dispatch' && inputs.environment == 'prod' && github.ref == 'refs/heads/main')
)Also applies to: 118 (deploy-dev should enforce github.ref == 'refs/heads/dev')
🤖 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/pipeline.yml around lines 115 - 119, The workflow's manual
trigger condition allows workflow_dispatch from any branch because it only
checks inputs.environment; update the if condition for the deploy jobs (e.g.,
the block referencing github.event_name, github.ref, inputs.environment and the
deploy-dev job) to require both inputs.environment == 'dev' AND github.ref ==
'refs/heads/dev' for workflow_dispatch paths (similarly enforce the correct
branch for other environments), so manual deployments only run when the trigger
comes from the matching branch as well as the matching environment input.
Description
Set up a modular GitHub Actions CI/CD pipeline. Instead of one giant file, I’ve broken the logic into reusable building blocks (nested workflows) for better maintenance and readability.
Related Issue
Fixes # (issue)
Type of Change
Summary of Work
Modular Design: Created sub-workflows for
lint,test,security,build, anddeploy.Security: Added Gitleaks for secret scanning and
npm auditfor dependency checks.Smart Triggers:
PRs: Only run Lint & Build (safely validates code).
Pushes: Automatic deployment to
dev,staging, orprodbased on the branch name.Manual: Added
workflow_dispatchto trigger deploys manually from the Actions tab.Deployment: Uses
sshpassandscpto bundle the app, ship it to the server, and restart via PM2.How Has This Been Tested?
workflow_dispatch)devbranch)Checklist
Additional Notes
Tests are currently commented out in the main pipeline until the team has written actual test files—uncomment
test-prortestwhen ready!Summary by CodeRabbit