본문으로 건너뛰기
cccskills
무료GitHub에 공개

adobe-known-pitfalls

Identify and avoid Adobe-specific anti-patterns: using deprecated JWT auth, not caching IMS tokens, ignoring Firefly content policy, missing async job polling, and leaking p8_ secrets. Real code examples with fixes. Trigger with phrases like "adobe mistakes", "adobe anti-patterns", "adobe pitfalls", "adobe what not to do", "adobe code review".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(2)

  • SKILL.md10.4 KB
  • source.json894 B

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Adobe Known Pitfalls

Overview

The 10 most common mistakes when integrating with Adobe APIs, based on real production issues. Each pitfall includes the anti-pattern, why it fails, and the correct approach.

Prerequisites

  • Access to your Adobe integration codebase
  • Understanding of Adobe API architecture (OAuth, async jobs, rate limits)

Instructions

Pitfall 1: Still Using JWT (Service Account) Credentials

Status: CRITICAL — JWT credentials reached End of Life June 2025.

// WRONG: JWT auth (no longer works as of 2025)
import jwt from 'jsonwebtoken';
import fs from 'fs';

const privateKey = fs.readFileSync('private.key');
const jwtToken = jwt.sign({
  exp: Math.round(Date.now() / 1000) + 86400,
  iss: orgId,
  sub: technicalAccountId,
  aud: `https://ims-na1.adobelogin.com/c/${clientId}`,
}, privateKey, { algorithm: 'RS256' });

// RIGHT: OAuth Server-to-Server (current standard)
const res = await fetch('https://ims-na1.adobelogin.com/ims/token/v3', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    client_id: process.env.ADOBE_CLIENT_ID!,
    client_secret: process.env.ADOBE_CLIENT_SECRET!,
    grant_type: 'client_credentials',
    scope: process.env.ADOBE_SCOPES!,
  }),
});

Pitfall 2: Not Caching IMS Access Tokens

IMS tokens are valid for 24 hours. Generating a new token per request wastes 200-500ms:

// WRONG: New token every request (200-500ms overhead each time)
async function callFirefly(prompt: string) {
  const tokenRes = await fetch('https://ims-na1.adobelogin.com/ims/token/v3', { ... });
  const { access_token } = await tokenRes.json();
  // ... use access_token
}

// RIGHT: Cache token with expiry check
let cached: { token: string; expiresAt: number } | null = null;

async function getToken(): Promise<string> {
  if (cached && cached.expiresAt > Date.now() + 300_000) return cached.token;
  const res = await fetch('https://ims-na1.adobelogin.com/ims/token/v3', { ... });
  const data = await res.json();
  cached = { token: data.access_token, expiresAt: Date.now() + data.expires_in * 1000 };
  return cached.token;
}

Pitfall 3: Using Firefly Sync Endpoint for Batch Operations

// WRONG: Sequential sync calls (each blocks 5-20s)
for (const prompt of prompts) {
  const result = await fetch('https://firefly-api.adobe.io/v3/images/generate', {
    method: 'POST', ...
  });
  results.push(await result.json());
}
// Total time: N * 5-20s = very slow

// RIGHT: Async endpoint with parallel submission
const jobs = await Promise.all(
  prompts.map(prompt =>
    fetch('https://firefly-api.adobe.io/v3/images/generate-async', {
      method: 'POST', ...
    }).then(r => r.json())
  )
);
// Poll all jobs in parallel
const results = await Promise.all(jobs.map(j => pollJob(j.statusUrl)));
// Total time: max(5-20s) = much faster

Pitfall 4: Ignoring Firefly Content Policy Errors

// WRONG: Treat all 400 errors the same
try {
  const result = await generateImage({ prompt: 'Photo of Nike shoes' });
} catch (e) {
  console.log('Generation failed');  // No idea why
}

// RIGHT: Handle content policy specifically
try {
  const result = await generateImage({ prompt });
} catch (e: any) {
  if (e.status === 400 && e.message?.includes('content policy')) {
    // Save the credit — don't retry, fix the prompt
    throw new Error(
      'Firefly content policy violation. ' +
      'Remove trademarks, real people, or explicit content from prompt.'
    );
  }
  throw e; // Other errors might be retryable
}

Pitfall 5: Uploading Files Directly to Photoshop/Lightroom API

// WRONG: Trying to upload file directly (not supported)
const formData = new FormData();
formData.append('image', fs.readFileSync('photo.jpg'));
await fetch('https://image.adobe.io/v2/remove-background', {
  method: 'POST',
  body: formData,  // Photoshop API doesn't accept direct uploads
});

