From salesloft-pack
Secures SalesLoft API integrations with OAuth token rotation, webhook signature verification, secret storage, and minimal scopes.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin salesloft-packThis skill is limited to using the following tools:
Secure SalesLoft API integrations: OAuth token management, webhook signature verification, secret storage, and scope-based access control. SalesLoft uses OAuth 2.0 bearer tokens and HMAC-SHA256 webhook signatures.
Applies HubSpot security best practices: least-privilege scopes, private app token storage/validation, and v3 webhook signature verification with HMAC SHA-256.
Provides production readiness checklist for SalesLoft API integrations: auth, error handling, rate limits, monitoring, data integrity, rollback. For deploy/launch validation.
Secures Attio API integrations via least-privilege token scoping, platform secret management, webhook verification, and rotation procedures.
Share bugs, ideas, or general feedback.
Secure SalesLoft API integrations: OAuth token management, webhook signature verification, secret storage, and scope-based access control. SalesLoft uses OAuth 2.0 bearer tokens and HMAC-SHA256 webhook signatures.
# .gitignore -- NEVER commit credentials
.env
.env.local
.env.*.local
# .env
SALESLOFT_CLIENT_ID=app-client-id
SALESLOFT_CLIENT_SECRET=app-secret
SALESLOFT_WEBHOOK_SECRET=webhook-signing-secret
// Validate secrets at startup
const required = ['SALESLOFT_CLIENT_ID', 'SALESLOFT_CLIENT_SECRET'];
for (const key of required) {
if (!process.env[key]) throw new Error(`Missing required env: ${key}`);
}
// Store tokens securely with expiry tracking
interface TokenStore {
accessToken: string;
refreshToken: string;
expiresAt: number; // Unix timestamp
}
async function getValidToken(store: TokenStore): Promise<string> {
// Refresh 5 minutes before expiry
if (Date.now() > (store.expiresAt - 300) * 1000) {
const refreshed = await refreshAccessToken(store.refreshToken);
store.accessToken = refreshed.access_token;
store.refreshToken = refreshed.refresh_token;
store.expiresAt = Math.floor(Date.now() / 1000) + refreshed.expires_in;
await persistTokenStore(store); // Save to DB or secret manager
}
return store.accessToken;
}
import crypto from 'crypto';
function verifyWebhookSignature(
rawBody: Buffer,
signature: string,
timestamp: string,
secret: string,
): boolean {
// Reject stale webhooks (replay attack prevention)
const age = Math.abs(Date.now() / 1000 - parseInt(timestamp));
if (age > 300) return false; // 5-minute window
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody.toString()}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature), Buffer.from(expected)
);
}
| Use Case | Required Scopes | Avoid |
|---|---|---|
| Read-only dashboard | people:read, cadences:read | *:write |
| Cadence enrollment | people:read, cadence_memberships:create | admin |
| Full sync | people:*, cadences:*, activities:read | Team admin scopes |
.env files in .gitignore| Issue | Detection | Response |
|---|---|---|
| Token leaked in git | GitHub secret scanning alerts | Revoke immediately, rotate |
| Webhook replay attack | Timestamp > 5 min old | Reject request |
| Brute force on webhook | High 401 rate | Rate limit webhook endpoint |
For production deployment, see salesloft-prod-checklist.