Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "make-no-mistakes",
"version": "1.7.0",
"version": "1.8.0",
"description": "The disciplined dev lifecycle — implement issues, review PRs, sync releases, test E2E, manage sessions, and stash secrets via OS-native prompts. One plugin to make no mistakes.",
"owner": {
"name": "Luis Andres Pena Castillo",
Expand All @@ -11,7 +11,7 @@
{
"name": "make-no-mistakes",
"description": "Dev lifecycle orchestrator: disciplined Linear issue execution with worktree isolation, PR review with Greptile gating, team release sync, E2E test generation and execution, test suite previewer, security pentesting, MoSCoW + RICE prioritization, cross-platform secret stash via OS-native GUI prompts (zenity / kdialog / osascript / Get-Credential), and session management. 18 commands, 6 auto-activating skills, 2 specialized agents.",
"version": "1.7.0",
"version": "1.8.0",
"author": {
"name": "Luis Andres Pena Castillo",
"email": "lapc506@users.noreply.github.com"
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "make-no-mistakes",
"version": "1.7.0",
"version": "1.8.0",
"description": "The disciplined dev lifecycle — implement issues, review PRs, sync releases, test E2E, manage sessions, stash secrets, and enforce manifest-driven tool-call hooks. One plugin to make no mistakes.",
"author": {
"name": "Luis Andres Pena Castillo",
Expand Down
26 changes: 15 additions & 11 deletions hooks/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,17 +145,21 @@ Adding a new family is fine — just keep ids unique and follow the schema.
- **Database / migration discipline**
(`schema-sql-outside-migrations`, `warn-psql-against-supabase-remote`,
`pr-create-with-migrations-needs-deploy-note`,
`block-supabase-db-push-prod`) — keep schema mutations inside
versioned `supabase/migrations/` files, nudge developers away from
direct `psql` / `pg_dump` / `pg_restore` execution against
`*.supabase.co` hosts, remind PR authors to document migration
deployment, and hard-block `supabase db push` aimed at the production
project ref or `--linked` (which transparently resolves to whichever
project was last linked, possibly production). The production project
ref is configured per install via the substitutions mechanism described
above (`PROD_SUPABASE_REF` token). Added after a discussion surfaced
drift between manually-applied SQL and the migrations directory when
migrations failed to auto-run after a teammate's PR merged.
`block-supabase-db-push-prod`, `warn-curl-mutating-supabase-rest`) —
keep schema mutations inside versioned `supabase/migrations/` files,
nudge developers away from direct `psql` / `pg_dump` / `pg_restore`
execution against `*.supabase.co` hosts, remind PR authors to document
migration deployment, hard-block `supabase db push` aimed at the
production project ref or `--linked` (which transparently resolves to
whichever project was last linked, possibly production), and warn on
ad-hoc mutating `curl` (`POST` / `PATCH` / `PUT` / `DELETE`) against
the Supabase PostgREST endpoint (drift risk equivalent to direct `psql`
+ RLS bypass — same memory ref `feedback_scripts_not_db.md`). The
production project ref is configured per install via the substitutions
mechanism described above (`PROD_SUPABASE_REF` token). Added after a
discussion surfaced drift between manually-applied SQL and the
migrations directory when migrations failed to auto-run after a
teammate's PR merged.

## Tier 2 — decomposing non-deterministic memories

Expand Down
147 changes: 147 additions & 0 deletions hooks/rules/rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -1229,5 +1229,152 @@
"expected_exit": 0
}
]
},
{
"id": "warn-curl-mutating-supabase-rest",
"description": "Warn when curl issues a mutating HTTP method (POST/PATCH/PUT/DELETE) against the Supabase PostgREST endpoint (drift risk vs versioned migrations + RLS bypass)",
"applies_to": [
"Bash"
],
"match": [
{
"field": "command",
"pattern": "curl.*((-X[[:space:]]*(POST|PATCH|PUT|DELETE).*\\.supabase\\.co/rest/v1/)|(\\.supabase\\.co/rest/v1/.*-X[[:space:]]*(POST|PATCH|PUT|DELETE))|((-d[[:space:]]|--data([[:space:]]|=|-raw[[:space:]]|-raw=|-binary[[:space:]]|-binary=|-urlencode[[:space:]]|-urlencode=)).*\\.supabase\\.co/rest/v1/)|(\\.supabase\\.co/rest/v1/.*(-d[[:space:]]|--data([[:space:]]|=|-raw[[:space:]]|-raw=|-binary[[:space:]]|-binary=|-urlencode[[:space:]]|-urlencode=))))",
"flags": "i"
}
],
"action": "warn",
"bypass_marker": "curl-supabase-rest-mutation",
"memory_ref": "feedback_scripts_not_db.md",
"message": "WARNING: mutating curl against the Supabase PostgREST endpoint.\n\nDirect REST mutations bypass:\n - the versioned migrations workflow (drift across local DBs / envs),\n - the RLS + admin-role checks the Supabase clients enforce, and\n - the access pattern already encapsulated in src/services/api/.\n\nCorrect outlets:\n - Schema changes -> supabase/migrations/<timestamp>_<slug>.sql\n - Data mutations -> an Edge Function or a committed script\n - One-shot fixes -> use the API client from a versioned script\n\nUse the bypass marker (curl-supabase-rest-mutation) only for documented\nhotfixes that the team has explicitly approved.\n",
"tests": [
{
"name": "warns-curl-post-supabase-rest",
"input": {
"tool_input": {
"command": "curl -X POST https://example.supabase.co/rest/v1/profiles -H 'apikey: x' -d '{\"id\":1}'"
}
},
"expected_exit": 0,
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
},
{
"name": "warns-curl-patch",
"input": {
"tool_input": {
"command": "curl -X PATCH https://example.supabase.co/rest/v1/profiles?id=eq.1 -d '{\"name\":\"a\"}'"
}
},
"expected_exit": 0,
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
},
{
"name": "warns-curl-put",
"input": {
"tool_input": {
"command": "curl -X PUT https://example.supabase.co/rest/v1/profiles?id=eq.1 -d '{\"name\":\"b\"}'"
}
},
"expected_exit": 0,
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
},
{
"name": "warns-curl-delete",
"input": {
"tool_input": {
"command": "curl -X DELETE https://example.supabase.co/rest/v1/profiles?id=eq.1"
}
},
"expected_exit": 0,
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
},
{
"name": "warns-curl-xpost-no-space",
"input": {
"tool_input": {
"command": "curl -XPOST https://example.supabase.co/rest/v1/profiles -d '{\"id\":1}'"
}
},
"expected_exit": 0,
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
},
{
"name": "warns-curl-url-before-flag",
"input": {
"tool_input": {
"command": "curl https://example.supabase.co/rest/v1/profiles -X POST -d '{\"id\":1}'"
}
},
"expected_exit": 0,
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
},
{
"name": "allows-curl-get-supabase-rest",
"input": {
"tool_input": {
"command": "curl https://example.supabase.co/rest/v1/profiles?select=id,name"
}
},
"expected_exit": 0
},
{
"name": "allows-curl-non-supabase",
"input": {
"tool_input": {
"command": "curl -X POST https://example.com/api/v1/users -d '{\"id\":1}'"
}
},
"expected_exit": 0
},
{
"name": "warns-curl-implicit-post-via-d",
"input": {
"tool_input": {
"command": "curl -d '{\"id\":1}' https://example.supabase.co/rest/v1/profiles"
}
},
"expected_exit": 0,
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
},
{
"name": "warns-curl-implicit-post-via-data-raw",
"input": {
"tool_input": {
"command": "curl --data-raw '{\"id\":1}' https://example.supabase.co/rest/v1/profiles"
}
},
"expected_exit": 0,
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
},
{
"name": "warns-curl-supabase-then-d",
"input": {
"tool_input": {
"command": "curl https://example.supabase.co/rest/v1/profiles -d '{\"id\":1}'"
}
},
"expected_exit": 0,
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
},
{
"name": "warns-curl-implicit-post-via-data-urlencode",
"input": {
"tool_input": {
"command": "curl --data-urlencode 'name=Alice' https://example.supabase.co/rest/v1/profiles"
}
},
"expected_exit": 0,
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
},
{
"name": "allows-bypass-marker",
"input": {
"tool_input": {
"command": "curl -X POST https://example.supabase.co/rest/v1/profiles -d '{\"id\":1}' # hook-bypass: curl-supabase-rest-mutation"
}
},
"expected_exit": 0
}
]
}
]
116 changes: 116 additions & 0 deletions hooks/rules/rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -985,3 +985,119 @@
tool_input:
command: 'supabase db push --linked # hook-bypass: prod-db-push-approved'
expected_exit: 0


