Skip to content
prod e051e98
Browse

Security & compliance

The mental model before you execute

Security on a CodeCanyon Laravel app is not one switch you flip — it is layers that each fail safely, plus the habit of never letting a secret touch your code. This guide builds that mental model so the security playbook phases make sense when you run them. It does not run commands for you; the playbook does that.

A vendor app ships installed and “working”, which makes it easy to forget that you — not the vendor — own its production security posture. The three failure modes that actually bite solo founders are: a leaked secret (an API key in git, a .env exposed over HTTP), a single point of failure (you trusted one layer and it was misconfigured), and an unenforced compliance promise (your privacy policy says “GDPR compliant” but there’s no delete button). Each section below is the mental model that prevents one of those.

Defense in depth — never trust one layer

Section titled “Defense in depth — never trust one layer”

The core principle: never rely on a single security layer. If one is bypassed or misconfigured, the others still protect you. Three layers wrap a Laravel app, from the outside in:

graph TD
Browser["Client browser"]
Browser -->|HTTPS| L3["Layer 3 — Edge / CDN<br/>(Cloudflare, optional)<br/>WAF · DDoS · rate limit · bot mgmt"]
L3 --> L2["Layer 2 — Server<br/>(Apache/Nginx + .htaccess)<br/>SSL/TLS · HSTS · security headers · file protection"]
L2 --> L1["Layer 1 — Application<br/>(Laravel + middleware)<br/>auth · CSRF · XSS · SQL-injection · encryption"]
L1 --> App["Laravel app → database / files"]

The order is deliberate — Layer 1 is the most reliable because it always runs with your PHP code, no matter how the server or CDN is configured. Layer 3 is an enhancement, not a replacement: if Cloudflare is bypassed by a direct-IP request, it gives zero protection. So the rule is always implement Layers 1 and 2; add Layer 3 when you need DDoS protection or global performance.

LayerWhat it doesIf misconfigured
1 — ApplicationCSRF tokens, Blade escaping, Eloquent, RBAC, encryptionForm hijacking, XSS, mass-assignment exploit
2 — ServerHTTPS enforcement, HSTS, headers, .env protectionDowngrade attacks, leaked credentials
3 — Edge/CDNWAF, DDoS absorption, rate limiting, bot managementNo edge protection (but app still safe)

Layer 1 — application security (Laravel’s defaults, used correctly)

Section titled “Layer 1 — application security (Laravel’s defaults, used correctly)”

Laravel ships most of this for free; the bugs come from bypassing the defaults. The OWASP Top 10 maps cleanly onto Laravel mechanisms:

OWASP riskLaravel protection
Broken access controlGates, Policies, middleware (spatie/laravel-permission)
Cryptographic failuresencrypt(), Hash::make(), encrypted casts
InjectionEloquent ORM / Query Builder (parameterized)
Security misconfigurationAPP_DEBUG=false in production
Vulnerable componentscomposer audit
Auth failuresrequest throttling, strong password rules
Logging failuresactivity logging (spatie/laravel-activitylog)

The non-negotiables a beginner most often gets wrong:

  • Mass assignment. Always set $fillable (whitelist) or $guarded. Never $guarded = [] — that lets an attacker set any field, including is_admin.
  • XSS. Use {{ $var }} (escaped). Reserve {!! $var !!} (raw) for trusted content only, and never with user input.
  • SQL injection. Use Eloquent or Query Builder bindings. Never concatenate user input into a raw query string.
  • Rate limiting. Throttle public endpoints, and throttle auth endpoints harder (throttle:5,1 on login/reset) to blunt brute-force.
  • Encryption. Cast sensitive columns as encrypted so the value is never stored in plaintext — this matters before the first secret is ever written, because plaintext leaks into binlogs and backups permanently.
  • Sessions. In production: SESSION_SECURE_COOKIE=true, http_only on, and same_site set to lax (the Laravel default of null ships no CSRF protection and trips security scanners).

Layer 2 — SSL, HSTS, and security headers

Section titled “Layer 2 — SSL, HSTS, and security headers”

Layer 2 protects requests before they reach Laravel. Two ideas do most of the work: forcing HTTPS, and sending the right response headers.

HSTS — teach the browser “HTTPS only”

Section titled “HSTS — teach the browser “HTTPS only””

HSTS (HTTP Strict Transport Security) is a header that tells the browser to always use HTTPS for your domain, even if the user types http://. After the first visit, the browser rewrites http to https itself — no server round-trip, so a man-in-the-middle on coffee-shop WiFi can’t downgrade the connection and steal a login.

Two facts that confuse people:

  • It’s browser-side, not server-side. The max-age timer resets on every visit, so a regular visitor effectively never loses protection. And you can always “unlock” users by sending max-age=0 — they are never permanently locked out as long as your server is reachable.
  • includeSubDomains is a foot-gun if rushed. It forces HTTPS on every subdomain — and any subdomain without a valid certificate becomes unreachable.

The safe rollout is a progression, not a one-shot:

graph LR
P1["Phase 1 · main domain only<br/>max-age=31536000 (1yr)"] --> P2["Phase 2 · add includeSubDomains<br/>after every subdomain has SSL"]
P2 --> P3["Phase 3 · max-age=63072000 (2yr)<br/>+ submit to hstspreload.org"]
When to add includeSubDomainsWhen to wait
Every subdomain has a valid, verified SSL certificateAny docs. / api. / dev. subdomain still lacks SSL
You’ve run stable HTTPS for a few monthsYou’re deploying HSTS for the very first time

