From d3753a7caf1cc337da52f52bd680024d45bb6215 Mon Sep 17 00:00:00 2001 From: Mohamed Ragab Date: Thu, 13 Aug 2026 18:38:29 +0300 Subject: [PATCH 1/2] Make command reference paths resolve outside a plugin install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ${CLAUDE_PLUGIN_ROOT} is substituted only for plugin skills. When an agent is asked to install this skill from GitHub it copies the files as a personal skill under ~/.claude/skills/ and the commands into ~/.claude/commands/, where the variable is never expanded and every reference-file read fails. For /paymob-check-hmac that failure is dangerous rather than cosmetic: the command exists to check a field order it deliberately does not carry, so a failed read invites an audit from recall that can pass a wrong order as correct — HMAC verification that looks verified and is not. Each command now resolves its root from the plugin root, the personal skills directory, or a repo checkout, searches as a last resort, and stops rather than answering from memory. allowed-tools gains Glob so the search is actually permitted. validate.py enforces both properties. --- commands/paymob-check-hmac.md | 16 ++++++++++++++-- commands/paymob-explain-error.md | 14 ++++++++++++-- commands/paymob-test-cards.md | 14 ++++++++++++-- scripts/validate.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/commands/paymob-check-hmac.md b/commands/paymob-check-hmac.md index 4d8d732..3c5109a 100644 --- a/commands/paymob-check-hmac.md +++ b/commands/paymob-check-hmac.md @@ -2,6 +2,7 @@ description: Audit a Paymob webhook HMAC verification implementation for correctness and safe failure behavior. argument-hint: "[path to the webhook handler, optional]" disable-model-invocation: true +allowed-tools: Read Glob Grep --- Audit the user's Paymob webhook HMAC verification code: $ARGUMENTS @@ -10,14 +11,25 @@ Audit the user's Paymob webhook HMAC verification code: $ARGUMENTS - Never ask the user to paste their HMAC secret, API key, or Secret Key into this conversation. - Never read a `.env` file's actual secret value, and never echo, print, or write any secret into chat output, logs, or a file you create. - The default mode is a **static code audit** — no computation is required to complete it. +- **Never audit the field order, digest, or callback shape from memory.** This command deliberately carries no copy of the spec. If you cannot read `hmac-verification.md`, you cannot do this audit — say so and stop. An audit performed from recall can pass a wrong field order as correct, which is the exact failure this command exists to catch: HMAC verification that looks verified and silently is not. + +## Step 0 — locate the spec + +`hmac-verification.md` lives in the skill's `references/` directory. Use the first of these that exists: + +1. `${CLAUDE_PLUGIN_ROOT}/skills/paymob-integration/references/` — plugin install +2. `~/.claude/skills/paymob-integration/references/` — personal skill install +3. `skills/paymob-integration/references/` — repository checkout + +`${CLAUDE_PLUGIN_ROOT}` is substituted only for plugin installs. If you see it unexpanded, you are not in one — use path 2 or 3. If none resolve, search for `hmac-verification.md` under any `paymob-integration` directory. Confirm you have actually read the file before starting Step 2. ## Step 1 — locate the code If `$ARGUMENTS` names a path, start there. Otherwise search for the callback route bound to `notification_url` / the `hmac` query parameter, or for `PAYMOB_HMAC_SECRET` usage. -## Step 2 — audit against `${CLAUDE_PLUGIN_ROOT}/skills/paymob-integration/references/hmac-verification.md` +## Step 2 — audit against `references/hmac-verification.md` -Read that file first — don't rely on memory for the field order or algorithm, since this command carries no copy of the spec. Then check each item below and report pass/fail with file/line references: +Read that file first, from the root you resolved in Step 0 — don't rely on memory for the field order or algorithm. Then check each item below and report pass/fail with file/line references: 1. **Digest**: HMAC-**SHA-512**, not SHA-256 or any other digest. 2. **Source and order**: the concatenation reads fields from `body.obj` (the POST payload) in exactly the field order given in `hmac-verification.md` — flag any other ordering, including "alphabetical by key name," as a failure. diff --git a/commands/paymob-explain-error.md b/commands/paymob-explain-error.md index 6c9c1ba..02e53c4 100644 --- a/commands/paymob-explain-error.md +++ b/commands/paymob-explain-error.md @@ -1,12 +1,22 @@ --- description: Explain a Paymob error code, HTTP status, or error message and give the cause and fix. argument-hint: "[error code, HTTP status, or error message]" -allowed-tools: Read +allowed-tools: Read Glob --- The user wants this Paymob error explained: $ARGUMENTS -1. Read the **Troubleshooting** table in `${CLAUDE_PLUGIN_ROOT}/skills/paymob-integration/SKILL.md` and match `$ARGUMENTS` against its rows (by HTTP status, error text, or symptom). Quote the cause(s) listed for the matching row — do not paraphrase from memory, read the table each time in case it has changed. +**Locating the skill files.** `SKILL.md` and its `references/` directory live together. Use the first of these that exists: + +1. `${CLAUDE_PLUGIN_ROOT}/skills/paymob-integration/` — plugin install +2. `~/.claude/skills/paymob-integration/` — personal skill install +3. `skills/paymob-integration/` — repository checkout + +`${CLAUDE_PLUGIN_ROOT}` is substituted only for plugin installs. If you see it unexpanded, you are not in one — use path 2 or 3. If none resolve, search for `SKILL.md` under any `paymob-integration` directory. Paths written as `references/…` below are relative to whichever root you resolved. + +**If you cannot read the files, say so and stop.** Do not explain a Paymob error from memory — a plausible-sounding wrong cause sends the merchant to debug the wrong layer. + +1. Read the **Troubleshooting** table in `SKILL.md` and match `$ARGUMENTS` against its rows (by HTTP status, error text, or symptom). Quote the cause(s) listed for the matching row — do not paraphrase from memory, read the table each time in case it has changed. 2. If the fix involves code, open the reference file matching the user's project language/framework — `references/code-nodejs.md`, `references/code-python.md`, `references/code-php.md`, `references/code-dotnet.md`, `references/code-ruby.md`, or `references/code-frontend.md` — and adapt its corrected snippet to the user's project. Do not write a fix that contradicts what's in that file (for example, don't invent a different HMAC digest, header scheme, or amount unit than what the reference shows). 3. If the matching row is the HMAC-mismatch row, also read `references/hmac-verification.md` before answering and get the field order, digest algorithm, and the `obj.id`/`order.id` (POST) vs `id`/`order_id` (GET redirect) distinction from that file directly — never restate the field order from memory or from this command file, since this file intentionally carries no copy of it. 4. If `$ARGUMENTS` does not match any row in the table, say so explicitly rather than guessing at a cause. Then: diff --git a/commands/paymob-test-cards.md b/commands/paymob-test-cards.md index 6ccd132..5b419a7 100644 --- a/commands/paymob-test-cards.md +++ b/commands/paymob-test-cards.md @@ -1,10 +1,20 @@ --- description: Show Paymob sandbox test card and wallet credentials, optionally filtered by payment method. argument-hint: "[card|wallet|kiosk|bnpl]" -allowed-tools: Read +allowed-tools: Read Glob --- -Read `${CLAUDE_PLUGIN_ROOT}/skills/paymob-integration/references/test-credentials.md` in full and present the sandbox test credentials it contains to the user. Do not use any test numbers from memory — this file is the only source. +Read `references/test-credentials.md` in full and present the sandbox test credentials it contains to the user. Do not use any test numbers from memory — this file is the only source. + +**Locating the reference file.** Use the first of these that exists: + +1. `${CLAUDE_PLUGIN_ROOT}/skills/paymob-integration/references/` — plugin install +2. `~/.claude/skills/paymob-integration/references/` — personal skill install +3. `skills/paymob-integration/references/` — repository checkout + +`${CLAUDE_PLUGIN_ROOT}` is substituted only for plugin installs. If you see it unexpanded, you are not in one — use path 2 or 3. If none resolve, search for `test-credentials.md` under any `paymob-integration` directory. + +**If you cannot read the file, say so and stop.** Do not list card numbers from memory — stale or invented test credentials waste a merchant's debugging time on a payment that was never going to succeed. Argument handling for `$ARGUMENTS`: - `card` → show only the Mastercard and Visa test card sections. diff --git a/scripts/validate.py b/scripts/validate.py index 46cf7db..82212eb 100644 --- a/scripts/validate.py +++ b/scripts/validate.py @@ -64,6 +64,22 @@ HMAC_FIELD_ORDER_RESTATEMENT_PATTERN = re.compile( r"amount_cents\s*\r?\n\s*created_at\s*\r?\n\s*currency" ) +# ${CLAUDE_PLUGIN_ROOT} is substituted only for plugin installs. A command that +# names it as the sole location breaks when the skill is installed as a personal +# skill under ~/.claude/skills/, which is what agents do when asked to install +# the skill from GitHub. Every command must offer a non-plugin fallback. +COMMAND_FALLBACK_PATH_PATTERNS = ( + re.compile(r"~/\.claude/skills/paymob-integration"), + re.compile(r"(? dict: @@ -488,6 +504,20 @@ def validate_commands(errors: list[str]) -> None: if COMMAND_SECRET_PATTERN.search(body): errors.append(f"{label} appears to contain a hardcoded secret or key") + if "${CLAUDE_PLUGIN_ROOT}" in body and not any( + pattern.search(body) for pattern in COMMAND_FALLBACK_PATH_PATTERNS + ): + errors.append( + f"{label} names ${{CLAUDE_PLUGIN_ROOT}} without a non-plugin fallback " + "path; the reference files are unreachable in a personal-skill install" + ) + + if not COMMAND_FAIL_CLOSED_PATTERN.search(body): + errors.append( + f"{label} must tell the agent to stop rather than answer from memory " + "when it cannot read its reference file" + ) + def validate_links(errors: list[str]) -> None: for markdown in ROOT.rglob("*.md"): From 60cbd8b6443e38dc2d966aaa14e0333cccda8d7f Mon Sep 17 00:00:00 2001 From: Mohamed Ragab Date: Thu, 13 Aug 2026 19:04:10 +0300 Subject: [PATCH 2/2] Spell out home-directory expansion in the fallback paths A fresh agent run through the personal-install path resolved the file correctly but had to expand "~" itself, and noted that a strict reading of the literal "~/.claude/..." string would fail on Windows. Name "~" as the home directory, give the %USERPROFILE% form, and mark path 3 as working only inside the skill repo so path 2 is understood as the reliable one for a personal install. --- commands/paymob-check-hmac.md | 4 ++-- commands/paymob-explain-error.md | 4 ++-- commands/paymob-test-cards.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/commands/paymob-check-hmac.md b/commands/paymob-check-hmac.md index 3c5109a..6401398 100644 --- a/commands/paymob-check-hmac.md +++ b/commands/paymob-check-hmac.md @@ -18,8 +18,8 @@ Audit the user's Paymob webhook HMAC verification code: $ARGUMENTS `hmac-verification.md` lives in the skill's `references/` directory. Use the first of these that exists: 1. `${CLAUDE_PLUGIN_ROOT}/skills/paymob-integration/references/` — plugin install -2. `~/.claude/skills/paymob-integration/references/` — personal skill install -3. `skills/paymob-integration/references/` — repository checkout +2. `~/.claude/skills/paymob-integration/references/` — personal skill install. `~` is the user's home directory; on Windows expand it yourself (`%USERPROFILE%\.claude\skills\…`), since a literal `~` will not resolve there. +3. `skills/paymob-integration/references/` — repository checkout, relative to the working directory. This only resolves if the user happens to be working inside the skill repo, so treat path 2 as the reliable one for a personal install. `${CLAUDE_PLUGIN_ROOT}` is substituted only for plugin installs. If you see it unexpanded, you are not in one — use path 2 or 3. If none resolve, search for `hmac-verification.md` under any `paymob-integration` directory. Confirm you have actually read the file before starting Step 2. diff --git a/commands/paymob-explain-error.md b/commands/paymob-explain-error.md index 02e53c4..0b51899 100644 --- a/commands/paymob-explain-error.md +++ b/commands/paymob-explain-error.md @@ -9,8 +9,8 @@ The user wants this Paymob error explained: $ARGUMENTS **Locating the skill files.** `SKILL.md` and its `references/` directory live together. Use the first of these that exists: 1. `${CLAUDE_PLUGIN_ROOT}/skills/paymob-integration/` — plugin install -2. `~/.claude/skills/paymob-integration/` — personal skill install -3. `skills/paymob-integration/` — repository checkout +2. `~/.claude/skills/paymob-integration/` — personal skill install. `~` is the user's home directory; on Windows expand it yourself (`%USERPROFILE%\.claude\skills\…`), since a literal `~` will not resolve there. +3. `skills/paymob-integration/` — repository checkout, relative to the working directory `${CLAUDE_PLUGIN_ROOT}` is substituted only for plugin installs. If you see it unexpanded, you are not in one — use path 2 or 3. If none resolve, search for `SKILL.md` under any `paymob-integration` directory. Paths written as `references/…` below are relative to whichever root you resolved. diff --git a/commands/paymob-test-cards.md b/commands/paymob-test-cards.md index 5b419a7..ee17dae 100644 --- a/commands/paymob-test-cards.md +++ b/commands/paymob-test-cards.md @@ -9,8 +9,8 @@ Read `references/test-credentials.md` in full and present the sandbox test crede **Locating the reference file.** Use the first of these that exists: 1. `${CLAUDE_PLUGIN_ROOT}/skills/paymob-integration/references/` — plugin install -2. `~/.claude/skills/paymob-integration/references/` — personal skill install -3. `skills/paymob-integration/references/` — repository checkout +2. `~/.claude/skills/paymob-integration/references/` — personal skill install. `~` is the user's home directory; on Windows expand it yourself (`%USERPROFILE%\.claude\skills\…`), since a literal `~` will not resolve there. +3. `skills/paymob-integration/references/` — repository checkout, relative to the working directory `${CLAUDE_PLUGIN_ROOT}` is substituted only for plugin installs. If you see it unexpanded, you are not in one — use path 2 or 3. If none resolve, search for `test-credentials.md` under any `paymob-integration` directory.