Claude-code-plugins lucidchart-security-basics

install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/lucidchart-pack/skills/lucidchart-security-basics" ~/.claude/skills/jeremylongshore-claude-code-plugins-lucidchart-security-basics && rm -rf "$T"
manifest: plugins/saas-packs/lucidchart-pack/skills/lucidchart-security-basics/SKILL.md
source content

Lucidchart Security Basics

Overview

Lucidchart documents often contain sensitive business diagrams — org charts, network topologies, database schemas, and architecture plans that reveal internal infrastructure. The API uses OAuth2 client credentials, meaning a compromised client secret grants access to every document the integration can reach. Collaboration sharing with granular permission levels (view, edit, owner) must be enforced server-side. API versioning via the

Lucid-Api-Version
header requires pinning to avoid unexpected schema changes that break validation logic.

Prerequisites

  • OAuth2 client ID and secret stored in a secrets manager (not environment files)
  • HTTPS enforced on all redirect URIs and webhook endpoints
  • Lucid-Api-Version
    header pinned to a tested version in all requests
  • .env
    files in
    .gitignore
    — never committed to version control

API Key Management

// OAuth2 client credentials — load from secrets manager at startup
const LUCID_CLIENT_ID = process.env.LUCID_CLIENT_ID;
const LUCID_CLIENT_SECRET = process.env.LUCID_CLIENT_SECRET;

function validateLucidConfig(): void {
  if (!LUCID_CLIENT_ID || !LUCID_CLIENT_SECRET) {
    throw new Error('Missing LUCID_CLIENT_ID or LUCID_CLIENT_SECRET');
  }
}

async function getLucidAccessToken(): Promise<string> {
  const resp = await fetch('https://api.lucid.co/oauth2/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: LUCID_CLIENT_ID!,
      client_secret: LUCID_CLIENT_SECRET!,
    }),
  });
  if (!resp.ok) throw new Error(`OAuth2 token request failed: ${resp.status}`);
  const { access_token } = await resp.json();
  return access_token;
  // Cache token until expiry — never log it
}

Webhook Signature Verification

import crypto from 'node:crypto';

const LUCID_WEBHOOK_SECRET = process.env.LUCID_WEBHOOK_SECRET!;

function verifyLucidWebhook(payload: string, signature: string): boolean {
  const expected = crypto
    .createHmac('sha256', LUCID_WEBHOOK_SECRET)
    .update(payload, 'utf8')
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

app.post('/webhooks/lucidchart', (req, res) => {
  const sig = req.headers['x-lucid-signature'] as string;
  if (!sig || !verifyLucidWebhook(JSON.stringify(req.body), sig)) {
    return res.status(401).json({ error: 'Invalid webhook signature' });
  }
  // Process verified document change event
});

Input Validation

// Validate document IDs and enforce API version pinning
const LUCID_API_VERSION = '1';

function validateDocumentId(docId: string): boolean {
  // Lucid document IDs are alphanumeric UUIDs
  return /^[a-f0-9-]{36}$/.test(docId);
}

function lucidApiHeaders(token: string): Record<string, string> {
  return {
    Authorization: `Bearer ${token}`,
    'Lucid-Api-Version': LUCID_API_VERSION,
    'Content-Type': 'application/json',
  };
}

Data Protection

function redactDiagramMetadata(doc: Record<string, unknown>): Record<string, unknown> {
  const sensitive = ['creator_email', 'collaborator_emails', 'share_link', 'embed_url'];
  const redacted = { ...doc };
  for (const field of sensitive) {
    if (redacted[field]) redacted[field] = '[REDACTED]';
  }
  return redacted;
}
// Always redact before logging — diagrams may contain org charts and network layouts

Access Control

type LucidPermission = 'view' | 'edit' | 'owner';

function assertDocumentPermission(
  userRole: LucidPermission,
  requiredRole: LucidPermission
): void {
  const hierarchy: LucidPermission[] = ['view', 'edit', 'owner'];
  if (hierarchy.indexOf(userRole) < hierarchy.indexOf(requiredRole)) {
    throw new Error(`Insufficient permission: need "${requiredRole}", have "${userRole}"`);
  }
}
// Enforce server-side — never rely on Lucidchart UI permissions alone

Security Checklist

  • OAuth2 client secret in secrets manager, rotated on schedule
  • Access tokens cached in memory only, never persisted to disk or logs
  • Lucid-Api-Version
    header pinned to a tested version
  • Webhook signatures verified with HMAC-SHA256
  • Document sharing permissions enforced server-side
  • Collaborator emails redacted before logging
  • Exported diagrams (PNG/PDF) treated as confidential artifacts
  • OAuth2 scopes requested at minimum privilege

Error Handling

VulnerabilityRiskMitigation
Client secret in logsFull API access compromiseNever log OAuth2 credentials; redact in error handlers
Unverified webhooksSpoofed document change eventsReject requests without valid
x-lucid-signature
Unpinned API versionBreaking schema changes bypass validationAlways send
Lucid-Api-Version
header
Over-permissioned sharingUnauthorized diagram accessEnforce view/edit/owner hierarchy server-side
Diagram data in logsLeaked org charts and network topologyRedact creator, collaborator, and share URLs

Resources

Next Steps

See

lucidchart-prod-checklist
.