// RIGHT: Use pre-signed cloud storage URLs
const inputUrl = await s3.getSignedUrl('getObject', {
  Bucket: 'my-bucket', Key: 'photo.jpg', Expires: 3600,
});
const outputUrl = await s3.getSignedUrl('putObject', {
  Bucket: 'my-bucket', Key: 'output.png', Expires: 3600,
});

await fetch('https://image.adobe.io/v2/remove-background', {
  method: 'POST',
  headers: { Authorization: `Bearer ${token}`, 'x-api-key': clientId, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    input: { href: inputUrl, storage: 'external' },
    output: { href: outputUrl, storage: 'external', type: 'image/png' },
  }),
});

Pitfall 6: Not Polling Async Job Status

Photoshop and Lightroom APIs return immediately with a job ID. You must poll for results:

// WRONG: Treating response as the final result
const res = await fetch('https://image.adobe.io/v2/remove-background', { ... });
const result = await res.json();
console.log('Done!', result);  // result is just { _links: { self: { href: ... } } }

// RIGHT: Poll the status URL until completion
const submission = await res.json();
let job;
do {
  await new Promise(r => setTimeout(r, 2000));
  const pollRes = await fetch(submission._links.self.href, {
    headers: { Authorization: `Bearer ${token}`, 'x-api-key': clientId },
  });
  job = await pollRes.json();
} while (job.status !== 'succeeded' && job.status !== 'failed');

if (job.status === 'failed') throw new Error(job.error?.message);

Pitfall 7: Leaking Adobe Credentials in Source Code

// WRONG: Hardcoded credentials (Adobe OAuth secrets start with p8_)
const client_secret = 'p8_XYZ_your_actual_secret_here_do_not_do_this';

// WRONG: Committed .env file
// git add .env && git commit -m "add config"

// RIGHT: Environment variables + .gitignore
const client_secret = process.env.ADOBE_CLIENT_SECRET!;
// .gitignore includes: .env, .env.local, .env.*.local

Pitfall 8: Not Handling PDF Services Quota

// WRONG: No quota awareness (free tier = 500 tx/month)
async function extractAllPdfs(paths: string[]) {
  for (const path of paths) {
    await extractPdf(path);  // Silently fails after 500th call
  }
}

// RIGHT: Track and enforce quota
let txCount = 0;
async function trackedExtract(path: string) {
  if (txCount >= 490) {  // Leave buffer
    throw new Error('Approaching PDF Services monthly limit. 10 transactions remaining.');
  }
  const result = await extractPdf(path);
  txCount++;
  return result;
}

Pitfall 9: Using Deprecated Photoshop Endpoints

// WRONG: v1 endpoint (deprecated)
await fetch('https://image.adobe.io/sensei/cutout', { ... });

// RIGHT: v2 endpoint (current)
await fetch('https://image.adobe.io/v2/remove-background', { ... });

Pitfall 10: Missing Webhook Signature Verification

// WRONG: Trust any incoming request (attackers can forge events)
app.post('/webhooks/adobe', (req, res) => {
  processEvent(req.body);
  res.sendStatus(200);
});

// RIGHT: Verify RSA-SHA256 signature from Adobe I/O Events
app.post('/webhooks/adobe', express.raw({ type: 'application/json' }), async (req, res) => {
  // Adobe uses RSA-SHA256 digital signatures (NOT HMAC)
  const sig = req.headers['x-adobe-digital-signature-1'];
  const keyPath = req.headers['x-adobe-public-key1-path'];

  const publicKey = await fetch(`https://static.adobeioevents.com${keyPath}`).then(r => r.text());
  const verifier = crypto.createVerify('RSA-SHA256');
  verifier.update(req.body);

  if (!verifier.verify(publicKey, sig, 'base64')) {
    return res.sendStatus(401);
  }

  processEvent(JSON.parse(req.body.toString()));
  res.sendStatus(200);
});

Quick Pitfall Scanner

# Run against your codebase
echo "=== Adobe Pitfall Scan ==="

# 1. JWT credentials (deprecated)
grep -rn "jsonwebtoken\|jwt\.sign\|RS256" --include="*.ts" --include="*.js" src/ && echo "FOUND: JWT auth (deprecated)" || echo "OK: No JWT"

