Skip to content

turnstile-spin: validate.sh always fails — jq '//' operator swallows success:false #86

Description

@abdulqadir-bhinderwala

Summary

skills/turnstile-spin/scripts/validate.sh reports failure for every correctly-configured integration. The dummy-token check can never observe success: false, which is the exact response it is written to accept.

Cause

Line:

success=$(echo "$dummy" | (jq -r '.success // "missing"' 2>/dev/null || echo "missing"))

jq's // is the alternative operator: it returns the right-hand side when the left is null or false. Since siteverify returns "success": false for a dummy token, .success // "missing" evaluates to "missing", never "false".

$ echo '{"success":false}' | jq -r '.success // "missing"'
missing
$ echo '{"success":false}' | jq -r '.success'
false

Control flow then hits:

if [ "$success" != "false" ]; then
  echo "{\"status\":\"error\",\"check\":\"dummy_siteverify\",...}"
  exit 1
fi

so the invalid-input-response branch below it is unreachable.

Reproduction

With a valid secret and any dummy token:

$ TURNSTILE_SECRET=<valid secret> ./validate.sh --sitekey <sitekey>
validate: unexpected shape for dummy token: {"error-codes":["invalid-input-response"],"success":false,"messages":[]}
{"status":"error","check":"dummy_siteverify","detail":"expected success:false on a dummy token"}

That response is the documented success signal — SKILL.md's own edge-case table says invalid-input-response "means the secret IS valid. validate.sh treats this as success." It does not.

This is reproducible with the published test secret 1x0000000000000000000000000000000AA, so it needs no real credentials.

Impact

Step 10 (Validation) fails for every user with a working setup. SKILL.md instructs the agent to "surface the error and stop" on validation failure, so a correct integration is reported as broken at the final step.

Suggested fix

success=$(echo "$dummy" | (jq -r 'if has("success") then .success else "missing" end' 2>/dev/null || echo "missing"))

Verified against live siteverify with both the always-pass and always-fail test secrets.

Note on the Python fallback

The || fallbacks throughout these scripts mask this class of bug: when jq is absent the fallback yields "missing", producing the same misleading error for a different reason. Worth distinguishing "parser unavailable" from "field absent".

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions