Claude-code-plugins appfolio-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/appfolio-pack/skills/appfolio-security-basics" ~/.claude/skills/jeremylongshore-claude-code-plugins-appfolio-security-basics && rm -rf "$T"
manifest: plugins/saas-packs/appfolio-pack/skills/appfolio-security-basics/SKILL.md
source content

AppFolio Security Basics

Overview

AppFolio manages property portfolios containing tenant PII (SSNs, bank accounts, lease terms), owner financial data, and maintenance vendor records. A breach exposes rent rolls, payment histories, and personally identifiable tenant information across every managed property. Secure every integration point: API credentials, webhook endpoints, and any pipeline that touches tenant or owner financial records.

API Key Management

import https from "https";
import axios, { AxiosInstance } from "axios";

function createAppFolioClient(): AxiosInstance {
  const clientId = process.env.APPFOLIO_CLIENT_ID;
  const clientSecret = process.env.APPFOLIO_CLIENT_SECRET;
  const baseUrl = process.env.APPFOLIO_BASE_URL;
  if (!clientId || !clientSecret || !baseUrl) {
    throw new Error("Missing APPFOLIO_CLIENT_ID, APPFOLIO_CLIENT_SECRET, or APPFOLIO_BASE_URL");
  }
  return axios.create({
    baseURL: baseUrl,
    auth: { username: clientId, password: clientSecret },
    httpsAgent: new https.Agent({ minVersion: "TLSv1.2", rejectUnauthorized: true }),
  });
}

Webhook Signature Verification

import crypto from "crypto";

function verifyAppFolioWebhook(req: Request, res: Response, next: NextFunction): void {
  const signature = req.headers["x-appfolio-signature"] as string;
  const secret = process.env.APPFOLIO_WEBHOOK_SECRET!;
  const expected = crypto.createHmac("sha256", secret).update(req.body).digest("hex");
  if (!signature || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    res.status(401).send("Invalid signature");
    return;
  }
  next();
}

Input Validation

import { z } from "zod";
const TenantSchema = z.object({
  tenant_id: z.string().uuid(),
  first_name: z.string().min(1).max(100),
  last_name: z.string().min(1).max(100),
  email: z.string().email(),
  unit_id: z.string().uuid(),
  lease_start: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
  rent_amount: z.number().positive().max(100000),
});

function validateTenantPayload(data: unknown) {
  return TenantSchema.parse(data);
}

Data Protection

const APPFOLIO_PII_FIELDS = ["ssn", "bank_account", "routing_number", "date_of_birth", "drivers_license"];

function redactAppFolioLog(record: Record<string, unknown>): Record<string, unknown> {
  const redacted = { ...record };
  for (const field of APPFOLIO_PII_FIELDS) {
    if (field in redacted) redacted[field] = "[REDACTED]";
  }
  return redacted;
}

Security Checklist

  • API credentials stored in secrets manager, not
    .env
    in production
  • HTTPS enforced with TLS 1.2+ for all API calls
  • Tenant SSN and bank account numbers never logged
  • Webhook signatures verified on every inbound request
  • API credentials rotated quarterly
  • Access scoped to minimum required property endpoints
  • Rent payment data encrypted at rest
  • Audit trail enabled for tenant record access

Error Handling

VulnerabilityRiskMitigation
Leaked API credentialsFull property portfolio exposureSecrets manager + rotation
Unvalidated webhook payloadsSpoofed tenant updatesHMAC signature verification
Tenant PII in logsCompliance violation (state privacy laws)Field-level redaction
Overly broad API scopeLateral access to unrelated propertiesPer-property credential scoping
Unencrypted payment dataFinancial data breachTLS 1.2+ in transit, AES at rest

Resources

Next Steps

See

appfolio-prod-checklist
.