# 2. Uncached token generation
grep -rn "ims/token/v3" --include="*.ts" src/ | wc -l | xargs -I{} echo "Token endpoint calls: {} (should be 1 — in auth.ts only)"

# 3. Hardcoded secrets
grep -rn "p8_" --include="*.ts" --include="*.js" src/ && echo "FOUND: Hardcoded Adobe secret" || echo "OK: No hardcoded secrets"

# 4. Deprecated endpoints
grep -rn "sensei/cutout" --include="*.ts" src/ && echo "FOUND: Deprecated Photoshop endpoint" || echo "OK: No deprecated endpoints"

# 5. Missing webhook verification
grep -rn "webhooks/adobe" --include="*.ts" src/ | grep -v "digital-signature\|verify\|RSA" && echo "WARNING: Webhook handler may lack signature verification"

Quick Reference Card

PitfallRiskDetectionFix
JWT authBroken authGrep for jwt.signMigrate to OAuth S2S
No token cachePerf (-500ms/req)Multiple ims/token callsCache with expiry
Sync Firefly for batchSlow (N*20s)Sequential generate callsUse async endpoint
Ignore content policyWasted creditsCatch 400 without reasonPre-screen prompts
Direct file upload400 errorsFormData to PhotoshopPre-signed URLs
No job pollingMissing resultsNo poll loop after submitPoll _links.self
Leaked p8_ secretCredential compromiseGrep for p8_Env vars + .gitignore
No quota trackingSilent failuresNo counterTrack per-month usage
Old PS endpoint404 errors/sensei/cutout/v2/remove-background
No webhook verifySecurity holeNo signature checkRSA-SHA256 verification

Resources

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

007

무료

Security audit, hardening, threat modeling (STRIDE/PASTA), Red/Blue Team, OWASP checks, code review, incident response, and infrastructure security for any project.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

aibot88/sec_skill_store52026년 5월 27일 업데이트

Guides the creation of agile user stories and Gherkin feature files. Use when the user wants to create a user story, write acceptance criteria, define Gherkin scenarios, or author BDD feature files. This should trigger for requests such as Create a user story; Write a user story; I need to write a user story. Part of cursor-rules-java project

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

aibot88/sec_skill_store52026년 5월 27일 업데이트

Facilitates conversational discovery to create Architectural Decision Records (ADRs) for non-functional requirements using the ISO/IEC 25010:2023 quality model. Use when the user wants to document quality attributes, NFR decisions, security/performance/scalability architecture, or design systems with measurable quality criteria. This should trigger for requests such as Create ADR for Non-functional requirements; Document Non-functional requirements; Capture Non-functional requirements; Generate Non-functional requirements in an ADR. Part of cursor-rules-java project

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

aibot88/sec_skill_store52026년 5월 27일 업데이트

Run a health check on an existing project: dependency audit, security scan, test runner detection, CI/CD evaluation, and missing configuration analysis. Maps the three execution gates (pre/in/post) from /10x-bootstrapper to an assessment framework for existing codebases. Reads optional context/foundation/stack-assessment.md from /10x-stack-assess to focus checks on identified gaps. Writes context/foundation/health-check.md with findings, prioritized fixes, and an agent-readiness verdict. Use when the user has an existing project and wants to verify its health before working with an agent. Trigger phrases: "health check", "check my project", "audit my project", "is my project healthy", "sprawdź projekt", "audyt projektu", "health-check", "project health". Use AFTER /10x-stack-assess (brownfield chain), BEFORE agent onboarding (m1-l4).

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

aibot88/sec_skill_store52026년 5월 27일 업데이트

10x-team

무료

You MUST use this when building projects end-to-end. Orchestrates all 12 team roles — automatically switches between CTO, architect, PM, engineers, SRE, security, DBA, QA, and EM based on the current phase of work. Starts with brainstorming before any implementation.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

aibot88/sec_skill_store52026년 5월 27일 업데이트

Use when you need to add or configure Maven plugins in your pom.xml — including quality tools (enforcer, surefire, failsafe, jacoco, pitest, spotbugs, pmd), security scanning (OWASP), code formatting (Spotless), version management, container image build (Jib), build information tracking, and benchmarking (JMH) — through a consultative, modular step-by-step approach that only adds what you actually need. This should trigger for requests such as Add Maven plugins in pom.xml; Improve Maven plugins in pom.xml. Part of cursor-rules-java project

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

aibot88/sec_skill_store52026년 5월 27일 업데이트

aibot88의 스킬 모두 보기

이 스킬의 문제 신고