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
23 changes: 23 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,27 @@ jobs:
file: ./Dockerfile
platforms: linux/amd64
push: false
load: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
PG_VERSION=${{ env.PG_VERSION }}
cache-from: type=gha
cache-to: type=gha,mode=max

# Gate: a build whose PostGIS is vulnerable to CVE-2026-73514 /
# CVE-2026-73515 must never reach a tag. The PR job runs it on the loaded
# image; the push job below runs it on the pushed digest before the
# manifest job can move `latest`/`18` onto it.
- name: Verify PostGIS security (PR, amd64)
if: github.event_name == 'pull_request' && matrix.platform == 'linux/amd64'
run: |
set -euo pipefail
tag="$(echo '${{ steps.meta.outputs.tags }}' | head -1)"
docker run -d --name postgis-verify -e POSTGRES_PASSWORD=test "$tag"
until docker exec postgis-verify pg_isready -U postgres -q; do sleep 1; done
./scripts/verify-postgis-security.sh postgis-verify

- name: Build & push by digest
if: github.event_name != 'pull_request'
id: build
Expand All @@ -92,6 +106,15 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Verify PostGIS security (pushed digest)
if: github.event_name != 'pull_request' && matrix.platform == 'linux/amd64'
run: |
set -euo pipefail
image="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}"
docker run -d --name postgis-verify -e POSTGRES_PASSWORD=test "$image"
until docker exec postgis-verify pg_isready -U postgres -q; do sleep 1; done
./scripts/verify-postgis-security.sh postgis-verify

- name: Export digest
if: github.event_name != 'pull_request'
run: |
Expand Down
20 changes: 17 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

ARG PG_VERSION=18
ARG PGVECTOR_VERSION=0.8.2
ARG POSTGIS_VERSION=3.6.2
ARG POSTGIS_VERSION=3.6.4
ARG PG_TEXTSEARCH_VERSION=1.3.1
ARG PG_PARTMAN_VERSION=5.4.3

Expand Down Expand Up @@ -48,12 +48,26 @@ RUN git clone --branch v${PGVECTOR_VERSION} --depth 1 https://github.com/pgvecto
make OPTFLAGS="" -j$(nproc) && \
make install

# PostGIS with Tiger geocoder and address standardizer
# PostGIS with Tiger geocoder and address standardizer.
# patches/ carries upstream security fixes released after the 3.6.4 tarball
# (CVE-2026-73515 FlatGeobuf, CVE-2026-73514 address_standardizer) — `patch`
# exits non-zero on a reject, so a patch that stops applying fails the build.
COPY patches/ /build/patches/

RUN curl -L https://download.osgeo.org/postgis/source/postgis-${POSTGIS_VERSION}.tar.gz | tar xz && \
cd postgis-${POSTGIS_VERSION} && \
for p in /build/patches/*.patch; do \
echo "Applying $(basename "$p")" && patch -p1 --batch --forward < "$p"; \
done && \
./configure --without-raster --without-topology && \
make && \
make install
make install && \
mkdir -p /usr/local/share/postgresql/security && \
{ echo "postgis_source_version=${POSTGIS_VERSION}"; \
for p in /build/patches/*.patch; do echo "patch=$(basename "$p")"; done; \
echo "cve_fixed=CVE-2026-73514"; \
echo "cve_fixed=CVE-2026-73515"; \
} > /usr/local/share/postgresql/security/postgis-patches.txt

# pg_textsearch (BM25)
RUN git clone --branch v${PG_TEXTSEARCH_VERSION} --depth 1 https://github.com/timescale/pg_textsearch.git && \
Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build run stop clean test shell push
.PHONY: build run stop clean test verify-security shell push

IMAGE_NAME ?= constructiveio/postgres-plus
IMAGE_TAG ?= latest
Expand Down Expand Up @@ -41,9 +41,15 @@ test: build
CREATE EXTENSION pg_textsearch; \
CREATE EXTENSION pg_partman; \
SELECT 'all extensions OK';"
@./scripts/verify-postgis-security.sh $(CONTAINER_NAME)-test
@docker stop $(CONTAINER_NAME)-test > /dev/null
@docker rm $(CONTAINER_NAME)-test > /dev/null

# Assert the running $(CONTAINER_NAME) is not a PostGIS build vulnerable to
# CVE-2026-73514 / CVE-2026-73515.
verify-security:
@./scripts/verify-postgis-security.sh $(CONTAINER_NAME)

clean: stop
docker rmi $(IMAGE_NAME):$(IMAGE_TAG) || true

Expand Down
39 changes: 33 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Lean PostgreSQL 18 image with essential extensions for modern applications.
| Extension | Version | Description |
|-----------|---------|-------------|
| [pgvector](https://github.com/pgvector/pgvector) | 0.8.2 | Vector similarity search for embeddings |
| [PostGIS](https://postgis.net/) | 3.6.2 | Spatial and geographic data |
| [PostGIS](https://postgis.net/) | 3.6.4 (+ security patches) | Spatial and geographic data |
| [pg_textsearch](https://github.com/timescale/pg_textsearch) | 1.3.1 | BM25 full-text search |
| [pg_partman](https://github.com/pgpartman/pg_partman) | 5.4.3 | Partition management |
| [pg_stat_statements](https://www.postgresql.org/docs/current/pgstatstatements.html) | built-in | Query performance statistics |
Expand Down Expand Up @@ -49,13 +49,40 @@ CREATE EXTENSION pg_stat_statements;
## Build

```bash
make build # Build image
make test # Build and verify extensions
make run # Run container
make shell # psql into container
make clean # Remove image
make build # Build image
make test # Build, verify extensions, and run the PostGIS security gate
make verify-security # Run the security gate against an already-running container
make run # Run container
make shell # psql into container
make clean # Remove image
```

## PostGIS security patches

PostGIS is built from the `3.6.4` tarball with the upstream security fixes in
[`patches/`](./patches) applied on top — no released tarball carries them yet:

| Patch | Fixes |
|-------|-------|
| `0001-flatgeobuf-validate-input-buffers-before-decoding` | CVE-2026-73515 — out-of-bounds read decoding a FlatGeobuf buffer (`ST_FromFlatGeobuf`). `postgis/stable-3.6` `53e273fae`, landed after 3.6.4. |
| `0002-address_standardizer-harden-scanner-and-rule-parsing` | CVE-2026-73514 — equivalent to `423570b` in the split-out [`postgis/address_standardizer`](https://github.com/postgis/address_standardizer) repo. |
| `0003-address_standardizer-clean-up-partial-2D-allocations` | leak/partial-allocation cleanup accompanying the above. |
| `0004-Avoid-out-of-bounds-write-uninitialized-memory` | the `parse_rule()` off-by-one write past `rule_arr[MAX_RULE_LENGTH]` plus an uninitialized `RULE_PARAM`. |

Each is a `git format-patch` of the upstream commit cherry-picked onto the
`3.6.4` tag, so the provenance stays greppable and a patch that stops applying
fails the build rather than being silently skipped.

`scripts/verify-postgis-security.sh <container>` is the gate that keeps a
vulnerable build from reaching a tag. It asserts against the *installed*
extension, not the Dockerfile args: release floor, the patch manifest baked into
the image, that a truncated FlatGeobuf buffer is rejected with the backend still
alive, and that `standardize_address()` still works. CI runs it on every PR and
on the pushed digest before the `latest`/`18` manifests move.

Drop the patches when a PostGIS release contains all four fixes; the gate's
`POSTGIS_MIN_VERSION` floor and manifest check are what to update then.

## Building manually

```bash
Expand Down
Loading
Loading