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

Fly.io Security Basics

Overview

Fly.io deploys applications to edge locations worldwide using Firecracker microVMs. Security concerns center on deploy token scoping (org-wide vs per-app), secrets management (encrypted at rest, injected as env vars), private networking via WireGuard mesh (6PN), and TLS certificate management. A leaked deploy token can push arbitrary code to production machines across all regions.

API Key Management

function validateFlyToken(): void {
  const token = process.env.FLY_API_TOKEN;
  if (!token) {
    throw new Error("Missing FLY_API_TOKEN — use `fly tokens create deploy -a <app>`");
  }
  // Never log tokens; log only token type for debugging
  const isDeployToken = token.startsWith("FlyV1");
  console.log("Fly.io token loaded, type:", isDeployToken ? "deploy" : "personal");
}

Webhook Signature Verification

import crypto from "crypto";
import { Request, Response, NextFunction } from "express";

function verifyFlyWebhook(req: Request, res: Response, next: NextFunction): void {
  const signature = req.headers["x-fly-signature"] as string;
  const secret = process.env.FLY_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 FlyDeploySchema = z.object({
  app_name: z.string().regex(/^[a-z0-9-]+$/).max(63),
  region: z.enum(["iad", "ord", "lax", "sjc", "ams", "lhr", "nrt", "syd", "gru"]),
  image: z.string().regex(/^registry\..+\/.+:.+$/),
  vm_size: z.enum(["shared-cpu-1x", "shared-cpu-2x", "performance-1x", "performance-2x"]).optional(),
  min_machines: z.number().int().min(0).max(20).optional(),
});

function validateDeployConfig(data: unknown) {
  return FlyDeploySchema.parse(data);
}

Data Protection

const FLY_SENSITIVE_FIELDS = ["fly_api_token", "deploy_token", "db_password", "wireguard_private_key", "tls_private_key"];

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

Security Checklist

  • All sensitive values in
    fly secrets
    , never in
    [env]
    section of fly.toml
  • Deploy tokens scoped per-app, not org-wide
  • force_https = true
    set in fly.toml
    [http_service]
  • Internal services use
    .internal
    DNS with no public ports
  • WireGuard keys rotated and unused tunnels removed
  • Secrets rotated on schedule (triggers rolling restart)
  • CI/CD uses deploy-scoped tokens, not personal tokens
  • Container images scanned before deployment

Error Handling

VulnerabilityRiskMitigation
Leaked deploy tokenArbitrary code deployed to productionPer-app scoped tokens + rotation
Secrets in fly.toml
[env]
Plaintext credentials in version controlUse
fly secrets set
exclusively
Open internal portsServices exposed to public internet
.internal
DNS + NetworkPolicy
Org-wide token in CIAll apps in org compromised via CI breachDeploy-scoped tokens per pipeline
Expired TLS certificatesMITM attacks on custom domainsAutomated Let's Encrypt renewal

Resources

Next Steps

See

flyio-prod-checklist
.