Browsers cap max-age at 2 years (63072000); larger values are ignored. The preload directive is permanent and very hard to undo — only submit to hstspreload.org after 12+ months of stable HTTPS.

These response headers each block a specific attack class. Set them all at once (in .htaccess mod_headers, or a Laravel middleware), not as a “future enhancement”:

HeaderStopsTypical value
Strict-Transport-SecurityProtocol-downgrade / MITMmax-age=31536000
X-Frame-OptionsClickjacking (iframe overlay)SAMEORIGIN
X-Content-Type-OptionsMIME-sniffing (image run as JS)nosniff
Referrer-PolicyLeaking sensitive URLsstrict-origin-when-cross-origin
Permissions-PolicyRogue camera/mic/geolocationcamera=(), microphone=(), geolocation=()
Content-Security-PolicyXSSstart in -Report-Only, then enforce

CSP is the one to add last and carefully — it can break a working site. Run it in report-only mode for a week or two, watch the violation reports, then switch to enforcing.

These check different things — you need both:

ToolChecksDoesn’t check
SSL Labs (ssllabs.com/ssltest)Certificate, TLS versions, ciphers, HSTS value.htaccess syntax, whether the redirect works, .env exposure
SecurityHeaders.comThe application headers above (grade A–F)TLS/cipher details

A quick curl -I https://yourdomain.com confirms the HSTS header is actually being sent, and curl -I https://yourdomain.com/.env should return 403 Forbidden — if it returns 200, your secrets are exposed and that is a stop-everything incident.

Secrets & password management — keep keys out of code

Section titled “Secrets & password management — keep keys out of code”

The highest-frequency real-world breach isn’t a clever exploit — it’s a key committed to git. Two habits prevent it.

Never commit secrets. .env and every .env.* is gitignored; .env.example holds placeholders only. Config reads secrets via env() at config-load time, never as inline literals in app code. Before every commit, scan your own diff for the give-away tokens — APP_KEY=, DB_PASSWORD, sk_, pk_live_, *_SECRET, AKIA, Bearer . If one ever ships, treat the credential as compromised: rotate it at the provider first, then scrub history.

Use a real password/secrets manager rather than a notes file or browser autofill. For developer work the must-haves are zero-knowledge encryption (the provider can never see your data), passkey + 2FA support, and — critically for a deploy pipeline — SSH-key storage, a CLI, and CI/CD secret injection. The practical picks:

Pick when you want…ToolWhy
Best developer workflow (SSH agent, CLI, CI/CD)1PasswordBuilt-in SSH agent (keys never touch disk), op CLI, official GitHub/GitLab actions, IDE op:// references
Budget + open-source + self-hostingBitwardenFree unlimited tier, self-host via Vaultwarden, bw CLI — but no native SSH agent
Beginner + real-time breach alertsNordPassXChaCha20, real-time dark-web monitoring — but no SSH keys or dev CLI, so not for deploy workflows

The pattern that pays off: store secrets in the manager, reference them by path in .env (e.g. API_KEY=op://Development/API/key), and run the app with the manager injecting them at runtime (op run -- npm run dev). The committed .env then contains references, not values — safe to share.

Compliance — what each framework actually requires

Section titled “Compliance — what each framework actually requires”

Compliance is mostly policy + evidence + a few technical features, not a single piece of code. Don’t pursue a framework you don’t need. Pick by your data and your customers:

graph TD
Q1{"Do you handle medical<br/>or health data (PHI)?"}
Q1 -->|Yes| HIPAA["HIPAA<br/>(+ BAAs, safeguards)"]
Q1 -->|No| Q2{"Do you process data<br/>of EU residents?"}
Q2 -->|Yes| GDPR["GDPR<br/>(rights + consent + DPAs)"]
Q2 -->|No| Q3{"Do enterprise clients<br/>ask for a security report?"}
Q3 -->|Yes| SOC2["SOC 2 Type II"]
Q3 -->|No| Base["Baseline security only<br/>(Layers 1 + 2 + secrets hygiene)"]
FrameworkTriggerCore technical asks
SOC 2Enterprise clients want assuranceMFA, RBAC, centralized logging, tested backups, incident-response plan, 3–12 months of evidence (Type II)
GDPRYou process EU residents’ dataData export + account deletion + consent logging, encryption, ROPA, signed DPAs with every vendor, 72-hour breach notice
HIPAAYou handle PHI (health data)PHI encrypted at rest + transit, unique user IDs, auto-logoff, full PHI-access audit logging, signed BAAs with every vendor

A few orienting facts so you size the effort correctly:

  • SOC 2 has five Trust Service Criteria; only Security is always required. SaaS typically adds Availability and Confidentiality. Type II (controls working over 3–12 months) is what enterprises actually ask for — Type I is just a point-in-time design snapshot. Automation platforms (Drata, Vanta, Secureframe, Sprinto) cut the evidence-collection grind dramatically.
  • GDPR is the one most solo apps genuinely need, and it’s mostly buildable: a data-export endpoint (Right to Portability), an account-deletion/anonymize flow (Right to Erasure), consent logged with a timestamp + IP, and a ROPA table documenting what you collect and why. You can only truthfully claim “GDPR compliant” once those features and the privacy policy actually exist.
  • HIPAA is significant extra cost and work — only pursue it if you handle PHI. If your SaaS has no health data and no healthcare clients, you don’t need it. When you do, the BAA (Business Associate Agreement) with every vendor that touches PHI is mandatory and non-optional.

This guide is the why. The hands-on steps — adding the headers, wiring activity logging, setting up backups, and the GDPR/SOC 2/HIPAA tracks — live in the security playbook phase, run in this order so secrets are encrypted before any secret is written: