Portable pre-admission evidence for PostgreSQL extensions.
2
stars
52
commits
Python
primary language
Aug 22, 2026
updated
Portable pre-admission security evidence for PostgreSQL extensions.
PgExtAssure inspects an extension source tree before a platform team allowlists, builds, or installs it. It reads PostgreSQL extension metadata and source files, reports security-relevant patterns, and can fail CI at a chosen severity.
It does not build, load, install, or execute the extension it scans.
PgExtAssure produces review evidence, not a certificate. A clean report is not proof that an extension is safe, and it is not legal, compliance, or security advice.
Canonical repository: https://github.com/borborich/pgextassure
Product site: https://pgextassure.shbb.pro/
The command-line executable and Python module are both named pgextassure.
Choose the shortest path for your role:
| You are | Start here | Result |
|---|---|---|
| Extension maintainer | Add the audit-only GitHub Action | A machine-readable report on every selected change |
| Platform or security reviewer | Create and verify Evidence Bundle 1.0 | Evidence bound to exact source, coverage, and policy inputs |
| Independent verifier | Run the public reproduction workflow | A public pass or failure record from infrastructure you control |
| Team with a real intake decision | Request a bounded evaluation | A customer-controlled admission evidence workflow and handoff report |
No hosted PgExtAssure account is required. The static scanner does not intentionally send source, findings, or telemetry to a PgExtAssure service.
PgExtAssure alpha.16 has been independently reproduced by multiple external software engineers. Three successful public workflow runs are recorded from independently controlled forks. The disclosure-safe public corpus index records 16 pinned projects and 2,114 files processed with alpha.15.
Read the exact run links, scope, and limitations in the Public validation record. These results demonstrate bounded reproducibility—not certification, customer adoption, production safety, or endorsement by an operator or employer.
For the problem and evidence-interface rationale, read PostgreSQL extension admission needs portable evidence.
Installing a PostgreSQL extension can cross unusually powerful trust
boundaries. Installation and upgrade scripts may run with elevated database
privileges; native modules may execute inside the PostgreSQL server process;
and a trusted or allowlisted extension can expose those capabilities to users
who are not superusers.
The admission decision is therefore difficult to review consistently:
PgExtAssure puts a deterministic, reviewable static gate before that decision.
From a clone of this repository:
git clone https://github.com/borborich/pgextassure.git
cd pgextassure
python -m venv .venv
source .venv/bin/activate
python -m pip install -e .
pgextassure scan /path/to/postgres-extension \
--format text \
--fail-on high
Equivalent module invocation:
python -m pgextassure scan /path/to/postgres-extension
Write a machine-readable report without blocking the current run:
pgextassure scan /path/to/postgres-extension \
--format json \
--output pgextassure.json \
--fail-on none
For a compact review queue that preserves every source location while grouping only findings with a proven shared routine identity:
pgextassure scan /path/to/postgres-extension \
--format grouped-json \
--output pgextassure-grouped.json \
--fail-on none
grouped-json is a separate report type. Its summary reports both raw finding
count and root-cause count. Rules without an explicit semantic identity remain
location-scoped and are never merged heuristically.
Create a deterministic work queue for a security engineer or AI coding agent:
pgextassure scan /path/to/postgres-extension \
--format review-json \
--output pgextassure-review.json \
--fail-on none
Agent Review Pack 1.0 binds every task to the exact grouped report and source
manifest, carries a closed disposition vocabulary, contains no source-file
payloads, and explicitly cannot grant admission. See
Agent Review Pack.
Create and verify the separate agent-authored Decision Ledger:
pgextassure review template pgextassure-review.json \
--output pgextassure-decisions.json
pgextassure review verify \
pgextassure-review.json \
pgextassure-decisions.json
Verification requires exact task coverage and citations for every resolved disposition. A structurally valid ledger still has no admission authority.
If reviewed build metadata generates an install SQL or control file that is absent from the source tree, supply a pinned, non-executing generation plan:
pgextassure scan /path/to/postgres-extension \
--generation-plan /path/to/generation-plan.json \
--format grouped-json \
--output pgextassure-grouped.json
PgExtAssure verifies every declared input SHA-256 and may apply bounded literal template substitutions in memory. It never runs the build. See Generation plans for the schema and trust boundary.
For a monorepository or source tree containing known generated aliases or oversized test fixtures, use a reviewed digest-bound scope plan:
pgextassure scan /path/to/postgres-extension \
--scope-plan /path/to/scope-plan.json \
--format grouped-json \
--output pgextassure-grouped.json
Scope plans declare non-overlapping relative scan roots. Every excluded regular file is pinned to its exact bytes and every excluded symlink to its exact target text. Missing, changed, or unused exclusions fail closed. See Scope plans.
To introduce a gate into a repository with existing findings, create and review a root-cause baseline:
pgextassure baseline /path/to/postgres-extension \
--created-on 2026-07-29 \
--output pgextassure-baseline.json
pgextassure scan /path/to/postgres-extension \
--baseline pgextassure-baseline.json \
--format grouped-json \
--fail-on high
New root causes still block. Baselined findings remain visible. Temporary exceptions require an exact root-cause ID, owner, reason, and expiry date; an expired exception blocks again. See Baselines and suppressions.
For a centrally reviewed gate, supply a strict organization policy:
pgextassure policy-template adoption \
--output pgextassure-policy.json
pgextassure scan /path/to/postgres-extension \
--policy pgextassure-policy.json \
--format grouped-json \
--output pgextassure-grouped.json
The policy owns the gate, can block exact capabilities or rules, and controls whether baseline/suppression mechanisms are allowed. See Organization policy.
Create a deterministic, independently verifiable pilot artifact:
pgextassure evidence create /path/to/postgres-extension \
--policy pgextassure-policy.json \
--created-on 2026-07-29 \
--component-name example-extension \
--output pgextassure-evidence.zip
pgextassure evidence verify pgextassure-evidence.zip
Evidence Bundle 1.0 binds the report, analyzed-source manifest, coverage, limited SPDX 2.3 inventory, and exact control-input bytes. It contains no source-file payloads. It can be signed with GitHub/Sigstore attestations or with an offline corporate RSA key:
pgextassure evidence sign pgextassure-evidence.zip \
--private-key corporate-release-key.pem \
--signer-id acme-security/postgresql-admission-key-01 \
--statement-output pgextassure-signature.json \
--signature-output pgextassure-signature.bin \
--public-key-output pgextassure-public-key.pem
pgextassure evidence verify-signature pgextassure-evidence.zip \
--statement pgextassure-signature.json \
--signature pgextassure-signature.bin \
--public-key pgextassure-public-key.pem \
--expected-key-sha256 'sha256:TRUSTED_64_HEX_DIGEST'
See Evidence bundles, Corporate Evidence Signature Profile 1.0, and the enterprise pilot.
Evaluate signed evidence against an organization-owned trust policy and create a deterministic Admission Receipt:
pgextassure trust evaluate pgextassure-evidence.zip \
--statement pgextassure-signature.json \
--signature pgextassure-signature.bin \
--public-key pgextassure-public-key.pem \
--trust-policy enterprise-trust-policy.json \
--evaluated-on 2026-07-29 \
--request-id CHG-2026-0042 \
--target postgresql-prod-eu/extension-slot-01 \
--output pgextassure-admission-receipt.json
Enterprise Trust Policy 1.0 owns accepted signers, key validity/revocation, tool/ruleset/policy constraints, age limits, and receipt lifetime. A deny receipt remains independently verifiable. See Enterprise trust and Admission Receipts.
Create a deterministic buyer handoff after all evidence, trust, and verification records are staged:
pgextassure pilot package pilot-staging \
--output pgextassure-enterprise-pilot.zip
pgextassure pilot verify-package pgextassure-enterprise-pilot.zip
The package command enforces a closed file set, release checksums, bounded content, flat non-symlink inputs, and private-key marker rejection. See Enterprise Pilot Package 1.0.
Validate both the package and a deployed TLS 1.3/mTLS Admission Gateway with one receiving-organization command:
pgextassure pilot accept pgextassure-enterprise-pilot.zip \
--gateway-url https://pgextassure-gateway.example.internal \
--ca-certificate gateway-ca.pem \
--client-certificate pilot-client.pem \
--client-key pilot-client-key.pem \
--expected-package-sha256 sha256:PACKAGE_DIGEST \
--expected-key-sha256 sha256:PUBLIC_KEY_FINGERPRINT \
--expected-trust-policy-sha256 sha256:TRUST_POLICY_DIGEST \
--expected-request-id CHG-2026-0042 \
--expected-target postgresql-prod/extension-slot-01 \
--expected-evaluated-on 2026-07-29 \
--verified-on 2026-07-29 \
--idempotency-key pilot-CHG-2026-0042-acceptance-01 \
--output pgextassure-pilot-acceptance.json
The runner performs offline enforcement, positive and negative TLS checks, first admission, and byte-identical replay, then retains a canonical credential-free report. See Self-service pilot acceptance.
Exit behavior is controlled by --fail-on:
critical | high | medium | low | none
The process exits non-zero when at least one finding meets or exceeds the selected threshold. Scanner errors also exit non-zero.
The exact rendering may evolve, but every format carries the same core evidence: rule identifier, severity, location, explanation, and remediation.
CRITICAL sql.security-definer-search-path
sql/example--1.0.sql:42
SECURITY DEFINER function does not establish a constrained search_path.
An attacker may be able to redirect an unqualified object reference.
Evidence:
CREATE FUNCTION example.refresh_cache() ...
SECURITY DEFINER;
Remediation:
Set a safe search_path and schema-qualify referenced objects. Review the
function against PostgreSQL's SECURITY DEFINER guidance.
Example summary:
PgExtAssure 0.1.0-alpha.17
Manifest: sha256:93c7a1aa82da96c290155124b31fcfaa15e369d105cef327c38c17e1b82d8128
Coverage: sha256:7cd80d20a4cdb7b1b88828e3d769f36a3353e6c955e07b65f717efa0d9c62a51 | Skipped: 0
Files: 6 | Findings: 0 (critical 0, high 0, medium 0, low 0)
Findings identify review work; they do not establish exploitability.
The repository includes a composite Action that installs the local PgExtAssure package and scans the requested path. The target extension is never executed.
name: PgExtAssure
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
security-events: write
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: borborich/pgextassure@v0.1.0-alpha.17
with:
path: .
format: sarif
output: pgextassure.sarif
annotations: active
max-annotations: "25"
fail-on: high
- name: Upload SARIF
if: ${{ always() && github.event_name != 'pull_request' }}
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
sarif_file: pgextassure.sarif
Action inputs:
| Input | Default | Meaning |
|---|---|---|
path | . | Extension source directory or supported input file |
format | sarif | text, json, grouped-json, review-json, or sarif |
output | pgextassure.sarif | Report file; set to an empty string for stdout |
generation-plan | empty | Optional reviewed, pinned generated-artifact declaration |
scope-plan | empty | Optional reviewed, digest-bound roots and exact exclusions |
baseline | empty | Optional reviewed root-cause baseline |
suppressions | empty | Optional owner-attributed expiring suppressions |
evaluated-on | current UTC date | Explicit YYYY-MM-DD suppression evaluation date |
policy | empty | Optional organization policy that owns the gate |
annotations | none | active, all, or none root-cause annotations |
max-annotations | 25 | Maximum annotation lines, from 2 through 50 |
evidence-output | empty | Enables evidence mode and writes Bundle 1.0 |
evidence-predicate-output | pgextassure-evidence-predicate.json | Verified custom-attestation predicate |
evidence-sbom-output | pgextassure-sbom.spdx.json | Verified SPDX inventory |
evidence-created-on | current UTC date | Optional explicit bundle date |
component-name | postgresql-extension | Non-secret SPDX component name |
component-version | empty | Optional SPDX component version |
fail-on | none | Minimum blocking severity, or none |
python-version | 3.11 | Python used to run PgExtAssure |
Annotations are grouped by root cause, omit matched source evidence, and are
bounded by max-annotations. active includes active and expired decisions;
all also emits accepted baseline and suppression decisions as notices. A
report output is required because workflow commands use stdout. See
GitHub annotations.
When evidence-output is set, the Action creates and verifies a bundle instead
of a standalone report. GitHub annotations are disabled in this mode. The
Action exposes the bundle, predicate, and SPDX paths as evidence-bundle,
evidence-predicate, and evidence-sbom outputs. Signing still requires an
explicit caller-owned actions/attest step and OIDC permissions.
Pin a released commit SHA in higher-assurance workflows. Uploading SARIF sends the generated report to GitHub; review your repository visibility, retention, and access settings before enabling that step. The example uploads only on non-PR events because fork pull requests receive a read-only token. SARIF upload is available for public repositories and for eligible private/internal repositories with GitHub Code Security enabled.
SARIF artifact URIs are made relative to GITHUB_WORKSPACE when the target is
inside that workspace, and special path characters are URI-encoded. The
embedded actions/setup-python v7 runtime requires GitHub Actions Runner
2.327.1 or newer; GitHub-hosted runners already satisfy this requirement.
The Action is audit-only by default because some high-severity records are
privileged-capability inventory rather than proven defects. Set fail-on
explicitly only after reviewing a baseline and choosing a policy appropriate to
the target platform.
The static MVP scans local files only. It is intended for an unpacked PostgreSQL extension source tree containing some or all of:
*.control files;*.control.in);*.sql / *.sql.in artifacts, including
their version graph;*.c, *.h);*.rs) and Cargo.toml.It does not fetch a repository, download a release, unpack an untrusted archive, connect to PostgreSQL, or resolve package dependencies. Scan an already checked out, bounded directory. Directory scans fail closed when no supported source is found, when a supported source is non-regular, or when the tree contains a symlinked directory/source that could escape the reviewed boundary. It also enforces limits on total entries, directories, path depth, path length, source files, bytes, and findings.
Every report exposes the exact static boundary: analyzed files are content-hashed in the manifest, while unsupported entries appear in a bounded metadata-only skipped-file inventory. See Report schemas and coverage.
See Rule reference for the implemented checks and input coverage.
PgExtAssure can normalize saved pgspot 0.9.2 single-file output without
executing pgspot or extension code:
pgextassure adapter pgspot sql/extension.sql \
--stdout pgspot.stdout \
--subject-path sql/extension.sql \
--analyzer-version 0.9.2 \
--exit-code 1 \
--output pgspot.external-analysis.json
pgextassure adapter verify pgspot.external-analysis.json \
--source sql/extension.sql \
--stdout pgspot.stdout
The strict document binds the exact source and stdout bytes, declared analyzer version, saved exit code, counters, and normalized diagnostics. It is observational review evidence and does not grant or deny admission. See External analyzer evidence.
Rules are grouped around pre-admission questions:
COPY ... PROGRAM, server-side file
access, untrusted procedural languages, or public execution grants?search_path?PgExtAssure favors evidence and remediation over a single opaque score. A finding can be a true security defect, a deliberate privileged capability, or a false positive requiring suppression in a future release.
The static MVP:
To keep hostile inputs from creating unbounded reports, a file emits at most 32 findings per rule; the first retained finding records when additional matches were omitted. A scan fails rather than returning a partial report if it exceeds the global finding limit.
Do not use a clean PgExtAssure result as the sole reason to grant superuser, filesystem, process, network, or preload privileges.
These tools solve different problems and can be used alongside PgExtAssure.
PgExtAssure assumes the scanned tree may be malicious or compromised. The MVP keeps that tree on the data side of the boundary: it reads recognized files and does not invoke its build system, SQL, hooks, or binaries.
The principal protected assets are:
The main adversaries are a malicious extension author, a compromised upstream release, and an otherwise benign maintainer who introduced an unsafe construct. Runtime-only behavior, unknown parser evasions, transitive dependency attacks, and source-to-binary substitution remain outside the static MVP's guarantees.
Read the complete threat model before using PgExtAssure as a CI gate.
The next assurance layer is intentionally separate from the static scanner:
Dynamic analysis will still be evidence, not certification. Details are in the roadmap.
PgExtAssure maintains a pinned public corpus for reproducibility and scanner quality. The disclosure-safe Extension Assurance Index publishes analysis coverage, provenance, and capability profiles for public revisions while omitting findings, severities, rule identifiers, evidence, paths, and source excerpts.
A completed index entry means only that the pinned source was processed. It is not a security rating, certification, endorsement, vulnerability report, or allowlist decision. Project-specific security observations remain behind the corpus publication gate until they have been reviewed and, where appropriate, coordinated with the upstream maintainer.
No corpus upload or telemetry exists in the static MVP. Any future contribution flow must be explicit opt-in, preview the exact payload, and support deletion.
--output writes only to the path you choose.python -m pip install -e .
python -m unittest discover -s tests -v
The CI workflow runs the same unit-test command.
Apache License 2.0. See LICENSE.
51 commits
1 commits
Python
97.9%
Portable pre-admission evidence for PostgreSQL extensions.
2
stars
52
commits
Python
primary language
Aug 22, 2026
updated
Portable pre-admission security evidence for PostgreSQL extensions.
PgExtAssure inspects an extension source tree before a platform team allowlists, builds, or installs it. It reads PostgreSQL extension metadata and source files, reports security-relevant patterns, and can fail CI at a chosen severity.
It does not build, load, install, or execute the extension it scans.
PgExtAssure produces review evidence, not a certificate. A clean report is not proof that an extension is safe, and it is not legal, compliance, or security advice.
Canonical repository: https://github.com/borborich/pgextassure
Product site: https://pgextassure.shbb.pro/
The command-line executable and Python module are both named pgextassure.
Choose the shortest path for your role:
| You are | Start here | Result |
|---|---|---|
| Extension maintainer | Add the audit-only GitHub Action | A machine-readable report on every selected change |
| Platform or security reviewer | Create and verify Evidence Bundle 1.0 | Evidence bound to exact source, coverage, and policy inputs |
| Independent verifier | Run the public reproduction workflow | A public pass or failure record from infrastructure you control |
| Team with a real intake decision | Request a bounded evaluation | A customer-controlled admission evidence workflow and handoff report |
No hosted PgExtAssure account is required. The static scanner does not intentionally send source, findings, or telemetry to a PgExtAssure service.
PgExtAssure alpha.16 has been independently reproduced by multiple external software engineers. Three successful public workflow runs are recorded from independently controlled forks. The disclosure-safe public corpus index records 16 pinned projects and 2,114 files processed with alpha.15.
Read the exact run links, scope, and limitations in the Public validation record. These results demonstrate bounded reproducibility—not certification, customer adoption, production safety, or endorsement by an operator or employer.
For the problem and evidence-interface rationale, read PostgreSQL extension admission needs portable evidence.
Installing a PostgreSQL extension can cross unusually powerful trust
boundaries. Installation and upgrade scripts may run with elevated database
privileges; native modules may execute inside the PostgreSQL server process;
and a trusted or allowlisted extension can expose those capabilities to users
who are not superusers.
The admission decision is therefore difficult to review consistently:
PgExtAssure puts a deterministic, reviewable static gate before that decision.
From a clone of this repository:
git clone https://github.com/borborich/pgextassure.git
cd pgextassure
python -m venv .venv
source .venv/bin/activate
python -m pip install -e .
pgextassure scan /path/to/postgres-extension \
--format text \
--fail-on high
Equivalent module invocation:
python -m pgextassure scan /path/to/postgres-extension
Write a machine-readable report without blocking the current run:
pgextassure scan /path/to/postgres-extension \
--format json \
--output pgextassure.json \
--fail-on none
For a compact review queue that preserves every source location while grouping only findings with a proven shared routine identity:
pgextassure scan /path/to/postgres-extension \
--format grouped-json \
--output pgextassure-grouped.json \
--fail-on none
grouped-json is a separate report type. Its summary reports both raw finding
count and root-cause count. Rules without an explicit semantic identity remain
location-scoped and are never merged heuristically.
Create a deterministic work queue for a security engineer or AI coding agent:
pgextassure scan /path/to/postgres-extension \
--format review-json \
--output pgextassure-review.json \
--fail-on none
Agent Review Pack 1.0 binds every task to the exact grouped report and source
manifest, carries a closed disposition vocabulary, contains no source-file
payloads, and explicitly cannot grant admission. See
Agent Review Pack.
Create and verify the separate agent-authored Decision Ledger:
pgextassure review template pgextassure-review.json \
--output pgextassure-decisions.json
pgextassure review verify \
pgextassure-review.json \
pgextassure-decisions.json
Verification requires exact task coverage and citations for every resolved disposition. A structurally valid ledger still has no admission authority.
If reviewed build metadata generates an install SQL or control file that is absent from the source tree, supply a pinned, non-executing generation plan:
pgextassure scan /path/to/postgres-extension \
--generation-plan /path/to/generation-plan.json \
--format grouped-json \
--output pgextassure-grouped.json
PgExtAssure verifies every declared input SHA-256 and may apply bounded literal template substitutions in memory. It never runs the build. See Generation plans for the schema and trust boundary.
For a monorepository or source tree containing known generated aliases or oversized test fixtures, use a reviewed digest-bound scope plan:
pgextassure scan /path/to/postgres-extension \
--scope-plan /path/to/scope-plan.json \
--format grouped-json \
--output pgextassure-grouped.json
Scope plans declare non-overlapping relative scan roots. Every excluded regular file is pinned to its exact bytes and every excluded symlink to its exact target text. Missing, changed, or unused exclusions fail closed. See Scope plans.
To introduce a gate into a repository with existing findings, create and review a root-cause baseline:
pgextassure baseline /path/to/postgres-extension \
--created-on 2026-07-29 \
--output pgextassure-baseline.json
pgextassure scan /path/to/postgres-extension \
--baseline pgextassure-baseline.json \
--format grouped-json \
--fail-on high
New root causes still block. Baselined findings remain visible. Temporary exceptions require an exact root-cause ID, owner, reason, and expiry date; an expired exception blocks again. See Baselines and suppressions.
For a centrally reviewed gate, supply a strict organization policy:
pgextassure policy-template adoption \
--output pgextassure-policy.json
pgextassure scan /path/to/postgres-extension \
--policy pgextassure-policy.json \
--format grouped-json \
--output pgextassure-grouped.json
The policy owns the gate, can block exact capabilities or rules, and controls whether baseline/suppression mechanisms are allowed. See Organization policy.
Create a deterministic, independently verifiable pilot artifact:
pgextassure evidence create /path/to/postgres-extension \
--policy pgextassure-policy.json \
--created-on 2026-07-29 \
--component-name example-extension \
--output pgextassure-evidence.zip
pgextassure evidence verify pgextassure-evidence.zip
Evidence Bundle 1.0 binds the report, analyzed-source manifest, coverage, limited SPDX 2.3 inventory, and exact control-input bytes. It contains no source-file payloads. It can be signed with GitHub/Sigstore attestations or with an offline corporate RSA key:
pgextassure evidence sign pgextassure-evidence.zip \
--private-key corporate-release-key.pem \
--signer-id acme-security/postgresql-admission-key-01 \
--statement-output pgextassure-signature.json \
--signature-output pgextassure-signature.bin \
--public-key-output pgextassure-public-key.pem
pgextassure evidence verify-signature pgextassure-evidence.zip \
--statement pgextassure-signature.json \
--signature pgextassure-signature.bin \
--public-key pgextassure-public-key.pem \
--expected-key-sha256 'sha256:TRUSTED_64_HEX_DIGEST'
See Evidence bundles, Corporate Evidence Signature Profile 1.0, and the enterprise pilot.
Evaluate signed evidence against an organization-owned trust policy and create a deterministic Admission Receipt:
pgextassure trust evaluate pgextassure-evidence.zip \
--statement pgextassure-signature.json \
--signature pgextassure-signature.bin \
--public-key pgextassure-public-key.pem \
--trust-policy enterprise-trust-policy.json \
--evaluated-on 2026-07-29 \
--request-id CHG-2026-0042 \
--target postgresql-prod-eu/extension-slot-01 \
--output pgextassure-admission-receipt.json
Enterprise Trust Policy 1.0 owns accepted signers, key validity/revocation, tool/ruleset/policy constraints, age limits, and receipt lifetime. A deny receipt remains independently verifiable. See Enterprise trust and Admission Receipts.
Create a deterministic buyer handoff after all evidence, trust, and verification records are staged:
pgextassure pilot package pilot-staging \
--output pgextassure-enterprise-pilot.zip
pgextassure pilot verify-package pgextassure-enterprise-pilot.zip
The package command enforces a closed file set, release checksums, bounded content, flat non-symlink inputs, and private-key marker rejection. See Enterprise Pilot Package 1.0.
Validate both the package and a deployed TLS 1.3/mTLS Admission Gateway with one receiving-organization command:
pgextassure pilot accept pgextassure-enterprise-pilot.zip \
--gateway-url https://pgextassure-gateway.example.internal \
--ca-certificate gateway-ca.pem \
--client-certificate pilot-client.pem \
--client-key pilot-client-key.pem \
--expected-package-sha256 sha256:PACKAGE_DIGEST \
--expected-key-sha256 sha256:PUBLIC_KEY_FINGERPRINT \
--expected-trust-policy-sha256 sha256:TRUST_POLICY_DIGEST \
--expected-request-id CHG-2026-0042 \
--expected-target postgresql-prod/extension-slot-01 \
--expected-evaluated-on 2026-07-29 \
--verified-on 2026-07-29 \
--idempotency-key pilot-CHG-2026-0042-acceptance-01 \
--output pgextassure-pilot-acceptance.json
The runner performs offline enforcement, positive and negative TLS checks, first admission, and byte-identical replay, then retains a canonical credential-free report. See Self-service pilot acceptance.
Exit behavior is controlled by --fail-on:
critical | high | medium | low | none
The process exits non-zero when at least one finding meets or exceeds the selected threshold. Scanner errors also exit non-zero.
The exact rendering may evolve, but every format carries the same core evidence: rule identifier, severity, location, explanation, and remediation.
CRITICAL sql.security-definer-search-path
sql/example--1.0.sql:42
SECURITY DEFINER function does not establish a constrained search_path.
An attacker may be able to redirect an unqualified object reference.
Evidence:
CREATE FUNCTION example.refresh_cache() ...
SECURITY DEFINER;
Remediation:
Set a safe search_path and schema-qualify referenced objects. Review the
function against PostgreSQL's SECURITY DEFINER guidance.
Example summary:
PgExtAssure 0.1.0-alpha.17
Manifest: sha256:93c7a1aa82da96c290155124b31fcfaa15e369d105cef327c38c17e1b82d8128
Coverage: sha256:7cd80d20a4cdb7b1b88828e3d769f36a3353e6c955e07b65f717efa0d9c62a51 | Skipped: 0
Files: 6 | Findings: 0 (critical 0, high 0, medium 0, low 0)
Findings identify review work; they do not establish exploitability.
The repository includes a composite Action that installs the local PgExtAssure package and scans the requested path. The target extension is never executed.
name: PgExtAssure
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
security-events: write
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: borborich/pgextassure@v0.1.0-alpha.17
with:
path: .
format: sarif
output: pgextassure.sarif
annotations: active
max-annotations: "25"
fail-on: high
- name: Upload SARIF
if: ${{ always() && github.event_name != 'pull_request' }}
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
sarif_file: pgextassure.sarif
Action inputs:
| Input | Default | Meaning |
|---|---|---|
path | . | Extension source directory or supported input file |
format | sarif | text, json, grouped-json, review-json, or sarif |
output | pgextassure.sarif | Report file; set to an empty string for stdout |
generation-plan | empty | Optional reviewed, pinned generated-artifact declaration |
scope-plan | empty | Optional reviewed, digest-bound roots and exact exclusions |
baseline | empty | Optional reviewed root-cause baseline |
suppressions | empty | Optional owner-attributed expiring suppressions |
evaluated-on | current UTC date | Explicit YYYY-MM-DD suppression evaluation date |
policy | empty | Optional organization policy that owns the gate |
annotations | none | active, all, or none root-cause annotations |
max-annotations | 25 | Maximum annotation lines, from 2 through 50 |
evidence-output | empty | Enables evidence mode and writes Bundle 1.0 |
evidence-predicate-output | pgextassure-evidence-predicate.json | Verified custom-attestation predicate |
evidence-sbom-output | pgextassure-sbom.spdx.json | Verified SPDX inventory |
evidence-created-on | current UTC date | Optional explicit bundle date |
component-name | postgresql-extension | Non-secret SPDX component name |
component-version | empty | Optional SPDX component version |
fail-on | none | Minimum blocking severity, or none |
python-version | 3.11 | Python used to run PgExtAssure |
Annotations are grouped by root cause, omit matched source evidence, and are
bounded by max-annotations. active includes active and expired decisions;
all also emits accepted baseline and suppression decisions as notices. A
report output is required because workflow commands use stdout. See
GitHub annotations.
When evidence-output is set, the Action creates and verifies a bundle instead
of a standalone report. GitHub annotations are disabled in this mode. The
Action exposes the bundle, predicate, and SPDX paths as evidence-bundle,
evidence-predicate, and evidence-sbom outputs. Signing still requires an
explicit caller-owned actions/attest step and OIDC permissions.
Pin a released commit SHA in higher-assurance workflows. Uploading SARIF sends the generated report to GitHub; review your repository visibility, retention, and access settings before enabling that step. The example uploads only on non-PR events because fork pull requests receive a read-only token. SARIF upload is available for public repositories and for eligible private/internal repositories with GitHub Code Security enabled.
SARIF artifact URIs are made relative to GITHUB_WORKSPACE when the target is
inside that workspace, and special path characters are URI-encoded. The
embedded actions/setup-python v7 runtime requires GitHub Actions Runner
2.327.1 or newer; GitHub-hosted runners already satisfy this requirement.
The Action is audit-only by default because some high-severity records are
privileged-capability inventory rather than proven defects. Set fail-on
explicitly only after reviewing a baseline and choosing a policy appropriate to
the target platform.
The static MVP scans local files only. It is intended for an unpacked PostgreSQL extension source tree containing some or all of:
*.control files;*.control.in);*.sql / *.sql.in artifacts, including
their version graph;*.c, *.h);*.rs) and Cargo.toml.It does not fetch a repository, download a release, unpack an untrusted archive, connect to PostgreSQL, or resolve package dependencies. Scan an already checked out, bounded directory. Directory scans fail closed when no supported source is found, when a supported source is non-regular, or when the tree contains a symlinked directory/source that could escape the reviewed boundary. It also enforces limits on total entries, directories, path depth, path length, source files, bytes, and findings.
Every report exposes the exact static boundary: analyzed files are content-hashed in the manifest, while unsupported entries appear in a bounded metadata-only skipped-file inventory. See Report schemas and coverage.
See Rule reference for the implemented checks and input coverage.
PgExtAssure can normalize saved pgspot 0.9.2 single-file output without
executing pgspot or extension code:
pgextassure adapter pgspot sql/extension.sql \
--stdout pgspot.stdout \
--subject-path sql/extension.sql \
--analyzer-version 0.9.2 \
--exit-code 1 \
--output pgspot.external-analysis.json
pgextassure adapter verify pgspot.external-analysis.json \
--source sql/extension.sql \
--stdout pgspot.stdout
The strict document binds the exact source and stdout bytes, declared analyzer version, saved exit code, counters, and normalized diagnostics. It is observational review evidence and does not grant or deny admission. See External analyzer evidence.
Rules are grouped around pre-admission questions:
COPY ... PROGRAM, server-side file
access, untrusted procedural languages, or public execution grants?search_path?PgExtAssure favors evidence and remediation over a single opaque score. A finding can be a true security defect, a deliberate privileged capability, or a false positive requiring suppression in a future release.
The static MVP:
To keep hostile inputs from creating unbounded reports, a file emits at most 32 findings per rule; the first retained finding records when additional matches were omitted. A scan fails rather than returning a partial report if it exceeds the global finding limit.
Do not use a clean PgExtAssure result as the sole reason to grant superuser, filesystem, process, network, or preload privileges.
These tools solve different problems and can be used alongside PgExtAssure.
PgExtAssure assumes the scanned tree may be malicious or compromised. The MVP keeps that tree on the data side of the boundary: it reads recognized files and does not invoke its build system, SQL, hooks, or binaries.
The principal protected assets are:
The main adversaries are a malicious extension author, a compromised upstream release, and an otherwise benign maintainer who introduced an unsafe construct. Runtime-only behavior, unknown parser evasions, transitive dependency attacks, and source-to-binary substitution remain outside the static MVP's guarantees.
Read the complete threat model before using PgExtAssure as a CI gate.
The next assurance layer is intentionally separate from the static scanner:
Dynamic analysis will still be evidence, not certification. Details are in the roadmap.
PgExtAssure maintains a pinned public corpus for reproducibility and scanner quality. The disclosure-safe Extension Assurance Index publishes analysis coverage, provenance, and capability profiles for public revisions while omitting findings, severities, rule identifiers, evidence, paths, and source excerpts.
A completed index entry means only that the pinned source was processed. It is not a security rating, certification, endorsement, vulnerability report, or allowlist decision. Project-specific security observations remain behind the corpus publication gate until they have been reviewed and, where appropriate, coordinated with the upstream maintainer.
No corpus upload or telemetry exists in the static MVP. Any future contribution flow must be explicit opt-in, preview the exact payload, and support deletion.
--output writes only to the path you choose.python -m pip install -e .
python -m unittest discover -s tests -v
The CI workflow runs the same unit-test command.
Apache License 2.0. See LICENSE.
51 commits
1 commits
Python
97.9%