Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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') }}
Expand Down
64 changes: 64 additions & 0 deletions .github/workflows/semgrep.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 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:
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)

# 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

jobs:
sast:
name: sast # display + required-check name
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
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Official PHP client library for the DatabunkerPro API.

- PHP 5.6 or higher
- JSON extension
- cURL extension

## Installation

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
13 changes: 10 additions & 3 deletions src/DatabunkerproApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
Loading