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
67 changes: 67 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# =============================================================================
# CodeQL Workflow — static analysis for Rust extension code
#
# Uses build-mode: none (the only mode supported for Rust) so CodeQL analyses
# the source directly. This complements clippy by looking for higher-level
# security and dataflow issues.
# =============================================================================
name: CodeQL

on:
push:
branches: [main]
paths-ignore:
- '**/*.md'
- 'docs/**'
- 'LICENSE'
- '.gitignore'
- 'PLAN*.md'
- 'REPORT*.md'
- 'adrs/**'
- 'plans/**'
- 'coverage/**'
pull_request:
branches: [main]
paths-ignore:
- '**/*.md'
- 'docs/**'
- 'LICENSE'
- '.gitignore'
- 'PLAN*.md'
- 'REPORT*.md'
- 'adrs/**'
- 'plans/**'
- 'coverage/**'
schedule:
- cron: '0 9 * * 1'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
actions: read
contents: read
security-events: write

env:
CARGO_TERM_COLOR: always
PG_VERSION: '18'

jobs:
analyze:
name: CodeQL (Rust)
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: rust
build-mode: none

- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v4
56 changes: 56 additions & 0 deletions .github/workflows/dependency-policy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# =============================================================================
# Dependency Policy Workflow — cargo-deny checks for advisory and supply-chain
# issues in the Rust dependency graph.
# =============================================================================
name: Dependency policy

on:
push:
branches: [main]
paths:
- 'Cargo.toml'
- 'Cargo.lock'
- 'deny.toml'
- '.github/workflows/dependency-policy.yml'
pull_request:
branches: [main]
paths:
- 'Cargo.toml'
- 'Cargo.lock'
- 'deny.toml'
- '.github/workflows/dependency-policy.yml'
schedule:
- cron: '30 9 * * 1'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
CARGO_TERM_COLOR: always

jobs:
cargo-deny:
name: cargo deny
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2

- name: Install cargo-deny
run: cargo install --locked cargo-deny

- name: Run cargo-deny policy checks
run: cargo deny check advisories bans sources
60 changes: 60 additions & 0 deletions .github/workflows/semgrep.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# =============================================================================
# Semgrep Workflow — repository-specific static checks for extension security
# hotspots such as dynamic SPI SQL and SECURITY DEFINER SQL.
#
# This first pass is advisory: findings are uploaded as SARIF but do not fail
# CI yet. That keeps the initial rollout useful without immediately blocking on
# the existing dynamic-SPI surface area.
# =============================================================================
name: Semgrep

on:
push:
branches: [main]
paths:
- 'src/**'
- 'sql/**'
- '.semgrep/**'
- '.github/workflows/semgrep.yml'
pull_request:
branches: [main]
paths:
- 'src/**'
- 'sql/**'
- '.semgrep/**'
- '.github/workflows/semgrep.yml'
schedule:
- cron: '0 10 * * 1'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read
security-events: write

jobs:
semgrep:
name: Semgrep
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install Semgrep
run: python -m pip install --upgrade semgrep

- name: Run Semgrep ruleset
run: semgrep scan --config .semgrep/pg_trickle.yml --sarif --output semgrep.sarif

- name: Upload SARIF report
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
44 changes: 44 additions & 0 deletions .semgrep/pg_trickle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
rules:
- id: rust.spi.run.dynamic-format
languages:
- rust
severity: WARNING
message: >-
Dynamic SQL passed to Spi::run() should be reviewed for quoting,
privilege context, and user-controlled interpolation. Prefer fixed SQL
with parameters when possible; otherwise ensure identifiers and literals
are escaped with the repo's quoting helpers.
patterns:
- pattern-either:
- pattern: Spi::run(&format!($FMT, ...))
- pattern: pgrx::Spi::run(&format!($FMT, ...))

- id: rust.spi.query.dynamic-format
languages:
- rust
severity: WARNING
message: >-
Dynamic SQL passed to SPI query helpers should be reviewed for SQL
injection risk, quoting discipline, and object-name construction.
patterns:
- pattern-either:
- pattern: Spi::get_one::<$T>(&format!($FMT, ...))
- pattern: Spi::get_two::<$T1, $T2>(&format!($FMT, ...))
- pattern: Spi::get_three::<$T1, $T2, $T3>(&format!($FMT, ...))
- pattern: pgrx::Spi::get_one::<$T>(&format!($FMT, ...))
- pattern: pgrx::Spi::get_two::<$T1, $T2>(&format!($FMT, ...))
- pattern: pgrx::Spi::get_three::<$T1, $T2, $T3>(&format!($FMT, ...))

