postgresql — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited postgresql (Agent Skill) and scored it 91/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} is the classic direct prompt-injection phrasing. Placed in a skill body that the agent reads as trusted instructions, it tries to make the agent abandon its prior rules and follow whatever comes next — a full system-prompt override.
ignore/disregard/forget … previous instructions sentence.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
| Need | Use | Avoid |
|---|---|---|
| Primary key | BIGINT GENERATED ALWAYS AS IDENTITY | SERIAL, BIGSERIAL |
| Timestamps | TIMESTAMPTZ | TIMESTAMP (loses timezone) |
| Text | TEXT | VARCHAR(n) unless constraint needed |
| Money | NUMERIC(precision, scale) | MONEY, FLOAT |
| Boolean | BOOLEAN with NOT NULL DEFAULT | nullable booleans |
| JSON | JSONB | JSON (no indexing), text JSON |
| UUID | gen_random_uuid() (PG13+) | uuid-ossp extension |
| IP addresses | INET / CIDR | text |
| Ranges | TSTZRANGE, INT4RANGE, etc. | pair of columns |
NOT NULL on every column unless NULL has business meaningCHECK constraints for domain rules at DB levelEXCLUDE constraints for range overlaps: EXCLUDE USING gist (room WITH =, during WITH &&)created_at TIMESTAMPTZ NOT NULL DEFAULT now()updated_at with trigger, never trust app layer aloneBIGINT PKs — cheaper JOINs than UUID, better index locality| Type | Use When |
|---|---|
| B-tree (default) | Equality, range, sorting, LIKE 'prefix%' |
| GIN | JSONB (@>, ?, ?&), arrays, full-text (tsvector) |
| GiST | Geometry, ranges, full-text (smaller but slower than GIN) |
| BRIN | Large tables with natural ordering (timestamps, serial IDs) |
Index rules:
WHERE status = 'active' — smaller, fasterINCLUDE (col) — avoids heap lookupON (lower(email)) — for function-based WHERESELECT * FROM pg_stat_user_indexes WHERE idx_scan = 0-- GIN index for containment queries
CREATE INDEX ON items USING gin (metadata);
SELECT * FROM items WHERE metadata @> '{"status": "active"}';
-- Expression index for specific key access
CREATE INDEX ON items ((metadata->>'category'));
SELECT * FROM items WHERE metadata->>'category' = 'electronics';Prefer typed columns over JSONB for frequently queried, well-structured data. Use JSONB for truly dynamic/variable attributes.
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) before optimizingWHERE for function wrappingrows removed by filter → index doesn't match predicateCTE is an optimization fence before PG12; use MATERIALIZED/NOT MATERIALIZED hints (PG12+)EXISTS over IN for correlated subqueriesLATERAL JOIN when subquery needs outer row referenceWHERE id > $last ORDER BY id LIMIT $n) over OFFSETUse when table exceeds ~100M rows or needs TTL purge:
RANGE — time-series (by month/year), most commonLIST — categorical (by region, tenant)HASH — even distribution when no natural keyPartition key must be in every unique/PK constraint. Create indexes on partitions, not parent.
SELECT ... FOR UPDATE SKIP LOCKED — job queue / work claiming patternpg_advisory_xact_lock(key)SELECT * FROM pg_stat_activity WHERE wait_event_type = 'Lock'-- Weighted tsvector column with trigger
ALTER TABLE articles ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(title,'')), 'A') ||
setweight(to_tsvector('english', coalesce(body,'')), 'B')
) STORED;
CREATE INDEX ON articles USING gin (search_vector);
SELECT * FROM articles WHERE search_vector @@ websearch_to_tsquery('english', $1);Key postgresql.conf parameters (adjust for available RAM):
shared_buffers = 25% of RAMeffective_cache_size = 75% of RAMwork_mem = RAM / max_connections / 4 (start 4-16MB)maintenance_work_mem = 256MB-1GBrandom_page_cost = 1.1 for SSD (default 4.0 is for HDD)Always pool in production. Direct connections cost ~10MB each.
transaction mode for most workloadsstatement mode if no session-level features (prepared statements, temp tables, advisory locks)pg_stat_statements extension — find slow queries by total time, not just durationpg_stat_user_tables — check n_dead_tup for vacuum needsautovacuum tuning: lower thresholds for hot tablesSELECT sum(heap_blks_hit) / sum(heap_blks_hit + heap_blks_read) FROM pg_statio_user_tables — should be > 99%| Anti-Pattern | Fix |
|---|---|
SERIAL / BIGSERIAL for PKs | BIGINT GENERATED ALWAYS AS IDENTITY |
| No FK indexes | Add index on every FK column |
OFFSET pagination | Cursor-based: WHERE id > $last |
SELECT * | List needed columns |
TIMESTAMP without timezone | TIMESTAMPTZ |
Functions in WHERE (lower(col)) | Expression index or citext extension |
| Storing structured data as text | JSONB with GIN index |
| Long-running transactions | Keep txns short, use idle_in_transaction_session_timeout |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.