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.mdsource 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
in production.env - 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
| Vulnerability | Risk | Mitigation |
|---|---|---|
| Leaked API credentials | Full property portfolio exposure | Secrets manager + rotation |
| Unvalidated webhook payloads | Spoofed tenant updates | HMAC signature verification |
| Tenant PII in logs | Compliance violation (state privacy laws) | Field-level redaction |
| Overly broad API scope | Lateral access to unrelated properties | Per-property credential scoping |
| Unencrypted payment data | Financial data breach | TLS 1.2+ in transit, AES at rest |
Resources
Next Steps
See
appfolio-prod-checklist.