---
name: jeremylongshore/figma-policy-guardrails
source: https://app.decimal.ai/s/jeremylongshore-figma-policy-guardrails@1/SKILL.md
source_sha256: eb3017bd34d8
---

# Figma Policy & Guardrails

## Overview

Automated guardrails for Figma API integrations: prevent token leaks, enforce scope minimization, validate webhook configurations, and catch common anti-patterns in CI.

## Prerequisites

- ESLint or similar linter
- CI/CD pipeline (GitHub Actions)
- Pre-commit hooks infrastructure

## Instructions

### Step 1: Token Leak Prevention

```bash
# .pre-commit-config.yaml -- catch Figma tokens before commit
repos:
  - repo: local
    hooks:
      - id: no-figma-tokens
        name: Check for Figma PAT leaks
        entry: bash -c '
          if git diff --cached --diff-filter=ACM -z -- . |
             xargs -0 grep -lP "figd_[a-zA-Z0-9_-]{20,}" 2>/dev/null; then
            echo "ERROR: Figma PAT found in staged files"
            echo "Store tokens in .env files (which should be in .gitignore)"
            exit 1
          fi
        '
        language: system
        pass_filenames: false
```

```yaml
# GitHub Actions secret scanning
# .github/workflows/figma-security.yml
name: Figma Security Check
on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Scan for Figma tokens
        run: |
          if grep -rP "figd_[a-zA-Z0-9_-]{20,}" \
            --include="*.ts" --include="*.js" --include="*.json" \
            --exclude-dir=node_modules .; then
            echo "::error::Figma PAT found in source code"
            exit 1
          fi

      - name: Check .env files not committed
        run: |
          if git ls-files --cached | grep -E '^\.(env|env\.local|env\.production)$'; then
            echo "::error::.env file committed to repository"
            exit 1
          fi
```

### Step 2: ESLint Rules for Figma

```javascript
// eslint-rules/no-figma-token-literal.js
module.exports = {
  meta: {
    type: 'problem',
    docs: { description: 'Disallow hardcoded Figma PATs' },
  },
  create(context) {
    return {
      Literal(node) {
        if (typeof node.value === 'string' && /^figd_[a-zA-Z0-9_-]{20,}/.test(node.value)) {
          context.report({
            node,
            message: 'Hardcoded Figma PAT detected. Use process.env.FIGMA_PAT instead.',
          });
        }
      },
      TemplateLiteral(node) {
        for (const quasi of node.quasis) {
          if (/figd_[a-zA-Z0-9_-]{20,}/.test(quasi.value.raw)) {
            context.report({
              node,
              message: 'Hardcoded Figma PAT in template literal.',
            });
          }
        }
      },
    };
  },
};
```

### Step 3: API Usage Policies

```typescript
// Runtime guardrails for Figma API usage

// Policy 1: No full-file fetches without justification
function validateFigmaRequest(path: string) {
  // Block unoptimized full file fetches
  if (path.match(/\/v1\/files\/[^/]+$/) && !path.includes('depth=')) {
    console.warn(
      '[figma-policy] Full file fetch without depth parameter. ' +
      'Use ?depth=1 or /nodes endpoint for better performance.'
    );
  }

  // Block deprecated scope indicator
  if (path.includes('files:read')) {
    throw new Error(
      '[figma-policy] files:read scope is deprecated. ' +
      'Use file_content:read instead.'
    );
  }
}

// Policy 2: Enforce timeout on all Figma calls
function validateTimeout(options: RequestInit) {
  if (!options.signal) {
    console.warn(
      '[figma-policy] Figma request without AbortSignal. ' +
      'Use AbortSignal.timeout() to prevent hung requests.'
    );
  }
}

// Policy 3: Rate limit safety margin
const MAX_REQUESTS_PER_MINUTE = 25; // Conservative limit
let requestsThisMinute = 0;
let minuteStart = Date.now();

function enforceRatePolicy() {
  if (Date.now() - minuteStart > 60_000) {
    requestsThisMinute = 0;
    minuteStart = Date.now();
  }

  requestsThisMinute++;
  if (requestsThisMinute > MAX_REQUESTS_PER_MINUTE) {
    throw new Error(
      `[figma-policy] Rate limit safety: ${requestsThisMinute} requests/min ` +
      `exceeds policy limit of ${MAX_REQUESTS_PER_MINUTE}`
    );
  }
}
```

