Skillshub apify-security-basics
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/apify-security-basics" ~/.claude/skills/comeonoliver-skillshub-apify-security-basics && rm -rf "$T"
manifest:
skills/jeremylongshore/claude-code-plugins-plus-skills/apify-security-basics/SKILL.mdsource content
Apify Security Basics
Overview
Security best practices for Apify API tokens, Actor data, proxy credentials, and webhook verification. Apify uses personal API tokens (prefixed
apify_api_) for all authentication.
Prerequisites
- Apify account with Console access
- Understanding of environment variables
- Access to your deployment platform's secrets management
Token Architecture
Apify uses a single API token per user account for full API access. There is no scope-based permission system per token, so token security is critical.
| Token Type | Format | Where to Find |
|---|---|---|
| Personal API token | | Console > Settings > Integrations |
| Proxy password | Alphanumeric | Console > Proxy > Connection settings |
Instructions
Step 1: Secure Token Storage
# .env (NEVER commit — must be in .gitignore) APIFY_TOKEN=apify_api_YOUR_TOKEN_HERE # .gitignore — mandatory entries .env .env.local .env.*.local storage/ # Local Apify storage may contain scraped data
// Validate token exists at startup function requireToken(): string { const token = process.env.APIFY_TOKEN; if (!token) { throw new Error( 'APIFY_TOKEN is required. Get yours at ' + 'https://console.apify.com/account/integrations' ); } if (!token.startsWith('apify_api_')) { console.warn('Warning: APIFY_TOKEN does not have expected prefix'); } return token; }
Step 2: Per-Environment Token Isolation
Use separate Apify accounts (or at minimum separate tokens) per environment:
# Development — your personal account APIFY_TOKEN=apify_api_dev_token # Staging — shared team account (limited usage) APIFY_TOKEN=apify_api_staging_token # Production — production account (separate billing) APIFY_TOKEN=apify_api_prod_token
Platform secrets management:
# GitHub Actions gh secret set APIFY_TOKEN --body "apify_api_prod_token" # Vercel vercel env add APIFY_TOKEN production # Google Cloud Secret Manager echo -n "apify_api_prod_token" | \ gcloud secrets create apify-token --data-file=-
Step 3: Token Rotation Procedure
# 1. Generate new token in Console > Settings > Integrations # (old token remains valid until explicitly revoked) # 2. Update in all environments gh secret set APIFY_TOKEN --body "apify_api_NEW_TOKEN" # 3. Verify new token works curl -sf -H "Authorization: Bearer $NEW_TOKEN" \ https://api.apify.com/v2/users/me | jq '.data.username' # 4. Revoke old token in Console # Settings > Integrations > (regenerate invalidates old token)
Step 4: Webhook Payload Verification
Apify webhooks include run data in the POST body. Verify the source:
import crypto from 'crypto'; import { type Request, type Response } from 'express'; // Apify doesn't sign webhooks by default, but you can verify // by checking that the run ID in the payload actually exists async function verifyWebhookPayload( payload: { eventData: { actorRunId: string } }, client: ApifyClient, ): Promise<boolean> { try { const run = await client.run(payload.eventData.actorRunId).get(); return run !== null && run !== undefined; } catch { return false; } } // Alternatively, use a shared secret in your webhook URL // https://your-server.com/webhook?secret=YOUR_WEBHOOK_SECRET function verifyWebhookSecret(req: Request): boolean { const secret = req.query.secret as string; if (!secret || !process.env.APIFY_WEBHOOK_SECRET) return false; return crypto.timingSafeEqual( Buffer.from(secret), Buffer.from(process.env.APIFY_WEBHOOK_SECRET), ); }
Step 5: Actor Data Security
// Sanitize sensitive data before pushing to datasets function sanitizeForDataset(item: Record<string, unknown>): Record<string, unknown> { const sensitiveFields = ['email', 'phone', 'password', 'ssn', 'creditCard']; const sanitized = { ...item }; for (const field of sensitiveFields) { if (field in sanitized) { sanitized[field] = '***REDACTED***'; } } return sanitized; } // Use named datasets with access control // Only your account can access your datasets by default // Public datasets require explicit sharing via API
Step 6: Proxy Security
// Never log or expose proxy URLs (they contain credentials) const proxyConfig = await Actor.createProxyConfiguration({ groups: ['RESIDENTIAL'], countryCode: 'US', }); // DO NOT do this: // console.log(await proxyConfig.newUrl()); // Leaks proxy password! // Instead, log proxy group info only console.log(`Using proxy group: ${proxyConfig.groups?.join(', ')}`);
Security Checklist
-
stored in environment variables (never hardcoded)APIFY_TOKEN -
and.env
instorage/.gitignore - Separate tokens for dev/staging/prod
- Token rotation schedule documented
- Webhook endpoints verify source
- Proxy URLs never logged
- Scraped PII redacted before storage
- Named datasets used for sensitive data (no public sharing)
- CI/CD secrets configured (not in repo)
Leaked Token Response
If a token is exposed:
- Immediately regenerate token in Console > Settings > Integrations
- Check recent Actor runs for unauthorized usage
- Review billing for unexpected charges
- Rotate proxy password if exposed
- Audit git history:
git log --all -p -- '*.env' '*.json' | grep apify_api_
Error Handling
| Issue | Detection | Mitigation |
|---|---|---|
| Token in git history | | Rotate token, use BFG to clean |
| Unauthorized runs | Unexpected runs in Console | Rotate token immediately |
| Proxy password exposed | Credentials in logs | Regenerate proxy password |
| Data breach in dataset | PII in public dataset | Delete dataset, sanitize pipeline |
Resources
Next Steps
For production deployment, see
apify-prod-checklist.