Skillshub canva-policy-guardrails
install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/jeremylongshore/claude-code-plugins-plus-skills/canva-policy-guardrails" ~/.claude/skills/comeonoliver-skillshub-canva-policy-guardrails && rm -rf "$T"
manifest:
skills/jeremylongshore/claude-code-plugins-plus-skills/canva-policy-guardrails/SKILL.mdsource content
Canva Policy & Guardrails
Overview
Automated policy enforcement for Canva Connect API integrations — prevent token leaks, enforce rate limit handling, require error handling, and validate OAuth configuration.
ESLint Rules
No Hardcoded Credentials
// eslint-rules/no-canva-credentials.js module.exports = { meta: { type: 'problem', docs: { description: 'Disallow hardcoded Canva OAuth credentials' }, }, create(context) { return { Literal(node) { if (typeof node.value !== 'string') return; const val = node.value; // Canva client IDs start with "OCA" if (/^OCA[A-Za-z0-9]{10,}/.test(val)) { context.report({ node, message: 'Hardcoded Canva client ID detected. Use environment variable.' }); } // Canva access tokens start with "cnvat_" if (/^cnvat_[A-Za-z0-9]{20,}/.test(val)) { context.report({ node, message: 'Hardcoded Canva access token detected. Use environment variable.' }); } }, }; }, };
Require Rate Limit Handling
// eslint-rules/require-canva-retry.js module.exports = { meta: { type: 'suggestion', docs: { description: 'Canva API calls should handle 429 responses' }, }, create(context) { return { CallExpression(node) { // Check for fetch calls to api.canva.com if (node.callee.name === 'fetch' && node.arguments[0]?.value?.includes('api.canva.com')) { // Check if parent is try-catch or has .catch() const parent = node.parent; if (parent.type !== 'AwaitExpression' || parent.parent?.type !== 'TryStatement') { context.report({ node, message: 'Canva API calls should be wrapped in try-catch with 429 handling', }); } } }, }; }, };
Pre-Commit Hooks
# .pre-commit-config.yaml repos: - repo: local hooks: - id: canva-no-tokens name: Check for Canva tokens entry: bash -c 'git diff --cached --name-only | xargs grep -lE "(cnvat_|OCA[A-Z0-9]{10})" 2>/dev/null && echo "ERROR: Canva credentials found" && exit 1 || exit 0' language: system pass_filenames: false - id: canva-no-raw-urls name: Check for hardcoded Canva API URLs entry: bash -c 'git diff --cached --name-only | xargs grep -lE "api\.canva\.com/rest/v1" --include="*.ts" --include="*.js" 2>/dev/null | while read f; do grep -n "api\.canva\.com" "$f" | grep -v "const.*BASE\|const.*URL\|import\|from\|//" && echo "WARNING: Direct Canva URL in $f — use client wrapper" && exit 1; done; exit 0' language: system pass_filenames: false
CI Policy Checks
# .github/workflows/canva-policy.yml name: Canva Policy Check on: [push, pull_request] jobs: policy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Check for hardcoded credentials run: | if grep -rE "(cnvat_[a-zA-Z0-9]{20,}|OCA[A-Z0-9]{10,})" \ --include="*.ts" --include="*.js" --include="*.json" \ --exclude-dir=node_modules .; then echo "ERROR: Hardcoded Canva credentials found" exit 1 fi - name: Check for unhandled API calls run: | # Warn if raw fetch to Canva without error handling DIRECT_CALLS=$(grep -rn "fetch.*api\.canva\.com" \ --include="*.ts" --include="*.js" \ --exclude="*client.ts" --exclude="*test*" \ . 2>/dev/null | wc -l) if [ "$DIRECT_CALLS" -gt 0 ]; then echo "WARNING: $DIRECT_CALLS direct Canva API calls found outside client wrapper" grep -rn "fetch.*api\.canva\.com" --include="*.ts" --include="*.js" \ --exclude="*client.ts" --exclude="*test*" . fi - name: Validate .env.example run: | if [ -f .env.example ]; then for var in CANVA_CLIENT_ID CANVA_CLIENT_SECRET CANVA_REDIRECT_URI; do if ! grep -q "^${var}=" .env.example; then echo "WARNING: ${var} missing from .env.example" fi done fi
Runtime Guardrails
// Prevent dangerous operations and enforce patterns at runtime class CanvaGuardrails { // Block requests without proper authorization header static validateRequest(init: RequestInit): void { const authHeader = (init.headers as Record<string, string>)?.['Authorization']; if (!authHeader || !authHeader.startsWith('Bearer ')) { throw new Error('Canva API call missing Bearer token'); } } // Enforce token is not expired before making call static validateToken(expiresAt: number): void { if (Date.now() > expiresAt) { throw new Error('Canva access token expired — refresh before calling'); } } // Block sensitive operations based on environment static validateEnvironment(operation: string): void { const blocked = ['deleteAsset', 'deleteFolder']; if (process.env.NODE_ENV !== 'production' && blocked.includes(operation)) { // Allow in dev for testing return; } // In production, require explicit confirmation if (process.env.NODE_ENV === 'production' && blocked.includes(operation)) { console.warn(`[guardrail] Destructive operation: ${operation}`); } } // Rate limit self-check — don't send if we know we'll get 429 static checkRateLimit( endpoint: string, tracker: Map<string, { count: number; resetAt: number }> ): void { const window = tracker.get(endpoint); if (window && window.count <= 0 && Date.now() < window.resetAt) { const waitMs = window.resetAt - Date.now(); throw new Error(`Rate limit exhausted for ${endpoint}. Wait ${(waitMs / 1000).toFixed(0)}s`); } } }
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| ESLint rule not firing | Wrong config path | Check plugin registration |
| Pre-commit skipped | used | Enforce in CI |
| False positive on "OCA" | String matches pattern | Narrow regex or add allowlist |
| Guardrail blocks valid op | Too strict | Add environment-based exceptions |
Resources
Next Steps
For architecture blueprints, see
canva-architecture-variants.