feat(safegres): stack + posture presets and four more exposure adapters - #1601
Merged
Conversation
…aphile adapters Presets configure, they never delete: a rule that does not apply to a stack is demoted to info (zero weight, so the score is unchanged) rather than switched off, so it stays in the report and stays re-tunable. constructive's A3 goes off -> info under the same rule. - stack presets (constructive, postgrest, supabase, hasura, graphile) each resolve exposure from a real catalog signal and supply that stack's untrusted role vocabulary to R1/R2/R3/L5 - posture presets (multi-tenant, oltp) carry no exposure of their own and compose: extends: ["safegres:supabase", "safegres:multi-tenant"] - postgrestAdapter reads pgrst.db_schemas / pgrst.db_anon_role from pg_db_role_setting; hasuraAdapter reads tracked tables from hdb_catalog; graphileAdapter resolves the graphile-starter layout - drop the hand-written "MIT (c) Constructive" line: the shared FOOTER.md is appended at build and already carries credits, license and disclaimer
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
devin-ai-integration Bot
pushed a commit
that referenced
this pull request
Aug 2, 2026
graphileAdapter answers which schemas a graphile-starter DB serves; postgraphileAdapter answers what the served schemas expose. The former delegates reach to the latter rather than duplicating the behavior read.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Packages the plane/adapter machinery from #1599 into presets you can name, split along the axis that actually varies: a stack preset knows how your framework declares exposure and what its role names mean, a posture preset says how harshly to read what it finds. They compose, because they are just partial configs:
{ "extends": ["safegres:supabase", "safegres:multi-tenant"] }constructiverouting_public.apis→api_schemasanonymouspostgrestpgrst.db_schemasinpg_db_role_settinganonsupabaseanon,authenticatedhasurahdb_cataloganonymous,publicgraphilegraphile-starterlayoutvisitormulti-tenantoltpfailOn: perfGrade CNothing is switched off. Per your note: a rule that doesn't apply to a stack is demoted to
info— zero weight, so the score is byte-identical to disabling it — instead of deleted, so it stays in the report and stays re-tunable. That also flips the existingconstructivepreset'sA3: 'off'→'info'(score unchanged; the finding becomes visible). Supabase's and Hasura's platform-managed schemas are scoped the same way, by demotion rather than exclusion.minimalis the one deliberate exception — being a smoke check is its whole job. A test enforces the invariant across every preset, and another asserts every string adapter a preset names exists inBUILTIN_ADAPTERS.Adapters read catalog state, not schema names. PostgREST publishes its own surface as a GUC, so
postgrestAdapterreadspg_db_role_settingrather than guessing — which also means it works from an audit connection that isn't the API's:It emits the API plane plus a
direct:<authenticator>role plane, because a role that canSET ROLEis a separate question from what the API serves. Hasura reads tracked tables out ofhdb_catalog.hdb_metadata(v2 JSON, with the v1hdb_tablefallback) — untracked tables are not served, so they are not the surface.graphileAdapteris the deliberate exception and is documented as one: PostGraphile's schema list is a process argument that leaves no trace in the database, so the adapter resolves thegraphile-starterlayout (app_public+app_hiddenserved,app_privateits own internal plane). That is naming-as-intent, which safegres otherwise refuses; the difference is consent — naming the adapter is the declaration. When the convention doesn't hold,exposure.schemasstill wins.Also: dropped the hand-written
MIT © Constructiveline from the README. Every package here gets the sharedFOOTER.mdappended at build time (credits, license, disclaimer), so safegres was the only one ending in a duplicate, half-size footer.Verification
pnpm lint,pnpm build,pnpm testinpackages/safegres— 23 suites / 243 tests, 10 of them new. The adapter tests run against a fixture that reproduces each stack's one catalog signal (__tests__/fixtures/stacks.sql), and include a negative: theconstructiveadapter must stay quiet in a database that isn't Constructive. Confirmeddist/README.mdrenders with the shared footer and no duplicate.Link to Devin session: https://app.devin.ai/sessions/b7874ecee0c7471ea271e6e7193869dc
Requested by: @pyramation