Agent skill
shopify-policy-guardrails
Implement Shopify app policy enforcement with ESLint rules for API key detection, query cost budgets, and App Store compliance checks. Trigger with phrases like "shopify policy", "shopify lint", "shopify guardrails", "shopify compliance", "shopify eslint", "shopify app review".
Install this agent skill to your Project
npx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/main/plugins/saas-packs/shopify-pack/skills/shopify-policy-guardrails
SKILL.md
Shopify Policy & Guardrails
Overview
Automated policy enforcement for Shopify apps: secret detection, query cost budgets, App Store compliance checks, and CI policy validation.
Prerequisites
- ESLint configured in project
- Pre-commit hooks infrastructure
- CI/CD pipeline with GitHub Actions
- Shopify app with
shopify.app.toml
Instructions
Step 1: Secret Detection Rules
// eslint-rules/no-shopify-secrets.js
module.exports = {
meta: {
type: "problem",
docs: { description: "Detect hardcoded Shopify tokens and secrets" },
messages: {
adminToken: "Hardcoded Shopify Admin API token detected (shpat_*)",
apiSecret: "Potential Shopify API secret detected",
storefrontToken: "Hardcoded Storefront API token detected",
},
},
create(context) {
return {
Literal(node) {
if (typeof node.value !== "string") return;
const v = node.value;
// Admin API access token: shpat_ + 32 hex chars
if (/^shpat_[a-f0-9]{32}$/i.test(v)) {
context.report({ node, messageId: "adminToken" });
}
// Storefront token: shpss_ pattern
if (/^shpss_[a-f0-9]{32}$/i.test(v)) {
context.report({ node, messageId: "storefrontToken" });
}
// Generic secret pattern (32+ hex that's clearly a token)
if (/^[a-f0-9]{32,}$/i.test(v) && v.length === 32) {
context.report({ node, messageId: "apiSecret" });
}
},
TemplateLiteral(node) {
for (const quasi of node.quasis) {
if (/shpat_[a-f0-9]/i.test(quasi.value.raw)) {
context.report({ node, messageId: "adminToken" });
}
}
},
};
},
};
Step 2: Query Cost Budget Enforcement
// Enforce query cost budgets at build/test time
interface QueryCostBudget {
maxFirstParam: number; // Max items per page
maxNestedDepth: number; // Max nested connection depth
maxEstimatedCost: number; // Max estimated query cost
}
const BUDGET: QueryCostBudget = {
maxFirstParam: 100, // Never request more than 100 items
maxNestedDepth: 3, // No more than 3 levels of edges/node
maxEstimatedCost: 500, // Stay well under 1,000 point limit
};
function validateQueryCost(query: string): string[] {
const violations: string[] = [];
// Check `first:` parameter values
const firstParams = query.matchAll(/first:\s*(\d+)/g);
for (const match of firstParams) {
if (parseInt(match[1]) > BUDGET.maxFirstParam) {
violations.push(
`first: ${match[1]} exceeds budget of ${BUDGET.maxFirstParam}`
);
}
}
// Check nesting depth (count "edges { node {" patterns)
const depth = (query.match(/edges\s*\{/g) || []).length;
if (depth > BUDGET.maxNestedDepth) {
violations.push(
`Nesting depth ${depth} exceeds budget of ${BUDGET.maxNestedDepth}`
);
}
// Estimate cost: multiply all `first` values along nested path
const firstValues = [...query.matchAll(/first:\s*(\d+)/g)].map((m) =>
parseInt(m[1])
);
const estimatedCost = firstValues.reduce((a, b) => a * b, 1);
if (estimatedCost > BUDGET.maxEstimatedCost) {
violations.push(
`Estimated cost ${estimatedCost} exceeds budget of ${BUDGET.maxEstimatedCost}`
);
}
return violations;
}
Step 3: Pre-Commit Hooks
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: shopify-token-scan
name: Scan for Shopify tokens
language: system
entry: bash -c '
if git diff --cached --diff-filter=d | grep -E "shpat_[a-f0-9]{32}|shpss_[a-f0-9]{32}" ; then
echo "ERROR: Shopify access token detected in staged changes"
exit 1
fi'
pass_filenames: false
- id: shopify-env-check
name: Check .env not staged
language: system
entry: bash -c '
if git diff --cached --name-only | grep -E "^\.env$|^\.env\.local$|^\.env\.production$" ; then
echo "ERROR: .env file staged for commit"
exit 1
fi'
pass_filenames: false
Step 4: App Store Compliance Checker
// scripts/check-app-compliance.ts
// Run before submitting to Shopify App Store
interface ComplianceCheck {
name: string;
required: boolean;
check: () => Promise<boolean>;
}
const checks: ComplianceCheck[] = [
{
name: "GDPR webhook: customers/data_request",
required: true,
check: async () => {
const toml = await readFile("shopify.app.toml", "utf-8");
return toml.includes("customers/data_request");
},
},
{
name: "GDPR webhook: customers/redact",
required: true,
check: async () => {
const toml = await readFile("shopify.app.toml", "utf-8");
return toml.includes("customers/redact");
},
},
{
name: "GDPR webhook: shop/redact",
required: true,
check: async () => {
const toml = await readFile("shopify.app.toml", "utf-8");
return toml.includes("shop/redact");
},
},
{
name: "No hardcoded tokens in source",
required: true,
check: async () => {
const { execSync } = require("child_process");
const result = execSync(
'grep -rE "shpat_[a-f0-9]{32}" app/ --include="*.ts" --include="*.tsx" || true'
).toString();
return result.trim() === "";
},
},
{
name: "CSP frame-ancestors header set",
required: true,
check: async () => {
const files = await glob("app/**/*.ts");
const hasCSP = files.some((f) => {
const content = readFileSync(f, "utf-8");
return content.includes("frame-ancestors");
});
return hasCSP;
},
},
{
name: "API version is not unstable",
required: true,
check: async () => {
const toml = await readFile("shopify.app.toml", "utf-8");
return !toml.includes('api_version = "unstable"');
},
},
];
async function runComplianceChecks(): Promise<void> {
console.log("=== Shopify App Store Compliance Check ===\n");
let passed = 0;
let failed = 0;
for (const check of checks) {
const result = await check.check();
const status = result ? "PASS" : check.required ? "FAIL" : "WARN";
console.log(`${status}: ${check.name}`);
result ? passed++ : failed++;
}
console.log(`\n${passed} passed, ${failed} failed`);
if (failed > 0) process.exit(1);
}
Step 5: CI Policy Pipeline
# .github/workflows/shopify-policy.yml
name: Shopify Policy
on: [push, pull_request]
jobs:
policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Scan for hardcoded Shopify tokens
run: |
if grep -rE "shpat_[a-f0-9]{32}|shpss_[a-f0-9]{32}" \
--include="*.ts" --include="*.tsx" --include="*.js" \
app/ src/ ; then
echo "::error::Hardcoded Shopify tokens found!"
exit 1
fi
- name: Check GDPR webhooks configured
run: |
for topic in "customers/data_request" "customers/redact" "shop/redact"; do
if ! grep -q "$topic" shopify.app.toml; then
echo "::error::Missing mandatory GDPR webhook: $topic"
exit 1
fi
done
echo "All GDPR webhooks configured"
- name: Validate API version
run: |
VERSION=$(grep 'api_version' shopify.app.toml | head -1 | grep -oP '\d{4}-\d{2}')
if [ "$VERSION" = "unstable" ]; then
echo "::error::Cannot use unstable API version"
exit 1
fi
echo "API version: $VERSION"
Output
- ESLint rules catching hardcoded tokens
- Query cost budgets enforced
- Pre-commit hooks blocking secret leaks
- App Store compliance checker
- CI policy pipeline preventing violations
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| False positive on token | Base64 string matched | Narrow regex pattern |
| Query cost estimate wrong | Complex variable nesting | Use actual debug header in tests |
| Pre-commit bypassed | --no-verify flag |
Enforce in CI as backup |
| App Store rejection | Missing GDPR webhook | Run compliance checker before submit |
Examples
Quick Policy Scan
# One-liner: check for token leaks in staged changes
git diff --cached | grep -E "shpat_|shpss_" && echo "TOKEN LEAK!" || echo "Clean"
# Check GDPR compliance
grep -c "customers/data_request\|customers/redact\|shop/redact" shopify.app.toml
# Should output: 3
Resources
Next Steps
For architecture blueprints, see shopify-architecture-variants.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
dockerfile-generator
Dockerfile Generator - Auto-activating skill for DevOps Basics. Triggers on: dockerfile generator, dockerfile generator Part of the DevOps Basics skill category.
branch-naming-helper
Branch Naming Helper - Auto-activating skill for DevOps Basics. Triggers on: branch naming helper, branch naming helper Part of the DevOps Basics skill category.
readme-generator
Readme Generator - Auto-activating skill for DevOps Basics. Triggers on: readme generator, readme generator Part of the DevOps Basics skill category.
makefile-generator
Makefile Generator - Auto-activating skill for DevOps Basics. Triggers on: makefile generator, makefile generator Part of the DevOps Basics skill category.
gitignore-generator
Gitignore Generator - Auto-activating skill for DevOps Basics. Triggers on: gitignore generator, gitignore generator Part of the DevOps Basics skill category.
pre-commit-hook-setup
Pre Commit Hook Setup - Auto-activating skill for DevOps Basics. Triggers on: pre commit hook setup, pre commit hook setup Part of the DevOps Basics skill category.
Didn't find tool you were looking for?