### Step 4: Configuration Validation

```typescript
// Validate Figma config at startup, fail fast if misconfigured
function validateFigmaConfig() {
  const errors: string[] = [];

  // Token format
  const pat = process.env.FIGMA_PAT;
  if (!pat) {
    errors.push('FIGMA_PAT is not set');
  } else if (!pat.startsWith('figd_')) {
    errors.push('FIGMA_PAT does not have expected figd_ prefix');
  }

  // File key format
  const fileKey = process.env.FIGMA_FILE_KEY;
  if (!fileKey) {
    errors.push('FIGMA_FILE_KEY is not set');
  } else if (fileKey.length < 10) {
    errors.push('FIGMA_FILE_KEY seems too short');
  }

  // Webhook passcode (if webhooks are configured)
  if (process.env.FIGMA_WEBHOOK_ENABLED === 'true') {
    if (!process.env.FIGMA_WEBHOOK_PASSCODE) {
      errors.push('FIGMA_WEBHOOK_PASSCODE required when webhooks are enabled');
    } else if (process.env.FIGMA_WEBHOOK_PASSCODE.length < 16) {
      errors.push('FIGMA_WEBHOOK_PASSCODE should be at least 16 characters');
    }
  }

  if (errors.length > 0) {
    console.error('[figma-policy] Configuration errors:');
    errors.forEach(e => console.error(`  - ${e}`));
    throw new Error(`Figma configuration invalid: ${errors.length} errors`);
  }

  console.log('[figma-policy] Configuration validated');
}

// Call at startup
validateFigmaConfig();
```

### Step 5: Audit Logging

```typescript
// Log all Figma API operations for compliance
interface FigmaAuditEntry {
  timestamp: string;
  action: string;
  endpoint: string;
  fileKey?: string;
  status: number;
  userId?: string;
}

function auditFigmaCall(entry: Omit<FigmaAuditEntry, 'timestamp'>) {
  const log: FigmaAuditEntry = {
    ...entry,
    timestamp: new Date().toISOString(),
  };

  // Structured log for aggregation
  console.log(JSON.stringify({ type: 'figma_audit', ...log }));
}
```

## Output

- Pre-commit hooks catching token leaks
- CI pipeline scanning for hardcoded credentials
- Runtime policies enforcing performance best practices
- Configuration validation at startup
- Audit logging for compliance

## Error Handling

| Issue | Cause | Solution |
|-------|-------|----------|
| False positive on token scan | Test fixture contains figd_ | Exclude test fixtures directory |
| Policy blocks legitimate request | Too restrictive | Add exception list for specific paths |
| Startup validation fails | Missing env vars | Check deployment config |
| Audit log noise | Too many entries | Filter to write operations only |

## Examples

Catch a hardcoded Figma PAT before it reaches a commit (Step 1 pre-commit hook):

```bash
echo 'const PAT = "figd_AbCdEf123456789";' > leak.ts
git add leak.ts && git commit -m "test"
# pre-commit: BLOCKED — figd_ token detected in leak.ts
```

Lint a violation of the API usage policy (Step 2 ESLint rule flags full-file fetches):

```text
src/sync.ts
  14:3  error  Figma file fetch without depth parameter — use ?depth=1
                or /nodes?ids=... (figma/no-unbounded-file-fetch)
```

Policy rule catalog and the audit-log event shape: `references/api-usage-policies.md` and `references/audit-logging.md`.

## Resources

- [Figma API Scopes](https://developers.figma.com/docs/rest-api/scopes/)
- [Pre-commit Framework](https://pre-commit.com/)
- [ESLint Custom Rules](https://eslint.org/docs/latest/extend/custom-rules)

## Next Steps

For architecture blueprints, see `figma-architecture-variants`.