Skip to main content

Security Best Practices

Security standards and practices for FLYPILOT development projects.

Credential Management

1Password

All credentials stored in 1Password vaults:

VaultContents
EngineeringAPI keys, service accounts
Client - [Name]Client-specific credentials
HostingServer access, hosting panels

Environment Variables

Never commit credentials to Git.

# .env.local (gitignored)
DATABASE_URL=postgresql://user:pass@host:5432/db
API_KEY=sk_live_xxxxxxxxxxxx

1Password CLI

# Inject secrets at runtime
op run --env-file=.env -- npm run start

# Reference in .env
DATABASE_URL=op://Engineering/Database/connection-string

Code Security

Input Validation

Always validate and sanitize user input:

// Good: Validate input
import { z } from 'zod';

const userSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
age: z.number().int().positive().optional(),
});

function createUser(data: unknown) {
const validated = userSchema.parse(data);
// Use validated data
}
// WordPress: Sanitize input
$email = sanitize_email($_POST['email']);
$title = sanitize_text_field($_POST['title']);
$content = wp_kses_post($_POST['content']);

Output Encoding

Always encode output to prevent XSS:

// React automatically escapes (safe)
<div>{userInput}</div>

// Dangerous: Only use with sanitized content
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
// WordPress: Escape output
echo esc_html($title);
echo esc_url($link);
echo esc_attr($attribute);
echo wp_kses_post($content);

SQL Injection Prevention

Always use parameterized queries:

// Good: Parameterized query
const user = await db.query(
'SELECT * FROM users WHERE id = $1',
[userId]
);

// Bad: String concatenation
const user = await db.query(
`SELECT * FROM users WHERE id = ${userId}` // VULNERABLE!
);
// WordPress: Use $wpdb->prepare()
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_author = %d",
$author_id
)
);

Authentication

Password Requirements

  • Minimum 12 characters
  • Mix of uppercase, lowercase, numbers
  • No common passwords
  • Unique per service

Session Security

// Next.js with next-auth
export const authOptions: NextAuthOptions = {
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
cookies: {
sessionToken: {
name: '__Secure-next-auth.session-token',
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: true,
},
},
},
};

API Authentication

// Verify API key
function authenticateRequest(req: Request) {
const apiKey = req.headers.get('x-api-key');

if (!apiKey) {
throw new Error('API key required');
}

// Use timing-safe comparison
const valid = crypto.timingSafeEqual(
Buffer.from(apiKey),
Buffer.from(process.env.API_KEY!)
);

if (!valid) {
throw new Error('Invalid API key');
}
}

HTTPS & Headers

Required Security Headers

// next.config.js
const securityHeaders = [
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
];

module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: securityHeaders,
},
];
},
};

Content Security Policy

const ContentSecurityPolicy = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
`;

WordPress Security

Plugin Security

// Verify nonces in forms
wp_nonce_field('my_action', 'my_nonce');

// Verify nonce on submission
if (!wp_verify_nonce($_POST['my_nonce'], 'my_action')) {
wp_die('Security check failed');
}

// Check user capabilities
if (!current_user_can('edit_posts')) {
wp_die('Unauthorized');
}

File Upload Security

// Restrict upload types
function restrict_upload_types($mimes) {
return [
'jpg|jpeg|jpe' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
'pdf' => 'application/pdf',
];
}
add_filter('upload_mimes', 'restrict_upload_types');

Disable XML-RPC

// Disable XML-RPC if not needed
add_filter('xmlrpc_enabled', '__return_false');

Dependency Security

Audit Dependencies

# Node.js
npm audit
npm audit fix

# WordPress
wp plugin update --all
wp core update

Automated Security Scanning

# .github/workflows/security.yml
name: Security Scan

on:
push:
branches: [main]
schedule:
- cron: '0 0 * * 1' # Weekly

jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Run npm audit
run: npm audit --audit-level=high

- name: Run Snyk scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

Incident Response

If Credentials Exposed

  1. Immediately rotate the exposed credential
  2. Audit access logs for unauthorized use
  3. Notify stakeholders if data accessed
  4. Document incident for post-mortem

If Site Compromised

  1. Take site offline if necessary
  2. Preserve evidence (logs, files)
  3. Identify entry point
  4. Clean and restore from backup
  5. Patch vulnerability
  6. Document and report

Security Checklist

Pre-Launch

  • HTTPS configured
  • Security headers set
  • Input validation implemented
  • Output encoding in place
  • SQL injection prevented
  • Authentication secure
  • Dependencies audited
  • Error messages sanitized
  • Logging configured
  • Backups working

Ongoing

  • Weekly dependency audits
  • Monthly security review
  • Quarterly penetration test
  • Annual security training

Reporting Security Issues

Report security vulnerabilities immediately to the tech lead. Do not discuss publicly until resolved.