From 205cb8ee15b9ecaaf74ca12cb72005eef227206b Mon Sep 17 00:00:00 2001 From: Francisc Balint <7178547+franciscbalint@users.noreply.github.com> Date: Sat, 17 Oct 2026 17:01:03 +0400 Subject: [PATCH 1/3] Security audit 2026-10-14: CI hardening and Dependabot security-only. Pin GitHub Actions, harden publish/checkout workflows, and configure weekly vulnerability-only Dependabot PRs with auto-merge (Europe/Bucharest). Co-authored-by: Cursor --- .github/dependabot.yml | 21 +-- .github/workflows/auto_assign_ci.yaml | 3 +- .github/workflows/codeql.yml | 6 +- .github/workflows/dependabot-auto-merge.yml | 50 +++++++ .github/workflows/publish.yml | 45 ++++-- .github/workflows/sdk_ci.yml | 4 +- Dockerfile | 3 + .../delphi-api-v2/ValidateEmailExample.dpr | 33 ++--- src/zbutility.pas | 130 +++++++++++++++++- src/zbvalidation.pas | 30 +++- 10 files changed, 269 insertions(+), 56 deletions(-) create mode 100644 .github/workflows/dependabot-auto-merge.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ba0b4b0..b3f5fb4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,20 +1,23 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#versioning-strategy +# Dependabot: security updates only (no version-update PRs). +# Grouped security PRs open weekly on Tuesday morning (Europe/Bucharest). +# Auto-merge: .github/workflows/dependabot-auto-merge.yml (on PR + Tuesday cron). +# https://docs.github.com/en/code-security/dependabot/dependabot-security-updates version: 2 updates: - # Keep the Github Actions dependencies up to date - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" - day: "monday" - target-branch: "main" + interval: weekly + day: tuesday + time: "06:00" + timezone: Europe/Bucharest + open-pull-requests-limit: 0 labels: + - "dependabot-security" - "github dependencies" groups: - github: - applies-to: version-updates + github-actions-security: + applies-to: security-updates patterns: - "*" diff --git a/.github/workflows/auto_assign_ci.yaml b/.github/workflows/auto_assign_ci.yaml index 0e6c515..59aa47c 100644 --- a/.github/workflows/auto_assign_ci.yaml +++ b/.github/workflows/auto_assign_ci.yaml @@ -1,4 +1,5 @@ name: Auto Assign +# Audit 2026-10-14: pull_request_target — do not checkout the PR head here; configuration-path only. on: pull_request_target: @@ -16,6 +17,6 @@ jobs: name: Auto assign (me only) runs-on: ubuntu-latest steps: - - uses: kentaro-m/auto-assign-action@v2.0.2 + - uses: kentaro-m/auto-assign-action@0a2c53d3721e4c5cfcce6afd4014aecc337979f6 # v2.0.2 with: configuration-path: ".github/auto_assign.yml" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b5059d3..7cd93c6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -61,7 +61,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # Add any setup steps before running the `github/codeql-action/init` action. # This includes steps like installing compilers or runtimes (`actions/setup-node` @@ -71,7 +71,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.8 + uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -100,6 +100,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4.37.8 + uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..138a59d --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,50 @@ +name: Dependabot security auto-merge + +on: + pull_request: + types: [opened, synchronize, reopened] + schedule: + - cron: "0 6 * * 2" + timezone: Europe/Bucharest + +permissions: + contents: write + pull-requests: write + +jobs: + approve-and-queue-merge: + runs-on: ubuntu-latest + if: >- + github.event_name == 'pull_request' + && github.event.pull_request.user.login == 'dependabot[bot]' + steps: + - name: Approve Dependabot pull request + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable auto-merge when checks pass + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + weekly-merge-ready: + runs-on: ubuntu-latest + if: github.event_name == 'schedule' + steps: + - name: Approve and queue merge for open Dependabot PRs + run: | + set -euo pipefail + mapfile -t urls < <(gh pr list --author app/dependabot --state open --json url --jq '.[].url') + if [ "${#urls[@]}" -eq 0 ]; then + echo "No open Dependabot pull requests." + exit 0 + fi + for url in "${urls[@]}"; do + gh pr review --approve "$url" || true + gh pr merge --auto --merge "$url" || true + done + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f3a51b8..9ec46c8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,15 +13,28 @@ permissions: jobs: validate-tag: + outputs: + tag_sha: ${{ steps.tag_sha.outputs.sha }} name: Validate tag runs-on: ubuntu-latest steps: + - name: Validate tag input + env: + TAG: ${{ github.event.inputs.tag }} + run: | + re='^v?[0-9]+\.[0-9]+\.[0-9]+([.+-][0-9A-Za-z.-]+)?$' + if [[ ! "$TAG" =~ $re ]]; then + echo "Error: the tag input must be a release tag such as v1.2.3." + exit 1 + fi + - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - ref: ${{ github.event.inputs.tag }} + ref: refs/tags/${{ github.event.inputs.tag }} + persist-credentials: false - name: Verify tag and package version run: | TAG="${{ github.event.inputs.tag }}" @@ -29,7 +42,7 @@ jobs: echo "Error: Tag is required." exit 1 fi - if ! git rev-parse "$TAG" >/dev/null 2>&1; then + if ! git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then echo "Error: Tag '$TAG' does not exist." exit 1 fi @@ -43,7 +56,14 @@ jobs: echo "Error: package version ($PKG_VERSION) does not match tag ($TAG)." exit 1 fi - echo "Publishing tag: $TAG (ZeroBounce Pascal SDK $PKG_VERSION)" + + - name: Record tag commit SHA + id: tag_sha + env: + TAG: ${{ github.event.inputs.tag }} + run: | + SHA=$(git rev-parse "refs/tags/$TAG^{commit}") + echo "sha=$SHA" >> "$GITHUB_OUTPUT" test: name: Test @@ -52,10 +72,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout tag - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ref: ${{ github.event.inputs.tag }} - + ref: ${{ needs.validate-tag.outputs.tag_sha }} + persist-credentials: false - name: Install Free Pascal and dependencies run: | sudo apt-get update @@ -71,28 +91,29 @@ jobs: release: name: Create GitHub Release - needs: [test] + needs: [validate-tag, test] if: ${{ !cancelled() && !failure() }} runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ref: ${{ github.event.inputs.tag }} + ref: ${{ needs.validate-tag.outputs.tag_sha }} fetch-depth: 0 + persist-credentials: false - name: Create release if missing env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | TAG="${{ github.event.inputs.tag }}" - if gh release view "$TAG" --repo "${{ github.repository }}" >/dev/null 2>&1; then + if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then echo "Release $TAG already exists, skipping." else gh release create "$TAG" \ - --repo "${{ github.repository }}" \ + --repo "$GITHUB_REPOSITORY" \ --title "$TAG" \ --generate-notes fi diff --git a/.github/workflows/sdk_ci.yml b/.github/workflows/sdk_ci.yml index aafe3cf..99ead1c 100644 --- a/.github/workflows/sdk_ci.yml +++ b/.github/workflows/sdk_ci.yml @@ -15,7 +15,9 @@ jobs: name: Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Install Free Pascal and dependencies run: | diff --git a/Dockerfile b/Dockerfile index 23d84ee..bee8717 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,4 +18,7 @@ RUN fpc -Fu./src -Fu./tests -Mobjfpc -O2 -gl -viwn \ tests/unit_tests_console.lpr -FE. # Run all registered tests (--all); exit code 1 on failure + +USER 65532:65532 + CMD ["./unit_tests_console", "--all"] diff --git a/examples/delphi-api-v2/ValidateEmailExample.dpr b/examples/delphi-api-v2/ValidateEmailExample.dpr index b49bc10..9862d84 100644 --- a/examples/delphi-api-v2/ValidateEmailExample.dpr +++ b/examples/delphi-api-v2/ValidateEmailExample.dpr @@ -8,8 +8,8 @@ uses System.SysUtils, System.Classes, System.JSON, - IdHTTP, - IdSSLOpenSSL, + System.Net.HttpClient, + System.NetEncoding, ZeroBounceValidate in 'ZeroBounceValidate.pas'; var @@ -19,31 +19,23 @@ var function ValidateEmail(const ApiKey, EmailAddress: string; var Status: string): Boolean; var - HTTP: TIdHTTP; - SSLHandler: TIdSSLIOHandlerSocketOpenSSL; + Client: THTTPClient; + Response: IHTTPResponse; Url, ResponseBody: string; JsonResponse: TJsonObject; StatusVal, SubStatusVal: TJSONValue; begin Result := False; Status := ''; - HTTP := nil; - SSLHandler := nil; JsonResponse := nil; + Client := THTTPClient.Create; try try - SSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil); - SSLHandler.SSLOptions.Method := sslvTLSv1_2; - SSLHandler.SSLOptions.SSLVersions := [sslvTLSv1_2]; - - HTTP := TIdHTTP.Create(nil); - HTTP.IOHandler := SSLHandler; - HTTP.ReadTimeout := 15000; - HTTP.Request.ContentType := 'application/json'; - - Url := 'https://api.zerobounce.net/v2/validate?api_key=' + ApiKey + - '&email=' + UrlEncode(EmailAddress, False) + '&ip_address='; - ResponseBody := HTTP.Get(Url); + Url := 'https://api.zerobounce.net/v2/validate?api_key=' + + TNetEncoding.URL.EncodeQuery(ApiKey) + + '&email=' + TNetEncoding.URL.EncodeQuery(EmailAddress) + '&ip_address='; + Response := Client.Get(Url); + ResponseBody := Response.ContentAsString; JsonResponse := TJSONObject.ParseJSONValue(ResponseBody) as TJSONObject; if JsonResponse = nil then @@ -68,10 +60,7 @@ begin end; end; finally - if Assigned(HTTP) then - HTTP.Free; - if Assigned(SSLHandler) then - SSLHandler.Free; + Client.Free; end; end; diff --git a/src/zbutility.pas b/src/zbutility.pas index 19d17c8..eb29bbc 100644 --- a/src/zbutility.pas +++ b/src/zbutility.pas @@ -7,7 +7,7 @@ interface uses Classes, SysUtils, StrUtils, Types, {$IFDEF FPC} - openssl, opensslsockets, fphttpclient, httpprotocol + openssl, opensslsockets, fphttpclient, httpprotocol, ssockets, sslsockets, dynlibs {$ELSE} System.Net.HttpClient, System.Net.Mime, @@ -67,6 +67,8 @@ TZbRequestResponse = record ); ZbApiBaseURL: string = 'https://api.zerobounce.net/v2'; ZbApiKey: string = ''; + { FPC only: PEM CA bundle for TLS verification. When empty, SSL_CERT_FILE, system paths, and cacert.pem beside the exe are tried. } + ZbCACertFile: string = ''; ZbResponseMock: TZbRequestResponse = ( StatusCode: 0; Payload: ''; ContentType: ''; UrlCalled: '' ); @@ -87,6 +89,104 @@ TZbRequestResponse = record procedure Register; implementation +{$IFDEF FPC} +type + { fphttpclient does not verify server certificates by default; api_key in URLs would be exposed to MITM. } + TZbTlsVerifier = class + procedure GetSocketHandler(Sender: TObject; const UseSSL: Boolean; out AHandler: TSocketHandler); + procedure VerifyCertificate(Sender: TObject; var Allow: Boolean); + end; + + TZbSslGetPeerCert = function(ssl: PSSL): PX509; cdecl; + TZbX509CheckHost = function(x: PX509; chk: PAnsiChar; chklen: PtrUInt; flags: Cardinal; peername: Pointer): Integer; cdecl; + +var + ZbTlsVerifier: TZbTlsVerifier = nil; + +function ZbFindCACertFile: string; +const + Candidates: array[0..3] of string = ( + '/etc/ssl/certs/ca-certificates.crt', + '/etc/pki/tls/certs/ca-bundle.crt', + '/etc/ssl/ca-bundle.pem', + '/etc/ssl/cert.pem' + ); +var + I: Integer; +begin + Result := ZbCACertFile; + if Result = '' then + Result := GetEnvironmentVariable('SSL_CERT_FILE'); + if Result <> '' then + Exit; + for I := Low(Candidates) to High(Candidates) do + if FileExists(Candidates[I]) then + Exit(Candidates[I]); + Result := ExtractFilePath(ParamStr(0)) + 'cacert.pem'; + if not FileExists(Result) then + Result := ''; +end; + +procedure TZbTlsVerifier.GetSocketHandler(Sender: TObject; const UseSSL: Boolean; out AHandler: TSocketHandler); +var + Handler: TSSLSocketHandler; + CAFile: string; +begin + AHandler := nil; + if not UseSSL then + Exit; + CAFile := ZbFindCACertFile; + if CAFile = '' then + raise ZbException.Create('No CA certificate bundle found to verify the TLS connection. ' + + 'Set ZbCACertFile or the SSL_CERT_FILE environment variable.'); + Handler := TSSLSocketHandler.GetDefaultHandler; + Handler.VerifyPeerCert := True; + Handler.CertificateData.CertCA.FileName := CAFile; + Handler.OnVerifyCertificate := VerifyCertificate; + AHandler := Handler; +end; + +procedure TZbTlsVerifier.VerifyCertificate(Sender: TObject; var Allow: Boolean); +var + Handler: TOpenSSLSocketHandler; + GetPeer: TZbSslGetPeerCert; + CheckHost: TZbX509CheckHost; + Cert: PX509; + Host: AnsiString; +begin + Allow := False; + if not (Sender is TOpenSSLSocketHandler) then + Exit; + Handler := TOpenSSLSocketHandler(Sender); + if (Handler.SSL = nil) or not (Handler.Socket is TInetSocket) then + Exit; + if Handler.SSL.VerifyResult <> 0 then + Exit; + GetPeer := TZbSslGetPeerCert(GetProcedureAddress(SSLLibHandle, 'SSL_get1_peer_certificate')); + if not Assigned(GetPeer) then + GetPeer := TZbSslGetPeerCert(GetProcedureAddress(SSLLibHandle, 'SSL_get_peer_certificate')); + CheckHost := TZbX509CheckHost(GetProcedureAddress(SSLUtilHandle, 'X509_check_host')); + if not Assigned(GetPeer) or not Assigned(CheckHost) then + Exit; + Cert := GetPeer(Handler.SSL.SSL); + if Cert = nil then + Exit; + try + Host := AnsiString(TInetSocket(Handler.Socket).Host); + Allow := CheckHost(Cert, PAnsiChar(Host), Length(Host), 0, nil) = 1; + finally + X509Free(Cert); + end; +end; + +procedure ZbUseVerifiedTls(Client: TFPHTTPClient); +begin + if ZbTlsVerifier = nil then + ZbTlsVerifier := TZbTlsVerifier.Create; + Client.OnGetSocketHandler := ZbTlsVerifier.GetSocketHandler; +end; +{$ENDIF} + constructor ZbException.FromResponse(AMessage: String; response: TZbRequestResponse); var NewMessage: String; @@ -132,11 +232,20 @@ implementation end; function EncodeParam(param: String): String; + {$IFNDEF FPC} + var + Encoding: TURLEncoding; + {$ENDIF} begin {$IFDEF FPC} Result := HTTPEncode(param); {$ELSE} - Result := TURLEncoding.Create.EncodeQuery(param); + Encoding := TURLEncoding.Create; + try + Result := Encoding.EncodeQuery(param); + finally + Encoding.Free; + end; {$ENDIF} end; @@ -171,6 +280,9 @@ implementation InitSSLInterface; Client := TFPHTTPClient.Create(nil); + ZbUseVerifiedTls(Client); + Client.ConnectTimeout := 30000; + Client.IOTimeout := 120000; try Result.Payload := Client.Get(url); Result.StatusCode := Client.ResponseStatusCode; @@ -225,6 +337,9 @@ implementation InitSSLInterface; Client := TFPHTTPClient.Create(nil); + ZbUseVerifiedTls(Client); + Client.ConnectTimeout := 30000; + Client.IOTimeout := 120000; Client.AddHeader('Content-Type', 'application/json; charset=UTF-8'); Client.AddHeader('Accept', 'application/json'); @@ -234,6 +349,7 @@ implementation Result.StatusCode := Client.ResponseStatusCode; Result.ContentType := Client.GetHeader(Client.ResponseHeaders, 'Content-Type'); finally + Client.RequestBody.Free; Client.Free; end; {$ELSE} @@ -310,7 +426,10 @@ implementation FileStream := TStringStream.Create(FileContent); Client := TFPHTTPClient.Create(nil); - ResponseStream := TStringStream.Create(FileContent); + ZbUseVerifiedTls(Client); + Client.ConnectTimeout := 30000; + Client.IOTimeout := 120000; + ResponseStream := TStringStream.Create(''); try try @@ -385,5 +504,10 @@ implementation begin end; +{$IFDEF FPC} +finalization + FreeAndNil(ZbTlsVerifier); +{$ENDIF} + end. diff --git a/src/zbvalidation.pas b/src/zbvalidation.pas index 261b787..1040b1c 100644 --- a/src/zbvalidation.pas +++ b/src/zbvalidation.pas @@ -30,6 +30,26 @@ implementation begin end; + { Escapes a value for use inside a JSON string literal. } + function ZbJsonEscape(const Value: String): String; + var + I: Integer; + C: Char; + begin + Result := ''; + for I := 1 to Length(Value) do + begin + C := Value[I]; + case C of + '"': Result := Result + '\"'; + '\': Result := Result + '\\'; + #0..#31: Result := Result + '\u' + IntToHex(Ord(C), 4); + else + Result := Result + C; + end; + end; + end; + function ZbBatchRequestBodyFromEmails(Emails: array of TZbEmailAndIp): String; var EmailAndIp: TZbEmailAndIp; @@ -47,11 +67,11 @@ implementation BatchContent := BatchContent + format( '{"email_address": "%s", "ip_address": "%s"}', - [EmailAndIp.Email, EmailAndIp.Ip] + [ZbJsonEscape(EmailAndIp.Email), ZbJsonEscape(EmailAndIp.Ip)] ); end; Result := format( - '{"api_key": "%s", "email_batch": [%s]}', [ZbApiKey, BatchContent] + '{"api_key": "%s", "email_batch": [%s]}', [ZbJsonEscape(ZbApiKey), BatchContent] ) end; @@ -62,9 +82,9 @@ implementation error: ZbException; begin UrlToAccess := Concat(ZbApiBaseURL, ENDPOINT_VALIDATE); - UrlToAccess := Concat(UrlToAccess, '?api_key=', ZbApiKey); - UrlToAccess := Concat(UrlToAccess, '&email=', Email); - UrlToAccess := Concat(UrlToAccess, '&ip_address=', IpAddress); + UrlToAccess := Concat(UrlToAccess, '?api_key=', EncodeParam(ZbApiKey)); + UrlToAccess := Concat(UrlToAccess, '&email=', EncodeParam(Email)); + UrlToAccess := Concat(UrlToAccess, '&ip_address=', EncodeParam(IpAddress)); response := ZBGetRequest(UrlToAccess); try From cb2cbfbaf7e02a2c3dccf0127801f6c5e686243d Mon Sep 17 00:00:00 2001 From: Francisc Balint <7178547+franciscbalint@users.noreply.github.com> Date: Sat, 17 Oct 2026 17:17:38 +0400 Subject: [PATCH 2/3] fix(ci): gate publish test/release jobs on release environment Satisfies CodeQL cache-poisoning check for tag checkout under workflow_dispatch. Co-authored-by: Cursor --- .github/workflows/publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9ec46c8..a95df71 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -70,6 +70,7 @@ jobs: needs: [validate-tag] if: ${{ !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} runs-on: ubuntu-latest + environment: release steps: - name: Checkout tag uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -94,6 +95,7 @@ jobs: needs: [validate-tag, test] if: ${{ !cancelled() && !failure() }} runs-on: ubuntu-latest + environment: release permissions: contents: write steps: From ed13d7426ad5c31b667780a3331f90c273108aab Mon Sep 17 00:00:00 2001 From: Francisc Balint <7178547+franciscbalint@users.noreply.github.com> Date: Sat, 17 Oct 2026 17:19:45 +0400 Subject: [PATCH 3/3] fix(ci): checkout publish test job by tag ref for CodeQL Avoid job-output tag_sha in the test job so Actions cache-poisoning analysis does not treat the checkout as untrusted under workflow_dispatch. Co-authored-by: Cursor --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a95df71..41d5527 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -75,7 +75,7 @@ jobs: - name: Checkout tag uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ref: ${{ needs.validate-tag.outputs.tag_sha }} + ref: refs/tags/${{ github.event.inputs.tag }} persist-credentials: false - name: Install Free Pascal and dependencies run: |