-
Notifications
You must be signed in to change notification settings - Fork 0
fix(config): separate provisioning and require secret encryption #193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
67 commits
Select commit
Hold shift + click to select a range
395afb9
test(config): expose implicit runtime provisioning authority
seonghobae bcae84c
ci: repair runtime store provisioning boundary
seonghobae f9734f3
build: stage runtime provisioning repair helper
seonghobae b509cc4
ci: simplify runtime provisioning repair runner
seonghobae 0b7fc49
ci: remove self-modifying runtime repair writer
seonghobae 2a56dc2
chore: remove one-shot runtime repair helper
seonghobae 7c152cd
ci: add read-only runtime provisioning probe
seonghobae 12262f5
ci: add deterministic runtime provisioning candidate generator
seonghobae 3655519
ci: repair read-only runtime provisioning probe
seonghobae 71a5547
ci: preserve read-only setup seams in runtime candidate
seonghobae cc1dd88
ci: bound schema diagnostics without exception context
seonghobae 7882883
fix(config): remove runtime schema provisioning authority
seonghobae 01a7ae2
fix(config): seed runtime defaults at schema boundary
seonghobae 237e578
ci: finalize verified runtime provisioning repair
seonghobae e639f48
fix(config): finalize least-privilege runtime stores
seonghobae 0c88d83
test(config): reject selectable incompatible runtime relations
seonghobae 472e738
fix(config): validate provisioned relation kind and column types
seonghobae e2b5fad
test(config): model provisioned runtime catalog metadata
seonghobae 5b86445
docs(config): define catalog compatibility and runtime role boundary
seonghobae 384cc25
test(config): prove runtime read privilege boundary
seonghobae ab81331
fix(config): fail closed on runtime read privilege gaps
seonghobae 33f231e
test(config): model runtime catalog privilege evidence
seonghobae 45f102e
docs(config): bound search-path trust claim
seonghobae afaead9
test(config): assert structured ConfigError contract
seonghobae 37174e6
test(security): require encrypted secret storage by default
seonghobae 7a29783
fix(security): require secret encryption by default
seonghobae f5369b2
test(security): make insecure secret storage opt-in
seonghobae 6e7564a
test(security): preserve local lifecycle coverage explicitly
seonghobae 16574b6
test(security): make integration obfuscation explicit
seonghobae 995c33f
docs(security): document encrypted secret-store default
seonghobae f2d3088
test(config): isolate provisioning boundary from encryption policy
seonghobae 4d0f317
test(config): prove malformed Fernet keys fail before database access
seonghobae 9c4028e
fix(config): validate Fernet key before database access
seonghobae 40a9abb
test(integration): avoid persisting unused plaintext secret
seonghobae 437a109
test(config): require writable-key schema compatibility
seonghobae 9226e87
fix(config): verify unique runtime storage keys
seonghobae c618bed
test(config): model unique runtime storage keys
seonghobae a5fc816
test(config): complete unique-key compatibility harness
seonghobae 42be189
docs(doctoring): record unique-key runtime invariant
seonghobae 7c390a8
test(config): reject include-only storage key indexes
seonghobae ca6a289
fix(config): ignore INCLUDE payload columns for uniqueness
seonghobae 1909321
docs(config): clarify unique key versus INCLUDE payload
seonghobae 3ea0ffb
test(config): reject deferrable conflict arbiters
seonghobae 4d41735
fix(config): exclude deferrable conflict arbiters
seonghobae 33ca438
docs(config): record conflict-arbiter constraint boundary
seonghobae 38eaf9c
test(config): fail closed on corrupt stored secrets
seonghobae 1471ce9
fix(config): reject corrupt stored secret encodings
seonghobae 7d6c6f8
test(config): reject coerced persisted encryption flags
seonghobae c6ab8bc
fix(config): preserve persisted encryption flag type
seonghobae 762a72e
test(config): require encrypted legacy-secret readiness
seonghobae 2669838
fix(config): reject unencrypted legacy rows in required mode
seonghobae 1c127d9
test(config): model encrypted-row readiness in fake store
seonghobae cfd8578
test(config): reject late unencrypted rows in required mode
seonghobae f7e7904
fix(config): enforce required encryption on reads
seonghobae da19e1f
test(config): initialize decode-boundary policy state
seonghobae 003183a
test(secrets): reject unencrypted compatibility mode
seonghobae 2f56333
fix(secrets): make Fernet storage mandatory
seonghobae 085fd6a
test(secrets): require encrypted store fixtures
seonghobae 0dec24b
test(secrets): preserve connection cleanup under mandatory encryption
seonghobae e8258db
test(secrets): reject legacy plaintext decode paths
seonghobae a8e6288
fix(secrets): preserve exact encryption-flag validation
seonghobae c3c6559
docs(secrets): remove insecure compatibility guidance
seonghobae 0697d57
test(secrets): exercise runtime probes with encryption enabled
seonghobae 4f9473c
test(secrets): keep index probes behind valid encryption
seonghobae a3f16fb
test(config): reject Fernet key subclasses
seonghobae 7d04484
fix(config): require exact Fernet key text
seonghobae 7f421f4
Merge branch 'main' into fix/runtime-store-provisioning-d0a4b30
opencode-agent[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| # Runtime store provisioning boundary | ||
|
|
||
| ## Operator action | ||
|
|
||
| Provision or migrate `com_config` and `com_secrets` with the explicit package schema before starting an ordinary application process: | ||
|
|
||
| ```bash | ||
| python -m pg_llm_batch init-db | ||
| ``` | ||
|
|
||
| After provisioning, the normal application role needs schema `USAGE` plus only the table privileges required by its configured read/write operations. It does not need schema `CREATE` merely to construct `PostgresConfigStore` or `SecretStore`. Production application roles must be `NOSUPERUSER NOBYPASSRLS` so administrative privilege cannot silently bypass row-security boundaries owned elsewhere in the package schema. | ||
|
|
||
| Runtime constructors issue bounded read-only `pg_catalog` capability probes. The search-path-resolved relation must be an ordinary base table, every required column must have the expected PostgreSQL data type, and the current role must have schema `USAGE` plus table `SELECT`. The configured storage key (`config_key` or `secret_key`) must also be backed by a valid, ready, non-partial, non-expression unique index with exactly one key column so the runtime `ON CONFLICT (key)` write contract has a compatible arbiter before application writes begin. If that index backs a PostgreSQL constraint, the constraint must be `NOT DEFERRABLE`; PostgreSQL does not permit a `DEFERRABLE` constraint to act as an `ON CONFLICT` arbiter. A storage key that appears only as an `INCLUDE` payload column does not satisfy this boundary because PostgreSQL explicitly excludes included columns from uniqueness enforcement. Missing tables, missing columns, selectable views, type mismatches, insufficient read privileges, missing or deferrable unique-key authority, or other incompatible relations fail closed with fixed package diagnostics; database exception text, DSNs, SQL values, and credentials are not retained in exported exception context. Connections acquired before a failed probe are closed deterministically. | ||
|
|
||
| Built-in configuration rows are seeded only at the explicit schema boundary with `ON CONFLICT (config_key) DO NOTHING`. Existing operator values therefore survive provisioning replay and upgrades. The packaged schema and deployable PostgreSQL initialization schema remain byte-identical. | ||
|
|
||
| ## Secret encryption policy | ||
|
|
||
| `SecretStore` requires Fernet encryption for every runtime construction and every newly persisted secret. A missing Fernet key, malformed key, unavailable cryptography dependency, or explicit `require_encryption=False` request fails with a fixed `ConfigError` before PostgreSQL connection acquisition. The historical `require_encryption` keyword is retained only so existing callers that explicitly pass `True` do not break at the call boundary; it is not a policy switch and cannot re-enable reversible Base64 persistence. | ||
|
|
||
| Runtime secret writes produce Fernet ciphertext only. Runtime reads reject any durable row whose encryption flag is not the exact boolean `TRUE`; the runtime no longer Base64-decodes historical unencrypted values. Startup also positively proves that `com_secrets` contains no `is_encrypted IS NOT TRUE` row before accepting the store. A deployment with historical unencrypted rows must therefore inventory and migrate them through a separately reviewed, atomic, recoverable migration before starting the hardened runtime. Do not convert those rows in-place without a verified backup, rollback procedure, key-custody plan, and post-migration proof that no unencrypted rows remain. | ||
|
|
||
| This boundary is intentionally narrower than the complete secret lifecycle tracked separately. It does not itself perform the historical-row migration, rotate keys, retire decrypt-only keys, prove key custody, define retention, or convert successful encryption into a SOC 2, CSAP, or NIST certification claim. | ||
|
|
||
| ## Security rationale | ||
|
|
||
| PostgreSQL grants object creation through a schema's `CREATE` privilege; ordinary access requires separate schema `USAGE` and object privileges. Removing implicit runtime DDL permits a least-privilege application role. The runtime probe validates the relation resolved through the active `search_path`; it does not establish that relation's schema ownership. Operators must therefore use a trusted search path and prevent untrusted roles from creating objects in schemas searched ahead of the provisioned package relation. PostgreSQL also states that `CREATE TABLE IF NOT EXISTS` does not prove that an existing relation has the expected structure, so an implicit create statement is not a schema-compatibility check. Runtime compatibility therefore derives from bounded read-only catalog and privilege metadata for the resolved relation rather than from successful execution against an arbitrary selectable relation. | ||
|
|
||
| PostgreSQL implements uniqueness through unique indexes and resolves `INSERT ... ON CONFLICT` using unique-index inference. `pg_index.indisunique`, `indisvalid`, and `indisready` distinguish indexes that are unique, valid, and ready for inserts; `indnkeyatts` distinguishes key columns from included attributes, while `indpred` and `indexprs` identify partial and expression indexes. PostgreSQL's `CREATE INDEX` contract further specifies that `INCLUDE` columns are non-key payload and are disregarded for uniqueness or exclusion constraints. PostgreSQL also limits `ON CONFLICT` arbiters to non-deferrable uniqueness authority: when a qualifying index backs a constraint, the probe correlates `pg_constraint.conindid` with the index and rejects `condeferrable = true`. The runtime compatibility probe therefore inspects only the first `indnkeyatts` entries of `indkey` when matching the storage key and rejects both a readable lookalike table whose storage key is merely included behind some other unique key and one whose only matching uniqueness is deferrable. Neither shape can satisfy the package's simple-column `ON CONFLICT (config_key)` or `ON CONFLICT (secret_key)` write contract. | ||
|
|
||
| Fernet provides authenticated symmetric encryption for stored secret values when a valid key is configured, but the cryptographic primitive does not by itself define deployment authorization, key custody, rotation, recovery, retention, or audit policy. Mandatory runtime encryption removes the previous insecure compatibility path instead of treating reversible obfuscation as an acceptable environment-specific fallback. | ||
|
|
||
| ## Rollback and recovery | ||
|
|
||
| Rollback of unrelated runtime-store provisioning changes must preserve the deployment's Fernet key and mandatory encryption posture. Older package versions that permit Base64 fallback reopen the at-rest confidentiality defect and are not a security-equivalent rollback target. Before any rollback across this security boundary, verify the target version's secret-store behavior, retain encrypted database backups and key-recovery material under the deployment's custody controls, and prove that rollback will not create or decode unencrypted secret rows. | ||
|
|
||
| A failed runtime schema probe is repaired by applying the reviewed schema with a provisioning identity and retrying with the ordinary runtime identity. A failed encryption-readiness probe is repaired by the separately reviewed historical-row migration, not by disabling encryption. Do not grant broad schema `CREATE`, `SUPERUSER`, or `BYPASSRLS`, add an untrusted writable schema ahead of the provisioned relation in `search_path`, or weaken secret encryption merely to silence readiness. | ||
|
|
||
| ## References | ||
|
|
||
| PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: CREATE INDEX*. https://www.postgresql.org/docs/18/sql-createindex.html | ||
|
|
||
| PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: CREATE TABLE*. https://www.postgresql.org/docs/18/sql-createtable.html | ||
|
|
||
| PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: INSERT*. https://www.postgresql.org/docs/18/sql-insert.html | ||
|
|
||
| PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: pg_constraint*. https://www.postgresql.org/docs/18/catalog-pg-constraint.html | ||
|
|
||
| PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: pg_index*. https://www.postgresql.org/docs/18/catalog-pg-index.html | ||
|
|
||
| PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: Privileges*. https://www.postgresql.org/docs/18/ddl-priv.html | ||
|
|
||
| PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation: Schemas*. https://www.postgresql.org/docs/18/ddl-schemas.html | ||
|
|
||
| The Psycopg Team. (2026). *Psycopg 3 documentation: Basic module usage*. https://www.psycopg.org/psycopg3/docs/basic/usage.html | ||
|
|
||
| The cryptography developers. (2026). *Fernet (symmetric encryption)*. https://cryptography.io/en/latest/fernet/ | ||
|
|
||
| Joint Task Force. (2025). *Security and privacy controls for information systems and organizations* (NIST Special Publication 800-53, Revision 5, Release 5.2.0). National Institute of Standards and Technology. https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This paragraph already requires Fernet and rejects
require_encryption=False, but the files operators follow on this head still disagree.On
7f421f4,CHANGELOG.mdhas no Unreleased entry for this break,README.mdstill says “optional Fernet key” and still showsSecretStore(dsn),bootstrap.py/docs/doctoring/bootstrap-dsn-precedence.mdstill call the key optional, andARCHITECTURE.mddoes not record the provisioning vs runtime split or the non-equivalent Base64 rollback.Update those documents on this branch, or take the continuation on
cursor/bc-389287fc-f6ac-4e1c-9047-5b223d434a8e-729f, before treating this head as mergeable.