samkhya is post-1.0 software (currently v1.2.x). The public API and
on-disk formats (Puffin sidecar layout, sketch payload codecs, SQLite
feedback-store schema) are covered by semver. Breaking changes require a
major-version bump and the deprecation window in docs/SEMVER.md. The
supply-chain guarantees in this document apply across all supported lines.
Sole author and security contact: Prateek Singh (via GitHub Security
Advisories on the singhpratech/samkhya repository — see the
"Reporting a vulnerability" section below). GHSA-only intake — do not
contact the maintainer by email for security issues.
samkhya follows the standard semver "current major + previous major's last minor" support window. Older releases receive no further security backports — operators on those lines must upgrade.
| Version | Supported |
|---|---|
| 1.2.x | yes (current) |
| 1.1.x | yes |
| 1.0.x | yes |
| < 1.0.0 | no |
The previous-major row will populate once v2.0 ships.
Do not open a public GitHub issue for security reports, and do not contact the maintainer by email. This project uses a GitHub Security Advisory (GHSA) only disclosure channel. The canonical URL pattern is:
https://github.com/singhpratech/samkhya/security/advisories/new
From the repository page → Security tab → Advisories → Report a vulnerability. This is the only supported intake channel; it is the one wired into the release process and the only channel for which an acknowledgement SLA is committed.
Initial acknowledgement target: 3 business days. Triage and remediation plan: 14 business days from acknowledgement.
Standard embargo: 90 days from acknowledgement to public disclosure. This matches the convention used by the broader Rust ecosystem (RustSec advisory database, cargo / crates.io security team) and gives downstream embedders (samkhya-datafusion adapter users, samkhya-duckdb extension users, samkhya-py wheel consumers) time to roll out fixes.
The embargo is negotiable in either direction:
- Shorter — when the vulnerability is already being actively exploited, or when the reporter has a public-talk deadline they've cleared with us, the embargo may be reduced (often to 7-14 days).
- Longer — when coordinated disclosure with an upstream dependency
(e.g. an issue in
gbdt,good_lp, orrusqlitethat surfaces through samkhya) requires more time, the embargo may extend beyond 90 days. In that case we publish an advisory at the original 90-day mark describing the shape of the issue and the affected version range, with full technical details deferred until the upstream fix lands.
A CVE will be requested for any vulnerability rated medium or higher on the CVSS v3.1 scale.
In-scope:
- All 13 workspace crates, whether or not they are released independently.
Eleven are publishable;
samkhya-benchand thesamkhya-itintegration-test harness setpublish = false:samkhya-core,samkhya-cli,samkhya-arrow,samkhya-bench,samkhya-datafusion,samkhya-duckdb,samkhya-duckdb-ext,samkhya-polars,samkhya-iceberg,samkhya-postgres,samkhya-gpudb,samkhya-py. - The on-disk formats: Puffin sidecar layout and every sketch payload
codec (
HllSketch::from_bytes,BloomFilter::from_bytes,CountMinSketch::from_bytes,EquiDepthHistogram::from_bytes,CorrelatedHistogram2D::from_bytes) plus the SQLite feedback-store schema. Everyfrom_bytesconstructor is adversarial-input scope: any panic, OOB read, allocator-DoS, or silent corruption on attacker -supplied bytes is a security bug. - The v1.1 portable-statistics handoff —
PortableStatsSnapshot, the Icebergload_portable_stats_from_tableloader, and the shared DataFusion / DuckDB consumers. These read Puffin blobs referenced from real Iceberg table metadata; rejecting corrupt payloads, duplicate or unknown blob kinds, and schema / snapshot / sequence-number mismatches (including explicitly declared future schema versions) is security scope. A malformed or stale sidecar that reaches engine internals unvalidated is a security bug. - The build pipeline (CI workflows,
deny.toml,Cargo.lock).
Out-of-scope:
- Bugs in transitive dependencies. RUSTSEC advisories suppressed in
deny.tomlare documented per the project's advisory-triage policy: every ignore entry carries an unreachability argument and a retire-condition (the upstream PR, the version we bump to, or the audit task that retires the ignore). Operators should re-runcargo deny check advisoriesafter every pull. - Every corrector backend equally — operator-chosen. The
Correctortrait surface (GBT default, AdditiveGBT opt-in, TabPFN-2.5 viatabpfn_http, and LLM-pluggable viallm_http) is opt-in and operator-controlled. Network behaviour, credential handling (includingTABPFN_TOKEN), telemetry disablement (TABPFN_DISABLE_TELEMETRY=1), and the choice of inference endpoint are operator concerns. samkhya commits only that every non-default backend is gated behind an explicit Cargo feature flag and is off in the default build. Operator obligation: corrector-backend selection is an operator decision; the framework does not pick for you. - Issues that require the operator to deliberately misuse the API (e.g. passing trusted but malformed bytes through a path that's documented as trusted-input-only).
- Performance / DoS issues that don't violate a stated bound — samkhya's contract is correctness, not real-time performance. A bench regression is not a vulnerability.
The envelope's ceiling is the guarantee everything else rests on: a correction clamped under it cannot regress a plan. A ceiling that can fall below the true cardinality is therefore a correctness defect in a safety guarantee, and is treated with the same seriousness as a memory-safety bug, even though it is not exploitable in the usual sense.
One such defect shipped. Through v1.1, LpJoinBound, AgmBound, and
ChainBound all returned ceilings below the true join cardinality on common
shapes — 2,179 violations in 3,704 measured bound-evaluations. It is fixed in
1.2.0 and documented in full, including how the measurement harness masked it,
in bench-results/20_bound_soundness.md. No advisory was filed: the defect
causes underestimation and bad plans, not memory unsafety, information
disclosure, or denial of service, and no CVE class fits it.
The standing invariants:
- Every bound exposed through
UpperBoundmust be sound for bag semantics — the semantics engines actually execute — for every database instance consistent with the statistics it was handed. - A bound that cannot be derived soundly from its inputs must return the Cartesian product, not a tighter guess. Given only row counts and which pairs of relations are joined, the product is the only sound answer.
- Every degree statistic feeding
samkhya-core::degreemust be an over -estimate. Constructors either derive that guarantee (from_distinct,from_count_min) or state the obligation on the caller (from_upper_bound).CountMinSketch::max_frequency_boundreturnsNonerather than an unsound number when its counters have saturated. - Soundness is tested absolutely, not relatively.
samkhya-core/tests/soundness_degree.rsbuilds instances, brute-forces the true join, and assertsceiling >= truth. Relative invariants between bounds hold perfectly well for a family that is wrong together, which is exactly what happened.
A reported ceiling that lands below a demonstrable true cardinality is a valid report under this policy and should be sent through the GHSA channel above.
Every from_bytes constructor performs structural-invariant
validation after deserialisation, not just byte-length checks. The
current invariant set:
- HllSketch — rejects 16-byte all-zero payloads; validates the register width and precision header before allocating the register array.
- EquiDepthHistogram — rejects 4 MiB all-zero payloads; validates bucket-count monotonicity and bin-edge ordering before accepting the histogram.
- BloomFilter — rejects byte vectors whose length does not match
ceil(num_bits / 8); validatesnum_hashes > 0and that the bit layout matches the storednum_bits. - CountMinSketch — validates
depth × width == counter array lengthand rejects depth-zero or width-zero payloads. - CorrelatedHistogram2D — validates the 2-D bucket grid against the declared row × column counts; rejects mismatched bin-edge arrays.
These checks were tightened in the path from v0.4 to v1.0 — the goal is that no attacker-supplied byte sequence reaches the sketch internals without first being structurally validated.
The BloomFilter::try_new constructor caps requested sizing at
MAX_NUM_BITS = 2^33 bits (~1 GiB) and returns a typed error rather
than allocating. The pre-cap formula num_bits = -n · ln(fp) / ln(2)^2
is unbounded as fp_rate → 0: an attacker-controlled fp_rate ≈ 0 on
the previous API could drive multi-EiB allocation attempts. The cap
fails closed (returns Err) rather than silently clamping, so callers
get a clear signal that their parameters are out of range.
samkhya ships 7 cargo-fuzz targets that exercise every adversarial -input surface:
puffin_reader— Puffin sidecar parsersketch_decoder— generic sketch payload dispatchfuzz_hll_parse—HllSketch::from_bytesfuzz_bloom_parse—BloomFilter::from_bytesfuzz_cms_parse—CountMinSketch::from_bytesfuzz_equidepth_parse—EquiDepthHistogram::from_bytesfuzz_correlated_parse—CorrelatedHistogram2D::from_bytes
Per the H01 fortress run (bench-results/H01_samkhya_core_fortress.md): 60 s × 7 targets = ~31.4 M total executions, 0 crashes, 0 OOMs. This is the floor, not the ceiling.
Automation status. The current GitHub Actions workflow does not schedule
fuzzing. Maintainers run every target locally for at least 60 s before a
release tag and retain the campaign receipts under bench-results/. A newly
discovered crash artefact is a release blocker. Scheduled fuzzing remains a
planned hardening step, not a property of the current CI configuration.
Per B11 (bench-results/B11_sanitizer.md):
- ASAN — clean on all
samkhya-corelib tests. - MIRI — green on the deterministic test subset (sketch codecs, Puffin reader, LpBound construction).
- LSan — clean (no leaks in lib tests).
- MSan — green on the subset that builds with MSan-instrumented std.
Automation status. These sanitizer campaigns are run locally during release hardening; they are not part of the current scheduled CI. A sanitizer regression found during that process is a release blocker. A future nightly workflow should make this gate continuous and publish its artefacts.
samkhya's safety story is built around the LpBound clamp: every corrector output is bounded above by a provable ceiling derived from sketch-level statistics, and that ceiling is what gates regression behavior. Two operator-side validation points:
- Validate the clamped ceiling against your own SLAs. The default
Correctorclamp usesLpJoinBound(shipped v1.0; the coarseProductBound/AgmBound/ChainBoundtriple remains the solver-failure fallback). Operators running samkhya in production should benchmark the clamped output on a representative query mix and confirm the worst-case row-count estimate is within their planner's SLA. samkhya provides the mechanism; the operator's workload defines the threshold. - Re-run the bench harness after every minor upgrade.
cargo run -p samkhya-bench --release -- compare --suite syntheticis the floor; operators should add their own engine-specific suite. A minor-version bump that holds the API surface stable may still change the numeric behavior of the corrector if a sketch precision or bound-construction detail moves. - Choose your corrector backend. GBT default is the safe production
choice. TabPFN-2.5 requires
TABPFN_TOKENlicense acceptance and is an opt-in research evaluator. The LLM-HTTP backend is also opt-in and must be pointed at an operator-controlled endpoint.
Reporters who follow this policy in good faith will be credited in the release notes and the published advisory, unless they request anonymity.
License: Apache-2.0. Sole author: Prateek Singh.