Skip to content

fix: escape backslashes and control chars in PostgreSQL config values - #10515

Merged
mnencia merged 5 commits into
mainfrom
dev/10506
Apr 29, 2026
Merged

mnencia merged 5 commits into
mainfrom
dev/10506

Conversation

@armru

@armru armru commented Apr 22, 2026 •

Copy link
Copy Markdown
Member

Values written into custom.conf were only escaped for single quotes. Backslashes and control characters (LF, CR, TAB, BS, FF) passed through unescaped, which either corrupted the configuration (e.g. a user-supplied recovery_target_name containing ' or a newline) or silently mangled the runtime value (a \ in log_line_prefix stripped by PostgreSQL's config lexer).

Introduce configfile.RenderPostgresConfiguration as the single canonical helper that emits a postgresql.conf-style fragment from a map[string]string, with values escaped according to the guc-file.l rules (, ', \n, \r, \t, \b, \f). Migrate every config-file-emitting site to it: pkg/configfile.UpdateConfigurationContents (replacing pq.QuoteLiteral, which emits an E'...' SQL-style literal that the config lexer rejects when the value contains a backslash), pkg/postgres.CreatePostgresqlConfFile, api/v1.(*RecoveryTarget).BuildPostgresOptions (previously did no escaping at all for recovery_target_name/_xid/_lsn/_timeline/_time), and pkg/management/postgres.getRestoreWalConfig and RestoreSnapshot's restore_command construction. After this change the inner escapePostgresConfLiteral is private to pkg/configfile.

Upgrade impact: clusters whose PostgreSQL parameters contain \ or a control character (most commonly a literal tab in log_line_prefix) will see one pg_reload_conf() on the first reconcile after upgrade because the config-file SHA256 changes. No restart, no connection drops. Clusters with only default or quote-only special values are unaffected.

Related #10518
Closes #10506

Reported-by: Koda Reef [email protected]

@armru
armru requested a review from a team April 22, 2026 14:11
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Apr 22, 2026
@cnpg-bot cnpg-bot added backport-requested ◀️ This pull request should be backported to all supported releases release-1.25 release-1.28 labels Apr 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

❗ By default, the pull request is configured to backport to all release branches.

  • To stop backporting this pr, remove the label: backport-requested ◀️ or add the label 'do not backport'
  • To stop backporting this pr to a certain release branch, remove the specific branch label: release-x.y

@dosubot dosubot Bot added the bug 🐛 Something isn't working label Apr 22, 2026
@armru
armru force-pushed the dev/10506 branch 2 times, most recently from 4af629d to e76484e Compare April 22, 2026 14:32
@armru

armru commented Apr 22, 2026

Copy link
Copy Markdown
Member Author

/test

@github-actions

Copy link
Copy Markdown
Contributor

@cnpg-bot cnpg-bot added the ok to merge 👌 This PR can be merged label Apr 22, 2026
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Apr 24, 2026
@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Apr 27, 2026
@leonardoce
leonardoce self-requested a review April 27, 2026 07:52
@mnencia mnencia changed the title fix: escape backslashes and control chars in PostgreSQL config values fix: harden postgresql.conf and restore_command emission Apr 27, 2026
@mnencia

mnencia commented Apr 27, 2026

Copy link
Copy Markdown
Member

/test

@github-actions

Copy link
Copy Markdown
Contributor

armru added 3 commits April 28, 2026 15:54
Values written into custom.conf were only escaped for single quotes.
Backslashes and control characters (LF, CR, TAB, BS, FF) passed through
unescaped, which either corrupted the configuration (e.g. a user-supplied
recovery_target_name containing ' or a newline) or silently mangled the
runtime value (a \ in log_line_prefix stripped by PostgreSQL's config
lexer).

Introduce configfile.EscapePostgresConfLiteral as the single canonical
escape helper and use it from:
  - pkg/configfile.UpdateConfigurationContents (replacing pq.QuoteLiteral,
    which emits an E'...' SQL-style literal that the config lexer rejects
    when the value contains a backslash)
  - pkg/postgres.escapePostgresConfValue
  - api/v1.(*RecoveryTarget).BuildPostgresOptions (previously did no
    escaping at all for recovery_target_name/_xid/_lsn/_timeline/_time)
  - pkg/management/postgres.getRestoreWalConfig and RestoreSnapshot's
    restore_command construction

Upgrade impact: clusters whose PostgreSQL parameters contain \ or a
control character (most commonly a literal tab in log_line_prefix) will
see one pg_reload_conf() on the first reconcile after upgrade because
the config-file SHA256 changes. No restart, no connection drops.
Clusters with only default or quote-only special values are unaffected.

Closes #10506

Signed-off-by: Armando Ruocco <[email protected]>
…iteral

Replace the hand-rolled byte-by-byte switch with a pre-computed
strings.NewReplacer. Same output, half the lines, and the replacer is
allocated once at package init and reused on every call.

Signed-off-by: Armando Ruocco <[email protected]>
…the configfile parser

Four sites in the codebase built postgresql.conf-style lines by hand and
called configfile.EscapePostgresConfLiteral directly: BuildPostgresOptions,
CreatePostgresqlConfFile, RestoreSnapshot's restoreCmd assembly, and
getRestoreWalConfig. Repeating the escape rule at every site is exactly the
foot-gun #10506 was about — a new call site easily forgets the escape.

Introduce configfile.RenderPostgresConfiguration, a thin wrapper over the
existing UpdateConfigurationContents parser that renders a map[string]string
as a PostgreSQL config fragment. Migrate the four call sites to build a map
and delegate. After this change, EscapePostgresConfLiteral is only invoked
from within pkg/configfile; everyone else stays above that abstraction.

Two legacy on-disk invariants are preserved deliberately to keep the bytes
of existing postgresql.conf / custom.conf files stable on operator upgrade:

  - cnpg.config_sha256 is rendered as its own last line, not sorted in with
    the other cnpg.* keys. Clusters running synchronous replication already
    have cnpg.synchronous_standby_names_metadata in the cnpg section;
    reordering relative to the sha256 line would rewrite every such
    cluster's postgresql.conf on upgrade and trigger a pg_reload_conf().
  - recovery_target and recovery_target_inclusive are still emitted as
    barewords (`= immediate`, `= true`/`= false`) rather than quoted.
    PostgreSQL parses both forms identically, but preserving the bareword
    form keeps custom.conf byte-stable during recovery bootstrap. These
    values come from a fixed enum, so the escape pass is unnecessary.

A new byte-exact test in pkg/postgres/configuration_test.go pins the
CreatePostgresqlConfFile layout (sorted non-cnpg keys, then other cnpg.*
keys, then cnpg.config_sha256 always last). Every prior assertion on this
function used ContainSubstring, so layout shifts were invisible to the
test suite; the new test makes them visible.

Signed-off-by: Armando Ruocco <[email protected]>
@mnencia mnencia changed the title fix: harden postgresql.conf and restore_command emission fix: escape backslashes and control chars in PostgreSQL config values Apr 28, 2026
@sxd

sxd commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

@mnencia @armru @leonardoce shouldn't we try to have some kind of validation also at OpenAPI level?

@mnencia

mnencia commented Apr 29, 2026

Copy link
Copy Markdown
Member

@sxd #10565 already does this for the recovery target fields: admission-time validation on targetName (rejects ASCII control chars, enforces MAXFNAMELEN) and targetXID (non-negative 32-bit int). It's webhook validation rather than OpenAPI annotations, but it runs at admission time too. Let's move the discussion there.

@mnencia
mnencia merged commit d65da47 into main Apr 29, 2026
43 checks passed
@mnencia
mnencia deleted the dev/10506 branch April 29, 2026 17:48
cnpg-bot pushed a commit that referenced this pull request Apr 29, 2026
…#10515)

Values written into custom.conf were only escaped for single quotes.
Backslashes and control characters (LF, CR, TAB, BS, FF) passed through
unescaped, which either corrupted the configuration (e.g. a
user-supplied recovery_target_name containing ' or a newline) or
silently mangled the runtime value (a \ in log_line_prefix stripped by
PostgreSQL's config lexer).

Introduce configfile.RenderPostgresConfiguration as the single canonical
helper that emits a postgresql.conf-style fragment from a
map[string]string, with values escaped according to the guc-file.l rules
(\, ', \n, \r, \t, \b, \f). Migrate every config-file-emitting site to
it: pkg/configfile.UpdateConfigurationContents (replacing
pq.QuoteLiteral, which emits an E'...' SQL-style literal that the config
lexer rejects when the value contains a backslash),
pkg/postgres.CreatePostgresqlConfFile,
api/v1.(*RecoveryTarget).BuildPostgresOptions (previously did no
escaping at all for recovery_target_name/_xid/_lsn/_timeline/_time), and
pkg/management/postgres.getRestoreWalConfig and RestoreSnapshot's
restore_command construction. After this change the inner
escapePostgresConfLiteral is private to pkg/configfile.

Upgrade impact: clusters whose PostgreSQL parameters contain \ or a
control character (most commonly a literal tab in log_line_prefix) will
see one pg_reload_conf() on the first reconcile after upgrade because
the config-file SHA256 changes. No restart, no connection drops.
Clusters with only default or quote-only special values are unaffected.

Related #10518
Closes #10506

Reported-by: Koda Reef <[email protected]>
Signed-off-by: Armando Ruocco <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
Signed-off-by: Niccolò Fei <[email protected]>
Co-authored-by: Marco Nenciarini <[email protected]>
Co-authored-by: Niccolò Fei <[email protected]>
(cherry picked from commit d65da47)
mnencia added a commit that referenced this pull request Apr 30, 2026
…#10515)

Values written into custom.conf were only escaped for single quotes.
Backslashes and control characters (LF, CR, TAB, BS, FF) passed through
unescaped, which either corrupted the configuration (e.g. a
user-supplied recovery_target_name containing ' or a newline) or
silently mangled the runtime value (a \ in log_line_prefix stripped by
PostgreSQL's config lexer).

Introduce configfile.RenderPostgresConfiguration as the single canonical
helper that emits a postgresql.conf-style fragment from a
map[string]string, with values escaped according to the guc-file.l rules
(\, ', \n, \r, \t, \b, \f). Migrate every config-file-emitting site to
it: pkg/configfile.UpdateConfigurationContents (replacing
pq.QuoteLiteral, which emits an E'...' SQL-style literal that the config
lexer rejects when the value contains a backslash),
pkg/postgres.CreatePostgresqlConfFile,
api/v1.(*RecoveryTarget).BuildPostgresOptions (previously did no
escaping at all for recovery_target_name/_xid/_lsn/_timeline/_time), and
pkg/management/postgres.getRestoreWalConfig and RestoreSnapshot's
restore_command construction. After this change the inner
escapePostgresConfLiteral is private to pkg/configfile.

Upgrade impact: clusters whose PostgreSQL parameters contain \ or a
control character (most commonly a literal tab in log_line_prefix) will
see one pg_reload_conf() on the first reconcile after upgrade because
the config-file SHA256 changes. No restart, no connection drops.
Clusters with only default or quote-only special values are unaffected.

Related #10518
Closes #10506

Reported-by: Koda Reef <[email protected]>
Signed-off-by: Armando Ruocco <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
Signed-off-by: Niccolò Fei <[email protected]>
Co-authored-by: Marco Nenciarini <[email protected]>
Co-authored-by: Niccolò Fei <[email protected]>
(cherry picked from commit d65da47)
mnencia added a commit that referenced this pull request Apr 30, 2026
…#10515)

Values written into custom.conf were only escaped for single quotes.
Backslashes and control characters (LF, CR, TAB, BS, FF) passed through
unescaped, which either corrupted the configuration (e.g. a
user-supplied recovery_target_name containing ' or a newline) or
silently mangled the runtime value (a \ in log_line_prefix stripped by
PostgreSQL's config lexer).

Introduce configfile.RenderPostgresConfiguration as the single canonical
helper that emits a postgresql.conf-style fragment from a
map[string]string, with values escaped according to the guc-file.l rules
(\, ', \n, \r, \t, \b, \f). Migrate every config-file-emitting site to
it: pkg/configfile.UpdateConfigurationContents (replacing
pq.QuoteLiteral, which emits an E'...' SQL-style literal that the config
lexer rejects when the value contains a backslash),
pkg/postgres.CreatePostgresqlConfFile,
api/v1.(*RecoveryTarget).BuildPostgresOptions (previously did no
escaping at all for recovery_target_name/_xid/_lsn/_timeline/_time), and
pkg/management/postgres.getRestoreWalConfig and RestoreSnapshot's
restore_command construction. After this change the inner
escapePostgresConfLiteral is private to pkg/configfile.

Upgrade impact: clusters whose PostgreSQL parameters contain \ or a
control character (most commonly a literal tab in log_line_prefix) will
see one pg_reload_conf() on the first reconcile after upgrade because
the config-file SHA256 changes. No restart, no connection drops.
Clusters with only default or quote-only special values are unaffected.

Related #10518
Closes #10506

Reported-by: Koda Reef <[email protected]>
Signed-off-by: Armando Ruocco <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
Signed-off-by: Niccolò Fei <[email protected]>
Co-authored-by: Marco Nenciarini <[email protected]>
Co-authored-by: Niccolò Fei <[email protected]>
(cherry picked from commit d65da47)
mnencia added a commit that referenced this pull request Apr 30, 2026
getRestoreWalConfig joined user-controlled backup status fields
(EndpointURL, DestinationPath, ServerName) with spaces into a single
restore_command string. PostgreSQL runs restore_command via system(),
and the POSIX shell word-splits unquoted whitespace, so a
DestinationPath like "s3://my bucket/wal" would be passed to
barman-cloud-wal-restore as separate arguments.

This bug predates #10506 but was unreachable for inputs containing
single quotes or backslashes because such values corrupted the config
file first. Now that the config file is well-formed, the shell layer
needs its own quoting.

Wrap each cmd element via github.com/kballard/go-shellquote before
passing the joined value through configfile.RenderPostgresConfiguration.
The result is two layered quoting passes: shell-safe single quotes
around any argument with whitespace or shell metacharacters, and the
standard config-file literal escape applied by the helper.

Related #10515

Signed-off-by: Armando Ruocco <[email protected]>
Signed-off-by: Leonardo Cecchi <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
Co-authored-by: Leonardo Cecchi <[email protected]>
Co-authored-by: Marco Nenciarini <[email protected]>
cnpg-bot pushed a commit that referenced this pull request Apr 30, 2026
getRestoreWalConfig joined user-controlled backup status fields
(EndpointURL, DestinationPath, ServerName) with spaces into a single
restore_command string. PostgreSQL runs restore_command via system(),
and the POSIX shell word-splits unquoted whitespace, so a
DestinationPath like "s3://my bucket/wal" would be passed to
barman-cloud-wal-restore as separate arguments.

This bug predates #10506 but was unreachable for inputs containing
single quotes or backslashes because such values corrupted the config
file first. Now that the config file is well-formed, the shell layer
needs its own quoting.

Wrap each cmd element via github.com/kballard/go-shellquote before
passing the joined value through configfile.RenderPostgresConfiguration.
The result is two layered quoting passes: shell-safe single quotes
around any argument with whitespace or shell metacharacters, and the
standard config-file literal escape applied by the helper.

Related #10515

Signed-off-by: Armando Ruocco <[email protected]>
Signed-off-by: Leonardo Cecchi <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
Co-authored-by: Leonardo Cecchi <[email protected]>
Co-authored-by: Marco Nenciarini <[email protected]>
(cherry picked from commit ed882c2)
cnpg-bot pushed a commit that referenced this pull request Apr 30, 2026
getRestoreWalConfig joined user-controlled backup status fields
(EndpointURL, DestinationPath, ServerName) with spaces into a single
restore_command string. PostgreSQL runs restore_command via system(),
and the POSIX shell word-splits unquoted whitespace, so a
DestinationPath like "s3://my bucket/wal" would be passed to
barman-cloud-wal-restore as separate arguments.

This bug predates #10506 but was unreachable for inputs containing
single quotes or backslashes because such values corrupted the config
file first. Now that the config file is well-formed, the shell layer
needs its own quoting.

Wrap each cmd element via github.com/kballard/go-shellquote before
passing the joined value through configfile.RenderPostgresConfiguration.
The result is two layered quoting passes: shell-safe single quotes
around any argument with whitespace or shell metacharacters, and the
standard config-file literal escape applied by the helper.

Related #10515

Signed-off-by: Armando Ruocco <[email protected]>
Signed-off-by: Leonardo Cecchi <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
Co-authored-by: Leonardo Cecchi <[email protected]>
Co-authored-by: Marco Nenciarini <[email protected]>
(cherry picked from commit ed882c2)
mnencia added a commit that referenced this pull request Apr 30, 2026
TargetXID was previously unchecked and a malformed value surfaced
only at PostgreSQL startup. Validate it as a non-negative 32-bit
integer at admission, matching PG's TransactionId width so values
that would silently truncate to a different XID are rejected up
front.

TargetName accepts arbitrary text in pg_create_restore_point, but
ASCII control characters (NUL, newline, DEL) are never legitimate
and would either break the line-oriented config file or signal a
hostile spec. Reject them. Also enforce the 64-byte MAXFNAMELEN
limit at admission rather than letting PG fail at recovery.

The escape layers introduced in #10515 already neutralise these
values, so this is admission-time defense-in-depth: it surfaces
the same errors earlier and with a clearer message.

Signed-off-by: Marco Nenciarini <[email protected]>
mnencia added a commit that referenced this pull request May 5, 2026
getRestoreWalConfig joined user-controlled backup status fields
(EndpointURL, DestinationPath, ServerName) with spaces into a single
restore_command string. PostgreSQL runs restore_command via system(),
and the POSIX shell word-splits unquoted whitespace, so a
DestinationPath like "s3://my bucket/wal" would be passed to
barman-cloud-wal-restore as separate arguments.

This bug predates #10506 but was unreachable for inputs containing
single quotes or backslashes because such values corrupted the config
file first. Now that the config file is well-formed, the shell layer
needs its own quoting.

Wrap each cmd element via github.com/kballard/go-shellquote before
passing the joined value through configfile.RenderPostgresConfiguration.
The result is two layered quoting passes: shell-safe single quotes
around any argument with whitespace or shell metacharacters, and the
standard config-file literal escape applied by the helper.

Related #10515

Signed-off-by: Armando Ruocco <[email protected]>
Signed-off-by: Leonardo Cecchi <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
Co-authored-by: Leonardo Cecchi <[email protected]>
Co-authored-by: Marco Nenciarini <[email protected]>
(cherry picked from commit ed882c2)
mnencia added a commit that referenced this pull request May 6, 2026
TargetXID was previously unchecked and a malformed value surfaced
only at PostgreSQL startup. Validate it as a non-negative 32-bit
integer at admission, matching PG's TransactionId width so values
that would silently truncate to a different XID are rejected up
front.

TargetName accepts arbitrary text in pg_create_restore_point, but
ASCII control characters (NUL, newline, DEL) are never legitimate
and would either break the line-oriented config file or signal a
hostile spec. Reject them. Also enforce the 64-byte MAXFNAMELEN
limit at admission rather than letting PG fail at recovery.

The escape layers introduced in #10515 already neutralise these
values, so this is admission-time defense-in-depth: it surfaces
the same errors earlier and with a clearer message.

Signed-off-by: Marco Nenciarini <[email protected]>
mnencia added a commit that referenced this pull request May 7, 2026
TargetXID was previously unchecked and a malformed value surfaced only
at PostgreSQL startup. Validate it as a non-negative 32-bit integer at
admission, matching PG's TransactionId width so values that would
silently truncate to a different XID are rejected up front.

TargetName accepts arbitrary text in pg_create_restore_point, but ASCII
control characters (NUL, newline, DEL) are never legitimate and would
either break the line-oriented config file or signal a hostile spec.
Reject them. Also enforce the 64-byte MAXFNAMELEN limit at admission
rather than letting PG fail at recovery.

Related #10515

Suggested-by: Koda Reef <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
cnpg-bot pushed a commit that referenced this pull request May 7, 2026
TargetXID was previously unchecked and a malformed value surfaced only
at PostgreSQL startup. Validate it as a non-negative 32-bit integer at
admission, matching PG's TransactionId width so values that would
silently truncate to a different XID are rejected up front.

TargetName accepts arbitrary text in pg_create_restore_point, but ASCII
control characters (NUL, newline, DEL) are never legitimate and would
either break the line-oriented config file or signal a hostile spec.
Reject them. Also enforce the 64-byte MAXFNAMELEN limit at admission
rather than letting PG fail at recovery.

Related #10515

Suggested-by: Koda Reef <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
(cherry picked from commit caf2077)
cnpg-bot pushed a commit that referenced this pull request May 7, 2026
TargetXID was previously unchecked and a malformed value surfaced only
at PostgreSQL startup. Validate it as a non-negative 32-bit integer at
admission, matching PG's TransactionId width so values that would
silently truncate to a different XID are rejected up front.

TargetName accepts arbitrary text in pg_create_restore_point, but ASCII
control characters (NUL, newline, DEL) are never legitimate and would
either break the line-oriented config file or signal a hostile spec.
Reject them. Also enforce the 64-byte MAXFNAMELEN limit at admission
rather than letting PG fail at recovery.

Related #10515

Suggested-by: Koda Reef <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
(cherry picked from commit caf2077)
cnpg-bot pushed a commit that referenced this pull request May 7, 2026
TargetXID was previously unchecked and a malformed value surfaced only
at PostgreSQL startup. Validate it as a non-negative 32-bit integer at
admission, matching PG's TransactionId width so values that would
silently truncate to a different XID are rejected up front.

TargetName accepts arbitrary text in pg_create_restore_point, but ASCII
control characters (NUL, newline, DEL) are never legitimate and would
either break the line-oriented config file or signal a hostile spec.
Reject them. Also enforce the 64-byte MAXFNAMELEN limit at admission
rather than letting PG fail at recovery.

Related #10515

Suggested-by: Koda Reef <[email protected]>
Signed-off-by: Marco Nenciarini <[email protected]>
(cherry picked from commit caf2077)
sdwilsh pushed a commit to sdwilsh/ansible-playbooks that referenced this pull request May 11, 2026
##### [\`v1.29.1\`](https://github.com/cloudnative-pg/cloudnative-pg/releases/tag/v1.29.1)

**Release date:** May 8, 2026

##### Security and Supply Chain

- **`CVE-2026-44477` / `GHSA-423p-g724-fr39`: metrics exporter privilege escalation**: the metrics exporter no longer authenticates as the `postgres` superuser. It now uses a dedicated `cnpg_metrics_exporter` role with `pg_monitor` privileges only, closing a chain that let a low-privilege database user gain PostgreSQL superuser. ([`GHSA-423p-g724-fr39`](GHSA-423p-g724-fr39)) <!-- 1.29 1.28 1.25 -->

  Upgrade impact: custom monitoring queries that read user-owned tables, or use `target_databases: '*'` against databases where `PUBLIC CONNECT` has been revoked, need explicit `GRANT` statements to `cnpg_metrics_exporter`. See ["Custom query privileges and safety"](../monitoring.md#custom-query-privileges-and-safety) and ["Manually creating the metrics exporter role"](../monitoring.md#manually-creating-the-metrics-exporter-role) in the monitoring documentation.

  For replica clusters, upgrade the source primary cluster before any replica clusters that consume from it. The `cnpg_metrics_exporter` role is created on the source primary and replicates downstream; a replica cluster upgraded first will scrape against a missing role until the source primary upgrades. The manual-recovery section linked above also covers replica clusters.

- **Schema-qualified catalog references in default monitoring queries**: hardened the shipped monitoring configuration and documentation samples by qualifying every `pg_catalog` object explicitly. Unqualified references resolve through `search_path`, which a database user can manipulate to shadow built-in objects. ([#10576](cloudnative-pg/cloudnative-pg#10576)) <!-- 1.29 1.28 1.25 -->

- **Discoverable SBOM and provenance attestations**: SBOM and SLSA provenance attached to operator container images now follow the OCI 1.1 Referrers spec, so standard registry tooling and supply-chain scanners can discover them automatically. ([#10601](cloudnative-pg/cloudnative-pg#10601)) <!-- 1.29 1.28 1.25 -->

- **CVE remediation in `github.com/jackc/pgx/v5`**: bumped to v5.9.2 to pick up upstream fixes for `CVE-2026-33816` (memory-safety in `pgproto3`) and `GHSA-j88v-2chj-qfwx` (SQL injection via simple-protocol dollar-quoted string handling). ([#10437](cloudnative-pg/cloudnative-pg#10437), [#10499](cloudnative-pg/cloudnative-pg#10499))

- **CVE remediation in the Go runtime**: built with Go 1.26.3 to pick up upstream fixes in `crypto/x509`, `crypto/tls`, `net/http`, and `net` (CVE-2026-32280, CVE-2026-32281, CVE-2026-33810, CVE-2026-33814, CVE-2026-33811, CVE-2026-39825). ([#10463](cloudnative-pg/cloudnative-pg#10463), [#10647](cloudnative-pg/cloudnative-pg#10647)) <!-- 1.29 1.28 1.25 -->

- **Build pipeline hardening**: the Go 1.26.3 bump also addresses CVE-2026-42501 (`cmd/go` module-checksum validation), reducing supply-chain exposure during release builds. The affected code paths are not reachable from the running operator. ([#10647](cloudnative-pg/cloudnative-pg#10647)) <!-- 1.29 1.28 1.25 -->

##### Changes

- Switched TLS peer verification from `VerifyPeerCertificate` to `VerifyConnection`, which runs on every completed handshake (the former is skipped on resumed TLS 1.3 sessions). Session resumption is not enabled in CloudNativePG today, so this has no observable effect, but it future-proofs verification if session caching is introduced later. ([#10478](cloudnative-pg/cloudnative-pg#10478)) <!-- 1.29 1.28 1.25 -->

##### Fixes

- Fixed a failover window where the former primary kept its primary label. If it returned during failover (for example, after a transient network partition), the `-rw` service kept routing to it, replicas could reconnect, and committed writes were lost to `pg_rewind`. The old primary is now labeled `unhealthy` to isolate it from service traffic during failover. ([#10409](cloudnative-pg/cloudnative-pg#10409)) <!-- 1.29 1.28 1.25 -->

- Fixed failover not being triggered when the node hosting the primary becomes unreachable. The operator now reads the pod's `Ready` condition (flipped to `False` by the node controller when the kubelet stops reporting) instead of `ContainersReady`, which stays stale as `True` in that scenario. Combined with the spurious-failover guard ([#10445](cloudnative-pg/cloudnative-pg#10445)), failover triggers only when Kubernetes itself marks the pod not Ready. ([#10448](cloudnative-pg/cloudnative-pg#10448)) <!-- 1.29 1.28 1.25 -->

- Fixed spurious failovers caused by transient failures on the primary's HTTP status endpoint. ([#10445](cloudnative-pg/cloudnative-pg#10445)) <!-- 1.29 1.28 1.25 -->

- Fixed escaping of backslashes and control characters in PostgreSQL configuration values. Previously, such characters in parameters like `log_line_prefix` could corrupt the configuration file or be silently stripped at runtime. ([#10515](cloudnative-pg/cloudnative-pg#10515)) <!-- 1.29 1.28 1.25 -->

- Fixed `restore_command` construction to shell-quote each argument. Values such as a `destinationPath` containing whitespace (for example, `s3://my bucket/wal`) were word-split by the POSIX shell and passed to the WAL restore tool as separate arguments. ([#10518](cloudnative-pg/cloudnative-pg#10518)) <!-- 1.29 1.28 1.25 -->

- Tightened `recoveryTarget` validation in the admission webhook: `targetXID` must now be a non-negative 32-bit integer, and `targetName` must be shorter than 64 bytes and free of ASCII control characters. Malformed values are rejected at admission instead of failing later during PostgreSQL recovery. ([#10565](cloudnative-pg/cloudnative-pg#10565)) <!-- 1.29 1.28 1.25 -->

- Fixed snapshot restores failing when leftover `pgsql_tmp*` directories were present in the data directory. ([#10447](cloudnative-pg/cloudnative-pg#10447)) <!-- 1.29 1.28 1.25 -->

- Fixed a deadlock occurring when PVC storage size and resource requests are changed simultaneously. ([#10427](cloudnative-pg/cloudnative-pg#10427)) <!-- 1.29 1.28 1.25 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-requested ◀️ This pull request should be backported to all supported releases bug 🐛 Something isn't working lgtm This PR has been approved by a maintainer ok to merge 👌 This PR can be merged release-1.28 size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Some strings aren't being escaped properly

6 participants