From 043ef968efb44f37fdef59161cdd4f23e8081cbc Mon Sep 17 00:00:00 2001 From: yuli Date: Tue, 21 Jul 2026 18:33:12 +0300 Subject: [PATCH 1/2] Fix TLS verification, add Semgrep SAST, pin CI actions - Enable TLS certificate verification by default (CURLOPT_SSL_VERIFYPEER + VERIFYHOST) with an opt-out $verifySsl constructor flag for self-signed/dev instances - Add semgrep.yml caller for the shared reusable SAST workflow - Pin checkout/setup-php/cache to commit SHAs - Document $verifySsl and add Security section to README Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 6 +++--- .github/workflows/semgrep.yml | 22 +++++++++++++++++++ README.md | 40 +++++++++++++++++++++++++++++++++++ src/DatabunkerproApi.php | 13 +++++++++--- 4 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/semgrep.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d96c811..eba0fac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,10 +26,10 @@ jobs: name: PHP ${{ matrix.php }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: php-version: ${{ matrix.php }} coverage: xdebug @@ -40,7 +40,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache Composer dependencies - uses: actions/cache@v3 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 0000000..ae97abb --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,22 @@ +# Caller: scans THIS repo using the shared reusable Semgrep workflow in databunker-devops. +# Ruleset = PHP language pack + the common security packs (see semgrep-reusable.yml). +name: semgrep + +on: + workflow_dispatch: # manual "Run workflow" button in the Actions tab + pull_request: + push: + branches: [main] + schedule: + - cron: "13 7 * * 1" # weekly full sweep (Mon 07:13 UTC) — offset from gitleaks (06:27) + +permissions: + contents: read + security-events: write + +jobs: + sast: + name: sast # display + required-check name + uses: securitybunker/databunker-devops/.github/workflows/semgrep-reusable.yml@main + with: + config: "p/php p/secrets p/security-audit p/owasp-top-ten" diff --git a/README.md b/README.md index beb1296..f7f9463 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Official PHP client library for the DatabunkerPro API. - PHP 5.6 or higher - JSON extension +- cURL extension ## Installation @@ -51,6 +52,24 @@ $api->updateUser('email', 'user@example.com', [ ]); ``` +### TLS certificate verification + +By default the client **verifies the server's TLS certificate** on every request. This is the +secure default and should be left on in production. If you run a self-hosted Databunker instance +with a self-signed certificate in a trusted/development network, you can opt out by passing +`false` as the fourth constructor argument: + +```php +// Verify TLS certificates (default, recommended) +$api = new DatabunkerproAPI($baseURL, $token, $tenant); + +// Disable verification — only for self-signed certs on a trusted network +$api = new DatabunkerproAPI($baseURL, $token, $tenant, false); +``` + +Disabling verification exposes your traffic (access token and personal data) to +man-in-the-middle interception, so avoid it against any public endpoint. + ## Available Methods The library provides methods for all DatabunkerPro API endpoints: @@ -96,6 +115,27 @@ Fix code style issues: composer cs-fix ``` +## Security + +This library is scanned on every push and pull request, with a weekly scheduled sweep to catch drift: + +- **SAST (Semgrep):** static analysis using the `p/php`, `p/secrets`, `p/security-audit`, and `p/owasp-top-ten` rulesets. A finding fails the check, and results are published to the repository's **Code Scanning** tab. See [`.github/workflows/semgrep.yml`](.github/workflows/semgrep.yml). +- **Supply-chain hardening:** every GitHub Action is pinned to a full commit SHA, so a mutable tag (`@v4`) cannot be silently repointed to malicious code. +- **TLS on by default:** the client verifies server certificates unless you explicitly opt out (see [TLS certificate verification](#tls-certificate-verification)). + +Reproduce the SAST scan locally: + +```bash +pip install semgrep +semgrep scan \ + --config p/php \ + --config p/secrets \ + --config p/security-audit \ + --config p/owasp-top-ten +``` + +To report a security vulnerability, please email hello@databunker.org rather than opening a public issue. + ## Contributing 1. Fork the repository diff --git a/src/DatabunkerproApi.php b/src/DatabunkerproApi.php index e0d9b9a..a910080 100644 --- a/src/DatabunkerproApi.php +++ b/src/DatabunkerproApi.php @@ -8,11 +8,16 @@ class DatabunkerproApi { private $baseURL; private $xBunkerToken; private $xBunkerTenant; + private $verifySsl; - public function __construct($baseURL, $xBunkerToken = '', $xBunkerTenant = '') { + // $verifySsl defaults to true so TLS certificates are validated on every request. + // Only pass false for a self-hosted instance using a self-signed certificate in a + // trusted/dev network — disabling it exposes traffic (tokens + PII) to MITM attacks. + public function __construct($baseURL, $xBunkerToken = '', $xBunkerTenant = '', $verifySsl = true) { $this->baseURL = $baseURL; $this->xBunkerToken = $xBunkerToken; $this->xBunkerTenant = $xBunkerTenant; + $this->verifySsl = $verifySsl; } private function makeRequest($endpoint, $data = null, $requestMetadata = null) { @@ -30,7 +35,8 @@ private function makeRequest($endpoint, $data = null, $requestMetadata = null) { curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verifySsl); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->verifySsl ? 2 : 0); if ($data || $requestMetadata) { $bodyData = $data ? $data : []; if ($requestMetadata) { @@ -70,7 +76,8 @@ public function rawRequest($endpoint, $data = null, $requestMetadata = null) { curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verifySsl); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->verifySsl ? 2 : 0); if ($data || $requestMetadata) { $bodyData = $data ? $data : []; if ($requestMetadata) { From fe24920d4b4d13c49ce5e3688a108ed6bb182435 Mon Sep 17 00:00:00 2001 From: yuli Date: Tue, 21 Jul 2026 18:59:15 +0300 Subject: [PATCH 2/2] Inline Semgrep workflow (public repo can't call private reusable workflow) GitHub blocks public repos from using a reusable workflow in a private repo, causing a startup_failure. Replace the caller with a self-contained scan. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/semgrep.yml | 52 +++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index ae97abb..c25aeb3 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -1,5 +1,7 @@ -# Caller: scans THIS repo using the shared reusable Semgrep workflow in databunker-devops. -# Ruleset = PHP language pack + the common security packs (see semgrep-reusable.yml). +# Self-contained Semgrep SAST scan (Semgrep OSS engine + community rules — LGPL, no account needed). +# Inlined rather than calling the shared reusable workflow in databunker-devops, because GitHub +# blocks a PUBLIC repo from using a reusable workflow stored in a PRIVATE repo (org access does +# not lift this). Keep the ruleset in sync with the other public SDK repos. name: semgrep on: @@ -10,6 +12,8 @@ on: schedule: - cron: "13 7 * * 1" # weekly full sweep (Mon 07:13 UTC) — offset from gitleaks (06:27) +# Least privilege. security-events:write publishes SARIF to the Code Scanning tab (free on +# public repos). Semgrep itself needs no write scope. permissions: contents: read security-events: write @@ -17,6 +21,44 @@ permissions: jobs: sast: name: sast # display + required-check name - uses: securitybunker/databunker-devops/.github/workflows/semgrep-reusable.yml@main - with: - config: "p/php p/secrets p/security-audit p/owasp-top-ten" + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.12" + + - name: Install Semgrep (pinned) + run: pip install semgrep==1.170.0 # bump deliberately (semgrep.dev/docs/release-notes) + + - name: Run Semgrep + env: + SEMGREP_SEND_METRICS: "off" # no telemetry; community rules only + run: | + # --error: exit non-zero on any finding -> blocks merge when set as a required check. + # SARIF is written before the non-zero exit, so the upload steps (if: always()) run. + semgrep scan \ + --config p/php \ + --config p/secrets \ + --config p/security-audit \ + --config p/owasp-top-ten \ + --error \ + --sarif --output semgrep.sarif + + - name: Upload SARIF to Code Scanning + if: always() # publish findings even when the scan fails the check + uses: github/codeql-action/upload-sarif@bb16b9baa2ec4010b29f5c606d57d01190139edd # v4.37.1 + with: + sarif_file: semgrep.sarif + category: semgrep + + - name: Upload SARIF artifact (dated evidence, retained 180d) + if: always() # keep the report for triage + audit evidence even on failure + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: semgrep-report-${{ github.sha }} + path: semgrep.sarif + retention-days: 180