- id: warn-curl-mutating-supabase-rest
description: Warn when curl issues a mutating HTTP method (POST/PATCH/PUT/DELETE) against the Supabase PostgREST endpoint (drift risk vs versioned migrations + RLS bypass)
applies_to: [Bash]
match:
# The pattern catches three mutation vectors against Supabase PostgREST:
# 1. Explicit `-X METHOD` (with or without space; `-X POST` and the
# no-space `-XPOST` shorthand both fire).
# 2. URL-before-flag ordering (`curl <url> -X POST ...`), which is
# common in shell scripts that build the URL into a variable.
# 3. Implicit POST via `-d` / `--data` / `--data-raw` / `--data-binary`.
# Curl auto-promotes to POST when these flags are present even
# without `-X`, so a one-liner like `curl -d '...' <url>` is a
# live mutation that would otherwise pass silently.
# The match is narrowed to `.supabase.co/rest/v1/` (PostgREST) so this
# rule does NOT fire for Supabase Auth / Storage / Edge Function
# endpoints — those have separate guidance and Edge Functions are the
# intended outlet.
- field: command
pattern: 'curl.*((-X[[:space:]]*(POST|PATCH|PUT|DELETE).*\.supabase\.co/rest/v1/)|(\.supabase\.co/rest/v1/.*-X[[:space:]]*(POST|PATCH|PUT|DELETE))|((-d[[:space:]]|--data([[:space:]]|=|-raw[[:space:]]|-raw=|-binary[[:space:]]|-binary=|-urlencode[[:space:]]|-urlencode=)).*\.supabase\.co/rest/v1/)|(\.supabase\.co/rest/v1/.*(-d[[:space:]]|--data([[:space:]]|=|-raw[[:space:]]|-raw=|-binary[[:space:]]|-binary=|-urlencode[[:space:]]|-urlencode=))))'
flags: i
action: warn
bypass_marker: curl-supabase-rest-mutation
memory_ref: feedback_scripts_not_db.md
message: |
WARNING: mutating curl against the Supabase PostgREST endpoint.

