--- name: adobe-policy-guardrails description: 'Implement Adobe-specific lint rules, CI policy checks, and runtime guardrails covering credential scanning (p8_ patterns), Firefly content policy pre-screening, PDF Services quota enforcement, and OAuth scope validation. Trigger with phrases like "adobe policy", "adobe lint", "adobe guardrails", "adobe eslint", "adobe content policy". ' allowed-tools: Read, Write, Edit, Bash(npx:*) version: 1.0.0 license: MIT author: Jeremy Longshore tags: - saas - design - adobe compatibility: Designed for Claude Code --- # Adobe Policy & Guardrails ## Overview Automated policy enforcement for Adobe integrations: credential pattern scanning (Adobe OAuth secrets use `p8_` prefix), Firefly content policy pre-screening, PDF Services quota guardrails, and OAuth scope validation. ## Prerequisites - ESLint configured in project - CI/CD pipeline (GitHub Actions) - Understanding of Adobe credential patterns ## Instructions ### Guardrail 1: Adobe Credential Pattern Scanner Adobe OAuth Server-to-Server secrets follow the `p8_` prefix pattern: ```yaml # .github/workflows/adobe-security.yml name: Adobe Security Scan on: [push, pull_request] jobs: credential-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Scan for Adobe credential patterns run: | EXIT_CODE=0 # Adobe OAuth client secrets (p8_ prefix) if grep -rE "p8_[A-Za-z0-9_-]{20,}" --include="*.ts" --include="*.js" --include="*.py" --include="*.json" --include="*.yaml" --include="*.yml" . 2>/dev/null | grep -v node_modules | grep -v '.git'; then echo "::error::Adobe client_secret pattern (p8_) found in source code" EXIT_CODE=1 fi # Adobe IMS access tokens (JWT format) if grep -rE "eyJ[A-Za-z0-9_-]{100,}\.[A-Za-z0-9_-]{100,}" --include="*.ts" --include="*.js" . 2>/dev/null | grep -v node_modules | grep -v '.git' | grep -v '\.test\.' | grep -v '__mock'; then echo "::warning::Potential Adobe access token found in source (may be test fixture)" fi # Org IDs (format: HEXSTRING@AdobeOrg) if grep -rE "[A-F0-9]{24}@AdobeOrg" --include="*.ts" --include="*.js" --include="*.json" . 2>/dev/null | grep -v node_modules | grep -v '.git' | grep -v '.env.example'; then echo "::warning::Adobe Org ID found in source — consider using env var" fi exit $EXIT_CODE ``` ### Guardrail 2: Firefly Content Policy Pre-Screener ```typescript // src/adobe/guardrails/content-policy.ts // Pre-screen prompts before sending to Firefly API to avoid wasted credits interface ContentPolicyResult { allowed: boolean; violations: string[]; suggestions: string[]; } const CONTENT_RULES = [ { name: 'real-people', pattern: /\b(photo of|portrait of|picture of)\s+(a\s+)?(real|actual|specific)\s+(person|man|woman|child)/i, message: 'Firefly cannot generate images of specific real people', suggestion: 'Use generic descriptions like "a professional in a business suit"', }, { name: 'trademarks', pattern: /\b(nike|adidas|apple|google|microsoft|disney|marvel|coca.?cola|pepsi|starbucks|mcdonalds)\b/i, message: 'Firefly will reject prompts containing brand trademarks', suggestion: 'Use generic descriptions like "athletic shoes" or "tech company logo style"', }, { name: 'explicit-content', pattern: /\b(nude|naked|explicit|pornograph|gore|violent|bloody|graphic death)\b/i, message: 'Firefly rejects explicit or violent content', suggestion: 'Use appropriate imagery descriptions', }, { name: 'celebrity', pattern: /\b(celebrity|famous|actor|actress|politician|president|singer|musician)\s+(name|like|resembling)/i, message: 'Firefly cannot generate images of identifiable celebrities', suggestion: 'Describe the style or aesthetic without naming individuals', }, ]; export function screenFireflyPrompt(prompt: string): ContentPolicyResult { const violations: string[] = []; const suggestions: string[] = []; for (const rule of CONTENT_RULES) { if (rule.pattern.test(prompt)) { violations.push(`[${rule.name}] ${rule.message}`); suggestions.push(rule.suggestion); } } return { allowed: violations.length === 0, violations, suggestions, }; } // Usage in API layer export function guardFireflyPrompt(prompt: string): void { const result = screenFireflyPrompt(prompt); if (!result.allowed) { throw new Error( `Firefly content policy pre-check failed:\n` + result.violations.join('\n') + '\n\nSuggestions:\n' + result.suggestions.join('\n') ); } } ``` ### Guardrail 3: PDF Services Quota Enforcement ```typescript // src/adobe/guardrails/pdf-quota.ts // Enforce PDF Services monthly transaction limits class PdfQuotaGuard { private monthlyLimit: number; private transactionsUsed: number = 0; private monthStart: Date; constructor(tier: 'free' | 'paid' = 'free') { this.monthlyLimit = tier === 'free' ? 500 : Infinity; this.monthStart = new Date(new Date().getFullYear(), new Date().getMonth(), 1); } check(): { allowed: boolean; remaining: number; warning: boolean } { // Reset counter on new month const now = new Date(); if (now.getMonth() !== this.monthStart.getMonth()) { this.transactionsUsed = 0; this.monthStart = new Date(now.getFullYear(), now.getMonth(), 1); } const remaining = this.monthlyLimit - this.transactionsUsed; return { allowed: remaining > 0, remaining, warning: remaining < this.monthlyLimit * 0.2, }; } record(): void { const status = this.check(); if (!status.allowed) { throw new Error(`PDF Services quota exhausted (${this.monthlyLimit} transactions/month)`); } this.transactionsUsed++; if (status.warning) { console.warn(`PDF Services: ${status.remaining - 1} transactions remaining this month`); } } } export const pdfQuota = new PdfQuotaGuard( process.env.ADOBE_PDF_TIER === 'paid' ? 'paid' : 'free' ); ``` ### Guardrail 4: OAuth Scope Validation ```typescript // Verify that the requested scopes match what the environment should use function validateAdobeScopes(scopes: string, environment: string): void { const scopeList = scopes.split(',').map(s => s.trim()); // Development should only have minimal scopes if (environment === 'development') { const prodOnlyScopes = ['ff_apis']; const violations = scopeList.filter(s => prodOnlyScopes.includes(s)); if (violations.length > 0) { console.warn(`Adobe scope warning: ${violations.join(', ')} should not be in development`); } } // Required scopes that should always be present const required = ['openid', 'AdobeID']; const missing = required.filter(s => !scopeList.includes(s)); if (missing.length > 0) { throw new Error(`Adobe required scopes missing: ${missing.join(', ')}`); } } ``` ### Guardrail 5: Runtime Operation Guard ```typescript // Prevent dangerous operations based on environment const BLOCKED_IN_PROD: Record = { 'delete-all-assets': 'Mass deletion blocked in production', 'reset-quota-counter': 'Quota reset blocked in production', 'use-test-credentials': 'Test credentials blocked in production', }; function guardAdobeOperation(operation: string): void { const isProd = process.env.NODE_ENV === 'production'; if (isProd && BLOCKED_IN_PROD[operation]) { throw new Error(`BLOCKED: ${BLOCKED_IN_PROD[operation]}`); } } ``` ## Output - CI secret scanning for Adobe credential patterns (`p8_`, JWTs, Org IDs) - Firefly prompt pre-screening avoiding wasted credits on policy violations - PDF Services quota enforcement with monthly tracking - OAuth scope validation per environment - Runtime operation guards for production safety ## Error Handling | Issue | Cause | Solution | |-------|-------|----------| | Secret scan false positive | Test fixture contains pattern | Exclude test dirs from scan | | Prompt wrongly rejected | Pattern too broad | Refine regex; allow legitimate uses | | Quota counter reset | Server restart | Persist counter in Redis/DB | | Scope validation fails | Wrong env var | Check `NODE_ENV` and `ADOBE_SCOPES` | ## Resources - [Firefly Content Policy](https://developer.adobe.com/firefly-services/docs/firefly-api/) - [PDF Services Pricing](https://developer.adobe.com/document-services/pricing/main/) - [OAuth Server-to-Server Scopes](https://developer.adobe.com/developer-console/docs/guides/authentication/ServerToServerAuthentication/implementation) ## Next Steps For architecture blueprints, see `adobe-architecture-variants`.