- id: sql.security-definer.present
languages:
- generic
severity: INFO
message: >-
SECURITY DEFINER requires explicit review. Pin search_path and verify the
function body cannot be influenced by attacker-controlled identifiers or
SQL fragments.
paths:
include:
- src/**
- sql/**
pattern-regex: SECURITY\s+DEFINER
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,29 @@ For future plans and release milestones, see [ROADMAP.md](ROADMAP.md).

### Added

- **SAST / static security analysis baseline** — new security tooling on the
`codeql-workflow` branch, now merged:
- **GitHub CodeQL** (`.github/workflows/codeql.yml`) — runs 16 Rust security
queries (SQL injection, path injection, SSRF, cleartext logging, weak
crypto, uncontrolled allocation, invalid pointer access, and more) on every
PR and push to `main`. First scan completed with **zero findings** across
all 115 Rust source files. Uses `build-mode: none` (the only mode supported
for Rust) and the `v4` action.
- **`cargo deny`** (`.github/workflows/dependency-policy.yml` + `deny.toml`)
— enforces an explicit license allow-list (Apache-2.0, MIT, BSD-2-Clause,
BSD-3-Clause, ISC, Unlicense, Zlib, BSL-1.0, Unicode-3.0), blocks unknown
registries and git sources, and surfaces unmaintained/yanked crates as
warnings. Duplicate-version skews from upstream pgrx / testcontainers
version mismatches are suppressed via `skip` entries.
- **Semgrep** (`.github/workflows/semgrep.yml` + `.semgrep/pg_trickle.yml`)
— repo-specific rules flagging dynamic SQL passed to `Spi::run` /
`Spi::get_*` and `SECURITY DEFINER` occurrences. Advisory-only (SARIF
upload, no CI failure) until rules are tuned.
- **`plans/testing/PLAN_SAST.md`** — full SAST strategy document including
threat model, five-phase rollout plan, Semgrep rules roadmap, unsafe/FFI
review policy, CI posture table, and a **Security Newbie Checklist** for
reviewers unfamiliar with extension-specific security patterns.

- **TPC-H test suite enhancements (T1–T6)** — second wave of TPC-H correctness
coverage, building on the 22/22 passing DIFFERENTIAL baseline:
- **T1 — `__pgt_count` guard** in `assert_tpch_invariant`: detects
Expand Down
75 changes: 75 additions & 0 deletions deny.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
[graph]
all-features = true

[licenses]
# Allow the standard permissive and weak-copyleft open-source licenses used
# across the dependency tree. Unicode-3.0 is required by the icu_* crates;
# BSL-1.0 is used by Boost-derived crates pulled in by pgrx internals.
version = 2
allow = [
"Apache-2.0",
"MIT",
"BSD-2-Clause",
"BSD-3-Clause",
"ISC",
"Unlicense",
"Zlib",
"BSL-1.0",
"Unicode-3.0",
]

[advisories]
version = 2
yanked = "warn"
ignore = [
# paste is used internally by pgrx macros; we have no upstream control over
# this dep. Not a vulnerability, only a maintenance status notice.
"RUSTSEC-2024-0436",
# rustls-pemfile v2 is pulled in by testcontainers/bollard (dev-only path).
# Maintenance moved to rustls-pki-types; will be resolved when bollard updates.
"RUSTSEC-2025-0134",
# serde_cbor is pulled in transitively (dev path). Not a security vulnerability,
# only unmaintained. Use of ciborium is the upstream recommendation but we
# have no direct control over this dep.
"RUSTSEC-2021-0127",
]

[bans]
multiple-versions = "warn"
wildcards = "deny"
highlight = "all"
# These duplicate versions come from pgrx pulling one version and
# testcontainers/bollard pulling another. We have no direct control over these
# version skews; they are dev-only or cross-compilation stubs.
skip = [
# thiserror: pgrx uses v1, our code and testcontainers use v2
{ name = "thiserror", version = "=1.0.69" },
{ name = "thiserror-impl", version = "=1.0.69" },
# getrandom: split between pgrx (0.3) and newer crates (0.4)
{ name = "getrandom", version = "=0.3.4" },
# hashbrown: multiple major versions pulled by indexmap and others
{ name = "hashbrown", version = "=0.15.5" },
# wasi: 0.11 from older crates, 0.14 from newer wasmtime-based stack
{ name = "wasi", version = "=0.11.1+wasi-snapshot-preview1" },
# windows-* crates: version skew between ring (0.52) and socket2/hyper-util
# (0.53+) pulled by bollard/testcontainers
{ name = "windows-core", version = "=0.57.0" },
{ name = "windows-implement", version = "=0.57.0" },
{ name = "windows-interface", version = "=0.57.0" },
{ name = "windows-result", version = "=0.1.2" },
{ name = "windows-sys", version = "=0.60.2" },
{ name = "windows-targets", version = "=0.52.6" },
{ name = "windows_aarch64_gnullvm", version = "=0.52.6" },
{ name = "windows_aarch64_msvc", version = "=0.52.6" },
{ name = "windows_i686_gnu", version = "=0.52.6" },
{ name = "windows_i686_gnullvm", version = "=0.52.6" },
{ name = "windows_i686_msvc", version = "=0.52.6" },
{ name = "windows_x86_64_gnu", version = "=0.52.6" },
{ name = "windows_x86_64_gnullvm", version = "=0.52.6" },
{ name = "windows_x86_64_msvc", version = "=0.52.6" },
]

[sources]
unknown-registry = "deny"
unknown-git = "deny"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
Loading
Loading