Direct REST mutations bypass:
- the versioned migrations workflow (drift across local DBs / envs),
- the RLS + admin-role checks the Supabase clients enforce, and
- the access pattern already encapsulated in src/services/api/.

Correct outlets:
- Schema changes -> supabase/migrations/<timestamp>_<slug>.sql
- Data mutations -> an Edge Function or a committed script
- One-shot fixes -> use the API client from a versioned script

Use the bypass marker (curl-supabase-rest-mutation) only for documented
hotfixes that the team has explicitly approved.
tests:
- name: warns-curl-post-supabase-rest
input:
tool_input:
command: "curl -X POST https://example.supabase.co/rest/v1/profiles -H 'apikey: x' -d '{\"id\":1}'"
expected_exit: 0
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
- name: warns-curl-patch
input:
tool_input:
command: "curl -X PATCH https://example.supabase.co/rest/v1/profiles?id=eq.1 -d '{\"name\":\"a\"}'"
expected_exit: 0
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
- name: warns-curl-put
input:
tool_input:
command: "curl -X PUT https://example.supabase.co/rest/v1/profiles?id=eq.1 -d '{\"name\":\"b\"}'"
expected_exit: 0
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
- name: warns-curl-delete
input:
tool_input:
command: 'curl -X DELETE https://example.supabase.co/rest/v1/profiles?id=eq.1'
expected_exit: 0
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
- name: warns-curl-xpost-no-space
input:
tool_input:
command: "curl -XPOST https://example.supabase.co/rest/v1/profiles -d '{\"id\":1}'"
expected_exit: 0
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
- name: warns-curl-url-before-flag
input:
tool_input:
command: "curl https://example.supabase.co/rest/v1/profiles -X POST -d '{\"id\":1}'"
expected_exit: 0
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
- name: allows-curl-get-supabase-rest
input:
tool_input:
command: 'curl https://example.supabase.co/rest/v1/profiles?select=id,name'
expected_exit: 0
- name: allows-curl-non-supabase
input:
tool_input:
command: "curl -X POST https://example.com/api/v1/users -d '{\"id\":1}'"
expected_exit: 0
- name: warns-curl-implicit-post-via-d
input:
tool_input:
command: "curl -d '{\"id\":1}' https://example.supabase.co/rest/v1/profiles"
expected_exit: 0
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
- name: warns-curl-implicit-post-via-data-raw
input:
tool_input:
command: "curl --data-raw '{\"id\":1}' https://example.supabase.co/rest/v1/profiles"
expected_exit: 0
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
- name: warns-curl-supabase-then-d
input:
tool_input:
command: "curl https://example.supabase.co/rest/v1/profiles -d '{\"id\":1}'"
expected_exit: 0
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
- name: warns-curl-implicit-post-via-data-urlencode
input:
tool_input:
command: "curl --data-urlencode 'name=Alice' https://example.supabase.co/rest/v1/profiles"
expected_exit: 0
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
- name: allows-bypass-marker
input:
tool_input:
command: "curl -X POST https://example.supabase.co/rest/v1/profiles -d '{\"id\":1}' # hook-bypass: curl-supabase-rest-mutation"
expected_exit: 0
Loading
Loading