-
Notifications
You must be signed in to change notification settings - Fork 3
Integrate Firebase Hosting for automated deployment (#45) #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "projects": { | ||
| "default": "servio-app" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,51 @@ | ||||||||||||||||||||||||||||||||||||||
| name: Deploy to Firebase Hosting | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||||||
| push: | ||||||||||||||||||||||||||||||||||||||
| branches: [main] | ||||||||||||||||||||||||||||||||||||||
| workflow_dispatch: | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||||||||||||||||
| contents: read | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| concurrency: | ||||||||||||||||||||||||||||||||||||||
| group: firebase-deploy-${{ github.ref }} | ||||||||||||||||||||||||||||||||||||||
| cancel-in-progress: true | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||||||||
| build-and-deploy: | ||||||||||||||||||||||||||||||||||||||
| name: Build & Deploy | ||||||||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||
| - name: Checkout | ||||||||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| - name: Set up Node.js | ||||||||||||||||||||||||||||||||||||||
| uses: actions/setup-node@v4 | ||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||
| node-version: 20 | ||||||||||||||||||||||||||||||||||||||
| cache: npm | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| - name: Install dependencies | ||||||||||||||||||||||||||||||||||||||
| run: npm ci | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| - name: Build | ||||||||||||||||||||||||||||||||||||||
| run: npm run build | ||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||
| VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }} | ||||||||||||||||||||||||||||||||||||||
| VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }} | ||||||||||||||||||||||||||||||||||||||
| VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID }} | ||||||||||||||||||||||||||||||||||||||
| VITE_FIREBASE_STORAGE_BUCKET: ${{ secrets.VITE_FIREBASE_STORAGE_BUCKET }} | ||||||||||||||||||||||||||||||||||||||
| VITE_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID }} | ||||||||||||||||||||||||||||||||||||||
| VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }} | ||||||||||||||||||||||||||||||||||||||
| VITE_FIREBASE_MEASUREMENT_ID: ${{ secrets.VITE_FIREBASE_MEASUREMENT_ID }} | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| - name: Deploy to Firebase Hosting | ||||||||||||||||||||||||||||||||||||||
| if: ${{ secrets.FIREBASE_SERVICE_ACCOUNT != '' }} | ||||||||||||||||||||||||||||||||||||||
| uses: FirebaseExtended/action-hosting-deploy@v0 | ||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||
| repoToken: ${{ secrets.GITHUB_TOKEN }} | ||||||||||||||||||||||||||||||||||||||
| firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }} | ||||||||||||||||||||||||||||||||||||||
| channelId: live | ||||||||||||||||||||||||||||||||||||||
| projectId: ${{ secrets.VITE_FIREBASE_PROJECT_ID }} | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: You cannot directly reference secrets in GitHub Actions if conditional expressions [1][2][3]. Attempting to use syntax like ${{ secrets.MY_SECRET }} within an if block will not function as expected, as the secrets context is not available for evaluation at that stage of the workflow execution [1][3]. To conditionally run a job or step based on the presence or value of a secret, use the following workaround: 1. Map the secret to an environment variable at the job or step level [1][4]. 2. Use that environment variable to evaluate the condition [4][3]. Example of using a secret to conditionally run a step: jobs: example-job: runs-on: ubuntu-latest steps: - name: Set secret as environment variable env: MY_SECRET_VAR: ${{ secrets.MY_SECRET }} run: | echo "SECRET_EXISTS=${{ env.MY_SECRET_VAR!= '' }}" >> Citations:
🏁 Script executed: # Check if the repository has the firebase-deploy.yml file and examine the context
git ls-files | grep -i firebaseRepository: hrx01-dev/Servio Length of output: 323 🏁 Script executed: cat -n .github/workflows/firebase-deploy.yml | sed -n '40,55p'Repository: hrx01-dev/Servio Length of output: 770 Use Line 45 uses Suggested fix - name: Deploy to Firebase Hosting
- if: ${{ secrets.FIREBASE_SERVICE_ACCOUNT != '' }}
+ env:
+ FIREBASE_SERVICE_ACCOUNT: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
+ if: ${{ env.FIREBASE_SERVICE_ACCOUNT != '' }}
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
- firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
+ firebaseServiceAccount: ${{ env.FIREBASE_SERVICE_ACCOUNT }}
channelId: live
projectId: ${{ secrets.VITE_FIREBASE_PROJECT_ID }}📝 Committable suggestion
Suggested change
🧰 Tools🪛 actionlint (1.7.12)[error] 45-45: context "secrets" is not allowed here. available contexts are "env", "github", "inputs", "job", "matrix", "needs", "runner", "steps", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details (expression) 🪛 zizmor (1.25.2)[error] 46-46: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| name: Firebase Hosting Preview | ||
|
|
||
| on: | ||
| pull_request: | ||
|
|
||
| permissions: | ||
| contents: read | ||
| checks: write | ||
| pull-requests: write | ||
|
|
||
| concurrency: | ||
| group: firebase-preview-${{ github.event.pull_request.number }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| preview: | ||
| if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} | ||
| name: Build & Preview | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
Comment on lines
+22
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n .github/workflows/firebase-preview.yml | head -50Repository: hrx01-dev/Servio Length of output: 1961 Pin action SHAs and disable credential persistence for checkout. Line 23 uses default credential persistence, and lines 23, 26, and 47 use mutable action refs. Pin to full commit SHAs and set Suggested hardening pattern - name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@<full-commit-sha>
+ with:
+ persist-credentials: false
- name: Set up Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@<full-commit-sha>
- name: Deploy Preview to Firebase Hosting
- uses: FirebaseExtended/action-hosting-deploy@v0
+ uses: FirebaseExtended/action-hosting-deploy@<full-commit-sha>🧰 Tools🪛 zizmor (1.25.2)[warning] 22-23: 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 AgentsSource: Linters/SAST tools |
||
|
|
||
| - name: Set up Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 20 | ||
| cache: npm | ||
|
|
||
| - name: Install dependencies | ||
| run: npm ci | ||
|
|
||
| - name: Build | ||
| run: npm run build | ||
| env: | ||
| VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }} | ||
| VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }} | ||
| VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID }} | ||
| VITE_FIREBASE_STORAGE_BUCKET: ${{ secrets.VITE_FIREBASE_STORAGE_BUCKET }} | ||
| VITE_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID }} | ||
| VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }} | ||
| VITE_FIREBASE_MEASUREMENT_ID: ${{ secrets.VITE_FIREBASE_MEASUREMENT_ID }} | ||
|
|
||
| - name: Deploy Preview to Firebase Hosting | ||
| if: ${{ secrets.FIREBASE_SERVICE_ACCOUNT != '' }} | ||
| uses: FirebaseExtended/action-hosting-deploy@v0 | ||
| with: | ||
| repoToken: ${{ secrets.GITHUB_TOKEN }} | ||
| firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }} | ||
| projectId: ${{ secrets.VITE_FIREBASE_PROJECT_ID }} | ||
|
Comment on lines
+45
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In GitHub Actions, you cannot directly reference secrets in if conditional expressions (e.g., if: ${{ secrets.MY_SECRET == 'value' }} will not work) [1][2][3]. This is a documented limitation [1][3]. To conditionally run a job or step based on the value or presence of a secret, use one of the following workarounds: 1. Use environment variables: Define an environment variable at the job or step level using the secret, then reference that environment variable in your if conditional [1][2][4]. Since an unset secret resolves to an empty string, you can check if it is defined by checking if it is not empty [1][3][4]. jobs: example-job: runs-on: ubuntu-latest steps: - name: Run if secret is set env: MY_SECRET_VAL: ${{ secrets.MY_SECRET }} if: ${{ env.MY_SECRET_VAL!= '' }} run: echo "Secret is present" 2. Use github.secret_source: If you need to determine generally if secrets are available (for example, to prevent failures when running workflows from forks), you can use the github.secret_source context in your if condition [5][6]. This property will be 'Actions' if secrets are accessible and 'None' if they are not [6]. jobs: job1: runs-on: ubuntu-latest if: ${{ github.secret_source == 'Actions' }} steps: - run: echo "Secrets are available" 3. Use a separate job for validation: If you need to perform complex logic based on multiple secrets, you can create a dedicated "check" job that evaluates the secrets and sets an output [6]. Subsequent jobs can then use the needs context to conditionally run based on that output [6]. Citations:
🌐 Web query:
💡 Result: In GitHub Actions, the secrets context is used to access repository, organization, or environment secrets within a workflow run [1][2]. Key rules regarding its availability and usage in expressions include: 1. Restriction in Conditionals: You cannot directly reference secrets in if: conditionals [3][4]. If a secret is used directly in an if: expression, the condition will not function as intended because the secret's value is not exposed in that context [3]. The standard workaround is to map the secret to a job-level or step-level environment variable and then reference that environment variable within the if: conditional [3][4]. 2. Availability: The secrets context is generally available within a job, but it is not available for composite actions for security reasons [1][5]. To provide a secret to a composite action, you must pass it explicitly as an input [1][2]. 3. Empty Values: If a referenced secret has not been set, the expression will return an empty string [3]. 4. Best Practices for Expressions: While you can use ${{ secrets.SECRET_NAME }} within expressions (such as when setting environment variables or action inputs), avoid interpolating them directly into shell commands in run: blocks [3][4]. Direct interpolation can lead to secrets appearing in process lists, shell history, or logs [4]. Instead, map the secret to an environment variable and access the environment variable within your script [4]. 5. Automatic Secrets: GITHUB_TOKEN is a built-in secret automatically included in the secrets context for every workflow run [1][6]. For detailed usage, you can map secrets to environment variables as follows: env: MY_SECRET_VAR: ${{ secrets.MY_SECRET }} run:./my-script.sh # access via $MY_SECRET_VAR in the script [4] Citations:
Replace The Suggested fix - name: Deploy Preview to Firebase Hosting
- if: ${{ secrets.FIREBASE_SERVICE_ACCOUNT != '' }}
+ env:
+ FIREBASE_SERVICE_ACCOUNT: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
+ if: ${{ env.FIREBASE_SERVICE_ACCOUNT != '' }}
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
- firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
+ firebaseServiceAccount: ${{ env.FIREBASE_SERVICE_ACCOUNT }}
projectId: ${{ secrets.VITE_FIREBASE_PROJECT_ID }}🧰 Tools🪛 actionlint (1.7.12)[error] 46-46: context "secrets" is not allowed here. available contexts are "env", "github", "inputs", "job", "matrix", "needs", "runner", "steps", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details (expression) 🪛 zizmor (1.25.2)[error] 47-47: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -30,6 +30,73 @@ A modern SaaS website built with React, Vite, and Tailwind CSS. This project is | |||||||||
| npm run dev | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ## 🚀 Deployment | ||||||||||
|
|
||||||||||
| This project uses **Firebase Hosting** for production deployments. | ||||||||||
|
|
||||||||||
| ### Prerequisites | ||||||||||
|
|
||||||||||
| - [Firebase CLI](https://firebase.google.com/docs/cli) installed globally: | ||||||||||
| ```bash | ||||||||||
| npm install -g firebase-tools | ||||||||||
| ``` | ||||||||||
| - A Firebase project with Hosting enabled | ||||||||||
| - Firebase authentication via `firebase login` | ||||||||||
|
|
||||||||||
| ### Environment Variables | ||||||||||
|
|
||||||||||
| Create a `.env` file at the project root with your Firebase configuration: | ||||||||||
|
|
||||||||||
| ```env | ||||||||||
| VITE_FIREBASE_API_KEY=your_api_key | ||||||||||
| VITE_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com | ||||||||||
| VITE_FIREBASE_PROJECT_ID=your_project_id | ||||||||||
| VITE_FIREBASE_STORAGE_BUCKET=your_project.firebasestorage.app | ||||||||||
| VITE_FIREBASE_MESSAGING_SENDER_ID=your_sender_id | ||||||||||
| VITE_FIREBASE_APP_ID=your_app_id | ||||||||||
| VITE_FIREBASE_MEASUREMENT_ID=your_measurement_id | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ### Manual Deployment | ||||||||||
|
|
||||||||||
| 1. Build and deploy to production: | ||||||||||
| ```bash | ||||||||||
| npm run deploy | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| 2. Deploy a preview channel: | ||||||||||
| ```bash | ||||||||||
| npm run deploy:preview | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ### Automated Deployment (CI/CD) | ||||||||||
|
|
||||||||||
| Deployments are automated via GitHub Actions: | ||||||||||
|
|
||||||||||
| - **Production:** Merges to `main` trigger a build and deploy to the live Firebase Hosting channel. | ||||||||||
| - **Preview:** Pull requests automatically deploy to a temporary preview URL (posted as a PR comment). | ||||||||||
|
Comment on lines
+76
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clarify that preview deploys are limited to same-repository PRs. Line 77 currently reads as all pull requests get preview URLs, but the workflow condition only allows PRs whose head branch is in this repository. A short note here will avoid contributor confusion. Suggested wording update-- **Preview:** Pull requests automatically deploy to a temporary preview URL (posted as a PR comment).
+- **Preview:** Pull requests from branches in this repository automatically deploy to a temporary preview URL (posted as a PR comment).📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| To enable automated deployments, add the following secrets to your GitHub repository (`Settings > Secrets and variables > Actions`): | ||||||||||
|
|
||||||||||
| | Secret | Description | | ||||||||||
| |--------|-------------| | ||||||||||
| | `FIREBASE_SERVICE_ACCOUNT` | Firebase service account JSON key (see [setup guide](https://github.com/FirebaseExtended/action-hosting-deploy/blob/main/docs/service-account.md)) | | ||||||||||
| | `VITE_FIREBASE_API_KEY` | Firebase API key | | ||||||||||
| | `VITE_FIREBASE_AUTH_DOMAIN` | Firebase auth domain | | ||||||||||
| | `VITE_FIREBASE_PROJECT_ID` | Firebase project ID | | ||||||||||
| | `VITE_FIREBASE_STORAGE_BUCKET` | Firebase storage bucket | | ||||||||||
| | `VITE_FIREBASE_MESSAGING_SENDER_ID` | Firebase messaging sender ID | | ||||||||||
| | `VITE_FIREBASE_APP_ID` | Firebase app ID | | ||||||||||
| | `VITE_FIREBASE_MEASUREMENT_ID` | Firebase measurement ID | | ||||||||||
|
|
||||||||||
| ### Firebase Project Setup | ||||||||||
|
|
||||||||||
| 1. Update the project ID in `.firebaserc` to match your Firebase project. | ||||||||||
| 2. The `firebase.json` configures: | ||||||||||
| - `dist/` as the public directory (Vite build output) | ||||||||||
| - SPA rewrites (all routes serve `index.html`) | ||||||||||
| - Cache headers for static assets | ||||||||||
|
|
||||||||||
| ## ✨ Key Features | ||||||||||
|
|
||||||||||
| - **Web Development Services:** Professional development of high-performance landing pages, corporate websites, and custom SaaS applications tailored for business growth. | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| { | ||
| "hosting": { | ||
| "public": "dist", | ||
| "ignore": [ | ||
| "firebase.json", | ||
| "**/.*", | ||
| "**/node_modules/**" | ||
| ], | ||
| "rewrites": [ | ||
| { | ||
| "source": "**", | ||
| "destination": "/index.html" | ||
| } | ||
| ], | ||
| "headers": [ | ||
| { | ||
| "source": "/assets/**", | ||
| "headers": [ | ||
| { | ||
| "key": "Cache-Control", | ||
| "value": "public, max-age=31536000, immutable" | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "source": "**/*.@(js|css)", | ||
| "headers": [ | ||
| { | ||
| "key": "Cache-Control", | ||
| "value": "public, max-age=31536000, immutable" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: hrx01-dev/Servio
Length of output: 100
🏁 Script executed:
Repository: hrx01-dev/Servio
Length of output: 1947
🌐 Web query:
actions/checkout@v4 persist-credentials default behavior security💡 Result:
In actions/checkout@v4, the persist-credentials input defaults to true [1][2]. When set to true (the default), the action configures the local Git repository's configuration files within the runner to include the authentication token or SSH key used for the checkout [3][2]. This enables subsequent steps in the same job to execute authenticated Git commands (such as git push or git fetch) without needing to manually re-authenticate [3][4]. The action is designed to remove these credentials during the post-job cleanup phase [3][2]. From a security perspective, there has been long-standing community discussion regarding this default behavior [5][6]. The primary security concern is that by persisting these credentials, any subsequent step in the workflow—including potentially untrusted third-party actions—gains the ability to access and utilize these credentials for the duration of the job [5][6]. To opt out of this behavior and prevent the credentials from being written to the local Git configuration, you should explicitly set the input to false in your workflow definition [3][2]: - uses: actions/checkout@v4 with: persist-credentials: false It is recommended to use the principle of least privilege by configuring appropriate permissions for your GitHub Actions tokens, regardless of this setting [3][6].
Citations:
persist-credentials=trueplease actions/checkout#2312persist-credentialsor change the default tofalseactions/checkout#485Pin actions to commit SHAs and disable credential persistence.
Line 22 persists checkout credentials by default (persist-credentials defaults to true), and lines 22, 25, and 46 use mutable action tags. Pin actions to immutable commit SHAs and set
persist-credentials: falseon the checkout action to reduce supply chain attack surface.Suggested hardening pattern
Also applies to: 24-26, 46-46
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 21-22: 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
Source: Linters/SAST tools