Agent Skills: HubSpot Security Basics

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/hubspot-security-basics

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/hubspot-pack/skills/hubspot-security-basics

Skill Files

Browse the full folder contents for hubspot-security-basics.

Download Skill

Loading file tree…

plugins/saas-packs/hubspot-pack/skills/hubspot-security-basics/SKILL.md

Skill Metadata

Name
hubspot-security-basics
Description
|

HubSpot Security Basics

Overview

Security best practices for HubSpot private app tokens, OAuth scopes, webhook signature verification, and secret management.

Prerequisites

  • HubSpot private app or OAuth app configured
  • Understanding of environment variables and secret management

Instructions

Step 1: Least-Privilege Scopes

Only request the scopes your integration actually uses:

| Use Case | Required Scopes | |----------|----------------| | Read contacts | crm.objects.contacts.read | | Write contacts | crm.objects.contacts.read, crm.objects.contacts.write | | Read/write deals | crm.objects.deals.read, crm.objects.deals.write | | Marketing emails | content | | Forms | forms | | Contact lists | crm.lists.read, crm.lists.write | | Properties | crm.schemas.contacts.read | | Custom objects | crm.objects.custom.read, crm.objects.custom.write, crm.schemas.custom.read | | Webhooks | automation |

Never use: Do not grant all scopes. If you regenerate a private app token, the old token is immediately revoked.

Step 2: Token Storage

# .env (NEVER commit)
HUBSPOT_ACCESS_TOKEN=pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
HUBSPOT_WEBHOOK_SECRET=your-webhook-secret

# .gitignore
.env
.env.local
.env.*.local
// Validate token is present at startup
function validateConfig(): void {
  if (!process.env.HUBSPOT_ACCESS_TOKEN) {
    throw new Error('HUBSPOT_ACCESS_TOKEN is required. See .env.example');
  }
  // Never log the token
  console.log('HubSpot: Token configured', {
    prefix: process.env.HUBSPOT_ACCESS_TOKEN.substring(0, 8) + '...',
  });
}

Step 3: Webhook Signature Verification (v3)

HubSpot sends webhooks with signature verification headers:

import crypto from 'crypto';
import express from 'express';

// HubSpot v3 signature verification
// Header: X-HubSpot-Signature-v3
function verifyHubSpotSignatureV3(
  requestBody: string,
  signature: string,
  timestamp: string,
  clientSecret: string,
  requestUri: string,
  method: string = 'POST'
): boolean {
  // Reject if timestamp is older than 5 minutes (replay protection)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) {
    console.warn('HubSpot webhook timestamp too old');
    return false;
  }

  // v3: HMAC SHA-256 of method + URI + body + timestamp
  const sourceString = `${method}${requestUri}${requestBody}${timestamp}`;
  const expectedSignature = crypto
    .createHmac('sha256', clientSecret)
    .update(sourceString)
    .digest('base64');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Express middleware
const webhookRouter = express.Router();
webhookRouter.post('/hubspot',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-hubspot-signature-v3'] as string;
    const timestamp = req.headers['x-hubspot-request-timestamp'] as string;
    const requestUri = `https://${req.headers.host}${req.originalUrl}`;

    if (!verifyHubSpotSignatureV3(
      req.body.toString(), signature, timestamp,
      process.env.HUBSPOT_WEBHOOK_SECRET!, requestUri
    )) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    const events = JSON.parse(req.body.toString());
    // Process events...
    res.status(200).json({ received: true });
  }
);

Step 4: Token Rotation Procedure

# 1. Generate new token in HubSpot
#    Settings > Integrations > Private Apps > [Your App] > Auth tab
#    Click "Rotate token" (old token revoked immediately)

# 2. Update in your secret manager
# AWS Secrets Manager
aws secretsmanager update-secret --secret-id hubspot/production \
  --secret-string '{"access_token":"pat-na1-NEW_TOKEN"}'

# GCP Secret Manager
echo -n "pat-na1-NEW_TOKEN" | gcloud secrets versions add hubspot-token --data-file=-

# 3. Restart/redeploy your application to pick up new token

# 4. Verify new token works
curl -s https://api.hubapi.com/crm/v3/objects/contacts?limit=1 \
  -H "Authorization: Bearer $HUBSPOT_ACCESS_TOKEN" | jq .status

Step 5: Git Secret Scanning

# .github/workflows/secret-scan.yml
name: Secret Scan
on: [push, pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check for HubSpot tokens
        run: |
          if grep -rE "pat-[a-z]{2}[0-9]-[a-f0-9-]{36}" --include="*.ts" --include="*.js" --include="*.json" .; then
            echo "ERROR: HubSpot access token found in source code"
            exit 1
          fi

Output

  • Minimal scopes configured per use case
  • Tokens stored in environment variables, never in code
  • Webhook signatures verified with replay protection
  • Token rotation procedure documented
  • CI scanning for leaked tokens

Error Handling

| Security Issue | Detection | Mitigation | |----------------|-----------|------------| | Token in git history | git log -p --all -S "pat-na1" | Rotate token immediately | | Excessive scopes | Audit in Settings > Private Apps | Remove unneeded scopes | | Unverified webhooks | Security audit | Add signature verification | | Token never rotated | Track creation date | Schedule quarterly rotation |

Resources

Next Steps

For production deployment, see hubspot-prod-checklist.