[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f1zqj0m9f205hj":3,"$f3k0rduxo7iuw8":50},{"sha":4,"committedAt":5,"fetchedAt":6,"source":7,"skill":8},"d545ab58bb89a5f88a08034a056af464abc229bf","2026-09-04T15:40:39.000Z","2026-09-04T15:49:35.573Z","github",{"slug":9,"name":10,"description":11,"tags":12,"author":16,"authorUrl":17,"badges":18,"fileCount":22,"totalBytes":23,"tree":24,"errors":49},"readonly-db","Read-only DB Access","A dedicated read-only role, a one-statement runner wrapped in BEGIN READ ONLY, and the rule that keeps destructive SQL out of Claude's hands.",[13,14,15],"database","postgres","safety","Patrity","https:\u002F\u002Fgithub.com\u002FPatrity",[19,20,21],"skills","rules","claude-md",4,11477,[25,34,43,46],{"name":20,"path":20,"type":26,"children":27},"dir",[28],{"name":29,"path":30,"type":31,"size":32,"kind":33},"database-safety.md","rules\u002Fdatabase-safety.md","file",1655,"text",{"name":19,"path":19,"type":26,"children":35},[36],{"name":9,"path":37,"type":26,"children":38},"skills\u002Freadonly-db",[39],{"name":40,"path":41,"type":31,"size":42,"kind":33},"SKILL.md","skills\u002Freadonly-db\u002FSKILL.md",6195,{"name":44,"path":44,"type":31,"size":45,"kind":33},"CLAUDE.md",539,{"name":47,"path":47,"type":31,"size":48,"kind":33},"README.md",3088,[],{"path":47,"language":51,"size":48,"kind":33,"content":52,"frontmatterRaw":53,"body":54,"data":615},"markdown","---\nname: Read-only DB Access\ndescription: A dedicated read-only role, a one-statement runner wrapped in BEGIN READ ONLY, and the rule that keeps destructive SQL out of Claude's hands.\ntags: [database, postgres, safety]\nauthor: Patrity\nauthorUrl: https:\u002F\u002Fgithub.com\u002FPatrity\n---\n\n# Read-only DB Access\n\nSome questions only rows can answer: did that trigger fire, what does this view actually return, is\nthat column populated, how does the query behave at volume. This bundle makes asking them routine\nwithout making writing them possible.\n\n## What's inside\n\n| Path | Purpose |\n| --- | --- |\n| `skills\u002Freadonly-db\u002F` | Setting up the read-only role and the `db:q` runner, and the five rules for handling what comes back. |\n| `rules\u002Fdatabase-safety.md` | Fires on `server\u002F`, `db\u002F`, `drizzle\u002F` and `prisma\u002F` — read-only exploration, no destructive SQL, no dumps, migrations from CI. |\n| `CLAUDE.md` | A pointer block to paste into your project's `CLAUDE.md`. |\n\n## The shape of it\n\nTwo independent guards, because either one alone is a single point of failure:\n\n1. **A dedicated role.** `\u003Capp>_claude_ro` has `SELECT` and nothing else, with sensitive tables\n   revoked explicitly and a `statement_timeout` so a bad join fails instead of hanging. Its\n   credential lives in its own env var, separate from the app's.\n2. **A one-statement runner.** `{{pm}} run db:q -- \"SELECT …\"` accepts only `SELECT`, `WITH`,\n   `EXPLAIN`, `SHOW`, `TABLE` and `VALUES`, rejects a second statement, wraps the call in\n   `BEGIN READ ONLY`, refuses to run outside development, and caps its own output.\n\nThe allowlist is there for the error message. `BEGIN READ ONLY` is what makes it true.\n\nIt is not a sandbox, and the skill says so plainly: any session in the repo can still reach the\nread-write credential the app uses. This removes a class of accident on the exploration path — it\ndoes not contain a session that goes looking.\n\n## Install\n\n```bash\nunzip readonly-db.zip\nmkdir -p .claude\u002Fskills .claude\u002Frules\ncp -R readonly-db\u002Fskills\u002F. .claude\u002Fskills\u002F\ncp -R readonly-db\u002Frules\u002F. .claude\u002Frules\u002F\ncat readonly-db\u002FCLAUDE.md >> CLAUDE.md\n```\n\nThen ask Claude to run the `readonly-db` skill: it writes the migration that creates the role and\nthe `db:q` script, and wires the npm script. Put the role's credential in whatever secret manager\nthe project already uses — never in a committed file.\n\n## Placeholders\n\n`{{pm}}` in the skill and the rule is your package manager. The setup CLI renders it; by hand,\nreplace it with `pnpm`, `npm`, `yarn` or `bun`.\n\n## What it deliberately does not do\n\n- No `pg_dump`\u002F`pg_restore` — a dump is every revoke undone at once.\n- No production connection. Reproduce on a branch or a local copy.\n- Nothing read from the database is written into a file, a commit message or a PR body. Git history\n  is permanent and gets read back into context on every later prompt.\n\n## Companion bundles\n\n- **[`nuxt`](\u002Fskill\u002Fnuxt)** — its `rules\u002Fdatabase.md` covers the Drizzle side (schema layout,\n  generated migrations, query conventions) and agrees with this one on the safety rules.\n","name: Read-only DB Access\ndescription: A dedicated read-only role, a one-statement runner wrapped in BEGIN READ ONLY, and the rule that keeps destructive SQL out of Claude's hands.\ntags: [database, postgres, safety]\nauthor: Patrity\nauthorUrl: https:\u002F\u002Fgithub.com\u002FPatrity",{"type":55,"children":56},"root",[57,64,70,77,201,207,212,320,332,337,343,467,486,492,532,538,573,579,609],{"type":58,"tag":59,"props":60,"children":62},"element","h1",{"id":61},"read-only-db-access",[63],{"type":33,"value":10},{"type":58,"tag":65,"props":66,"children":67},"p",{},[68],{"type":33,"value":69},"Some questions only rows can answer: did that trigger fire, what does this view actually return, is\nthat column populated, how does the query behave at volume. This bundle makes asking them routine\nwithout making writing them possible.",{"type":58,"tag":71,"props":72,"children":74},"h2",{"id":73},"whats-inside",[75],{"type":33,"value":76},"What's inside",{"type":58,"tag":78,"props":79,"children":80},"table",{},[81,100],{"type":58,"tag":82,"props":83,"children":84},"thead",{},[85],{"type":58,"tag":86,"props":87,"children":88},"tr",{},[89,95],{"type":58,"tag":90,"props":91,"children":92},"th",{},[93],{"type":33,"value":94},"Path",{"type":58,"tag":90,"props":96,"children":97},{},[98],{"type":33,"value":99},"Purpose",{"type":58,"tag":101,"props":102,"children":103},"tbody",{},[104,131,178],{"type":58,"tag":86,"props":105,"children":106},{},[107,118],{"type":58,"tag":108,"props":109,"children":110},"td",{},[111],{"type":58,"tag":112,"props":113,"children":115},"code",{"className":114},[],[116],{"type":33,"value":117},"skills\u002Freadonly-db\u002F",{"type":58,"tag":108,"props":119,"children":120},{},[121,123,129],{"type":33,"value":122},"Setting up the read-only role and the ",{"type":58,"tag":112,"props":124,"children":126},{"className":125},[],[127],{"type":33,"value":128},"db:q",{"type":33,"value":130}," runner, and the five rules for handling what comes back.",{"type":58,"tag":86,"props":132,"children":133},{},[134,142],{"type":58,"tag":108,"props":135,"children":136},{},[137],{"type":58,"tag":112,"props":138,"children":140},{"className":139},[],[141],{"type":33,"value":30},{"type":58,"tag":108,"props":143,"children":144},{},[145,147,153,155,161,162,168,170,176],{"type":33,"value":146},"Fires on ",{"type":58,"tag":112,"props":148,"children":150},{"className":149},[],[151],{"type":33,"value":152},"server\u002F",{"type":33,"value":154},", ",{"type":58,"tag":112,"props":156,"children":158},{"className":157},[],[159],{"type":33,"value":160},"db\u002F",{"type":33,"value":154},{"type":58,"tag":112,"props":163,"children":165},{"className":164},[],[166],{"type":33,"value":167},"drizzle\u002F",{"type":33,"value":169}," and ",{"type":58,"tag":112,"props":171,"children":173},{"className":172},[],[174],{"type":33,"value":175},"prisma\u002F",{"type":33,"value":177}," — read-only exploration, no destructive SQL, no dumps, migrations from CI.",{"type":58,"tag":86,"props":179,"children":180},{},[181,189],{"type":58,"tag":108,"props":182,"children":183},{},[184],{"type":58,"tag":112,"props":185,"children":187},{"className":186},[],[188],{"type":33,"value":44},{"type":58,"tag":108,"props":190,"children":191},{},[192,194,199],{"type":33,"value":193},"A pointer block to paste into your project's ",{"type":58,"tag":112,"props":195,"children":197},{"className":196},[],[198],{"type":33,"value":44},{"type":33,"value":200},".",{"type":58,"tag":71,"props":202,"children":204},{"id":203},"the-shape-of-it",[205],{"type":33,"value":206},"The shape of it",{"type":58,"tag":65,"props":208,"children":209},{},[210],{"type":33,"value":211},"Two independent guards, because either one alone is a single point of failure:",{"type":58,"tag":213,"props":214,"children":215},"ol",{},[216,252],{"type":58,"tag":217,"props":218,"children":219},"li",{},[220,226,228,234,236,242,244,250],{"type":58,"tag":221,"props":222,"children":223},"strong",{},[224],{"type":33,"value":225},"A dedicated role.",{"type":33,"value":227}," ",{"type":58,"tag":112,"props":229,"children":231},{"className":230},[],[232],{"type":33,"value":233},"\u003Capp>_claude_ro",{"type":33,"value":235}," has ",{"type":58,"tag":112,"props":237,"children":239},{"className":238},[],[240],{"type":33,"value":241},"SELECT",{"type":33,"value":243}," and nothing else, with sensitive tables\nrevoked explicitly and a ",{"type":58,"tag":112,"props":245,"children":247},{"className":246},[],[248],{"type":33,"value":249},"statement_timeout",{"type":33,"value":251}," so a bad join fails instead of hanging. Its\ncredential lives in its own env var, separate from the app's.",{"type":58,"tag":217,"props":253,"children":254},{},[255,260,261,267,269,274,275,281,283,289,290,296,297,303,304,310,312,318],{"type":58,"tag":221,"props":256,"children":257},{},[258],{"type":33,"value":259},"A one-statement runner.",{"type":33,"value":227},{"type":58,"tag":112,"props":262,"children":264},{"className":263},[],[265],{"type":33,"value":266},"{{pm}} run db:q -- \"SELECT …\"",{"type":33,"value":268}," accepts only ",{"type":58,"tag":112,"props":270,"children":272},{"className":271},[],[273],{"type":33,"value":241},{"type":33,"value":154},{"type":58,"tag":112,"props":276,"children":278},{"className":277},[],[279],{"type":33,"value":280},"WITH",{"type":33,"value":282},",\n",{"type":58,"tag":112,"props":284,"children":286},{"className":285},[],[287],{"type":33,"value":288},"EXPLAIN",{"type":33,"value":154},{"type":58,"tag":112,"props":291,"children":293},{"className":292},[],[294],{"type":33,"value":295},"SHOW",{"type":33,"value":154},{"type":58,"tag":112,"props":298,"children":300},{"className":299},[],[301],{"type":33,"value":302},"TABLE",{"type":33,"value":169},{"type":58,"tag":112,"props":305,"children":307},{"className":306},[],[308],{"type":33,"value":309},"VALUES",{"type":33,"value":311},", rejects a second statement, wraps the call in\n",{"type":58,"tag":112,"props":313,"children":315},{"className":314},[],[316],{"type":33,"value":317},"BEGIN READ ONLY",{"type":33,"value":319},", refuses to run outside development, and caps its own output.",{"type":58,"tag":65,"props":321,"children":322},{},[323,325,330],{"type":33,"value":324},"The allowlist is there for the error message. ",{"type":58,"tag":112,"props":326,"children":328},{"className":327},[],[329],{"type":33,"value":317},{"type":33,"value":331}," is what makes it true.",{"type":58,"tag":65,"props":333,"children":334},{},[335],{"type":33,"value":336},"It is not a sandbox, and the skill says so plainly: any session in the repo can still reach the\nread-write credential the app uses. This removes a class of accident on the exploration path — it\ndoes not contain a session that goes looking.",{"type":58,"tag":71,"props":338,"children":340},{"id":339},"install",[341],{"type":33,"value":342},"Install",{"type":58,"tag":344,"props":345,"children":350},"pre",{"className":346,"code":347,"language":348,"meta":349,"style":349},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","unzip readonly-db.zip\nmkdir -p .claude\u002Fskills .claude\u002Frules\ncp -R readonly-db\u002Fskills\u002F. .claude\u002Fskills\u002F\ncp -R readonly-db\u002Frules\u002F. .claude\u002Frules\u002F\ncat readonly-db\u002FCLAUDE.md >> CLAUDE.md\n","bash","",[351],{"type":58,"tag":112,"props":352,"children":353},{"__ignoreMap":349},[354,372,397,421,442],{"type":58,"tag":355,"props":356,"children":359},"span",{"class":357,"line":358},"line",1,[360,366],{"type":58,"tag":355,"props":361,"children":363},{"style":362},"--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0",[364],{"type":33,"value":365},"unzip",{"type":58,"tag":355,"props":367,"children":369},{"style":368},"--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF",[370],{"type":33,"value":371}," readonly-db.zip\n",{"type":58,"tag":355,"props":373,"children":375},{"class":357,"line":374},2,[376,381,387,392],{"type":58,"tag":355,"props":377,"children":378},{"style":362},[379],{"type":33,"value":380},"mkdir",{"type":58,"tag":355,"props":382,"children":384},{"style":383},"--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF",[385],{"type":33,"value":386}," -p",{"type":58,"tag":355,"props":388,"children":389},{"style":368},[390],{"type":33,"value":391}," .claude\u002Fskills",{"type":58,"tag":355,"props":393,"children":394},{"style":368},[395],{"type":33,"value":396}," .claude\u002Frules\n",{"type":58,"tag":355,"props":398,"children":400},{"class":357,"line":399},3,[401,406,411,416],{"type":58,"tag":355,"props":402,"children":403},{"style":362},[404],{"type":33,"value":405},"cp",{"type":58,"tag":355,"props":407,"children":408},{"style":383},[409],{"type":33,"value":410}," -R",{"type":58,"tag":355,"props":412,"children":413},{"style":368},[414],{"type":33,"value":415}," readonly-db\u002Fskills\u002F.",{"type":58,"tag":355,"props":417,"children":418},{"style":368},[419],{"type":33,"value":420}," .claude\u002Fskills\u002F\n",{"type":58,"tag":355,"props":422,"children":423},{"class":357,"line":22},[424,428,432,437],{"type":58,"tag":355,"props":425,"children":426},{"style":362},[427],{"type":33,"value":405},{"type":58,"tag":355,"props":429,"children":430},{"style":383},[431],{"type":33,"value":410},{"type":58,"tag":355,"props":433,"children":434},{"style":368},[435],{"type":33,"value":436}," readonly-db\u002Frules\u002F.",{"type":58,"tag":355,"props":438,"children":439},{"style":368},[440],{"type":33,"value":441}," .claude\u002Frules\u002F\n",{"type":58,"tag":355,"props":443,"children":445},{"class":357,"line":444},5,[446,451,456,462],{"type":58,"tag":355,"props":447,"children":448},{"style":362},[449],{"type":33,"value":450},"cat",{"type":58,"tag":355,"props":452,"children":453},{"style":368},[454],{"type":33,"value":455}," readonly-db\u002FCLAUDE.md",{"type":58,"tag":355,"props":457,"children":459},{"style":458},"--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583",[460],{"type":33,"value":461}," >>",{"type":58,"tag":355,"props":463,"children":464},{"style":368},[465],{"type":33,"value":466}," CLAUDE.md\n",{"type":58,"tag":65,"props":468,"children":469},{},[470,472,477,479,484],{"type":33,"value":471},"Then ask Claude to run the ",{"type":58,"tag":112,"props":473,"children":475},{"className":474},[],[476],{"type":33,"value":9},{"type":33,"value":478}," skill: it writes the migration that creates the role and\nthe ",{"type":58,"tag":112,"props":480,"children":482},{"className":481},[],[483],{"type":33,"value":128},{"type":33,"value":485}," script, and wires the npm script. Put the role's credential in whatever secret manager\nthe project already uses — never in a committed file.",{"type":58,"tag":71,"props":487,"children":489},{"id":488},"placeholders",[490],{"type":33,"value":491},"Placeholders",{"type":58,"tag":65,"props":493,"children":494},{},[495,501,503,509,510,516,517,523,525,531],{"type":58,"tag":112,"props":496,"children":498},{"className":497},[],[499],{"type":33,"value":500},"{{pm}}",{"type":33,"value":502}," in the skill and the rule is your package manager. The setup CLI renders it; by hand,\nreplace it with ",{"type":58,"tag":112,"props":504,"children":506},{"className":505},[],[507],{"type":33,"value":508},"pnpm",{"type":33,"value":154},{"type":58,"tag":112,"props":511,"children":513},{"className":512},[],[514],{"type":33,"value":515},"npm",{"type":33,"value":154},{"type":58,"tag":112,"props":518,"children":520},{"className":519},[],[521],{"type":33,"value":522},"yarn",{"type":33,"value":524}," or ",{"type":58,"tag":112,"props":526,"children":528},{"className":527},[],[529],{"type":33,"value":530},"bun",{"type":33,"value":200},{"type":58,"tag":71,"props":533,"children":535},{"id":534},"what-it-deliberately-does-not-do",[536],{"type":33,"value":537},"What it deliberately does not do",{"type":58,"tag":539,"props":540,"children":541},"ul",{},[542,563,568],{"type":58,"tag":217,"props":543,"children":544},{},[545,547,553,555,561],{"type":33,"value":546},"No ",{"type":58,"tag":112,"props":548,"children":550},{"className":549},[],[551],{"type":33,"value":552},"pg_dump",{"type":33,"value":554},"\u002F",{"type":58,"tag":112,"props":556,"children":558},{"className":557},[],[559],{"type":33,"value":560},"pg_restore",{"type":33,"value":562}," — a dump is every revoke undone at once.",{"type":58,"tag":217,"props":564,"children":565},{},[566],{"type":33,"value":567},"No production connection. Reproduce on a branch or a local copy.",{"type":58,"tag":217,"props":569,"children":570},{},[571],{"type":33,"value":572},"Nothing read from the database is written into a file, a commit message or a PR body. Git history\nis permanent and gets read back into context on every later prompt.",{"type":58,"tag":71,"props":574,"children":576},{"id":575},"companion-bundles",[577],{"type":33,"value":578},"Companion bundles",{"type":58,"tag":539,"props":580,"children":581},{},[582],{"type":58,"tag":217,"props":583,"children":584},{},[585,599,601,607],{"type":58,"tag":221,"props":586,"children":587},{},[588],{"type":58,"tag":589,"props":590,"children":592},"a",{"href":591},"\u002Fskill\u002Fnuxt",[593],{"type":58,"tag":112,"props":594,"children":596},{"className":595},[],[597],{"type":33,"value":598},"nuxt",{"type":33,"value":600}," — its ",{"type":58,"tag":112,"props":602,"children":604},{"className":603},[],[605],{"type":33,"value":606},"rules\u002Fdatabase.md",{"type":33,"value":608}," covers the Drizzle side (schema layout,\ngenerated migrations, query conventions) and agrees with this one on the safety rules.",{"type":58,"tag":610,"props":611,"children":612},"style",{},[613],{"type":33,"value":614},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"name":10,"description":11,"tags":616,"author":16,"authorUrl":17},[13,14,15]]