Security Controls Documentation — Amaturalist (FOBi)

Status: The application is not yet ISO/IEC 27001 certified, however a number of technical security controls have been implemented following industry best practices (Laravel Security Best Practices, OWASP Top 10 2021, OWASP ASVS L1/L2) and can be mapped to ISO/IEC 27001:2022 Annex A.

This document serves as implementation evidence proving that security controls are actively enforced at the application/code level.

  • Audit date: July 08, 2026
  • Scope: app/ and app/Http/Middleware/ (Laravel backend)
  • Baseline: ISO/IEC 27001:2022 Annex A, OWASP Top 10 2021, OWASP ASVS 4.0
  • Method: Static code review of middleware, services, and Kernel configuration

1. Executive Summary

The Amaturalist application applies defense-in-depth through layered Laravel middleware covering authentication, authorization, input validation, HTTP header hardening, admin activity logging, tiered rate limiting, and response sanitization. Summary of verified controls:

| Control Category | Implemented Controls | Primary Location | |---|---|---| | Access Control & AuthN/AuthZ | 7 | app/Http/Middleware/ | | Cryptography & Data Protection | 4 | EncryptCookies, Hash::make, HTTPS/HSTS | | Logging & Monitoring | 3 | LogAdminActivity, SecurityLogger, AdminActivityLog | | Input Validation & Output Encoding | 2 | FormRequest, Validator, CSP | | Network Security | 4 | CORS, Origin validation, TrustProxies, HSTS | | Rate Limiting / Anti-Abuse | 8 profiles | RouteServiceProvider | | Total | 28+ | |


2. Mapping to ISO/IEC 27001:2022 Annex A

A.5 — Organizational Controls

| Clause | ISO Control | Implementation | Evidence (file) | |---|---|---|---| | A.5.15 | Access control policy | Access policy based on user level (1–4) and capability system | CheckAdminLevel.php, CheckCapability.php | | A.5.17 | Authentication information | Passwords hashed with bcrypt, JWT tokens issued, remember tokens stripped from responses | SecureApiResponse.php, AuthController | | A.5.18 | Access rights | Level-based + capability-based ACL, enforced per-route | CheckCapability.php, Kernel.php:73–82 |

A.8 — Technological Controls

| Clause | ISO Control | Implementation | Evidence (file & line) | |---|---|---|---| | A.8.2 | Privileged access rights | The admin.level middleware requires level ∈ {2,3,4} and auto-logs out invalid users | CheckAdminLevel.php:48–72 | | A.8.3 | Information access restriction | Every admin route is protected by middleware; capability guard applies for granular actions | Kernel.php, CheckCapability.php | | A.8.5 | Secure authentication | JWT (Tymon) for API, session guard for web, explicit handling of expired/blacklisted/invalid tokens | JwtMiddleware.php:24–55, Authenticate.php | | A.8.6 | Capacity management | Tiered rate limiting per endpoint type (auth, polling, taxa search, observations, cached) | RouteServiceProvider.php:29–124 | | A.8.9 | Configuration management | Centralized configuration for TrustedProxies, allowed CORS origins, CSP directives | TrustProxies.php, ValidateApiOrigin.php:15–25, SecurityHeaders.php:36–50 | | A.8.10 | Information deletion | Soft delete (deleted_at) on models, session flush() on unauth | CheckAdminLevel.php:30 | | A.8.12 | Data leakage prevention | SecureApiResponse strips 7 sensitive fields from all JSON responses; X-Powered-By/Server headers removed | SecureApiResponse.php:14–22, 53–54 | | A.8.15 | Logging | All write requests (POST/PUT/PATCH/DELETE) by admins are logged: user_id, IP, UA, action, metadata (passwords/tokens excluded) | LogAdminActivity.php:60–80, 186–191 | | A.8.16 | Monitoring activities | Failed logins, unauthorized access, session timeouts, and rate-limit breaches are logged to the security channel and trigger admin notifications | SecurityLogger.php:16–46, 78–142 | | A.8.20 | Networks security | Strict CORS, Origin & Referer validation in production, TrustProxies for X-Forwarded-* headers | ValidateApiOrigin.php, TrustProxies.php, ForceCors.php | | A.8.21 | Security of network services | HSTS (production + HTTPS), CSP, X-Frame-Options SAMEORIGIN, Permissions-Policy | SecurityHeaders.php:22–60 | | A.8.23 | Web filtering | Content Security Policy restricts script/style/frame sources to a curated CDN whitelist | SecurityHeaders.php:36–51 | | A.8.24 | Use of cryptography | EncryptCookies (AES via APP_KEY), Hash::make() bcrypt, HTTPS enforced via HSTS | EncryptCookies.php, Kernel.php:35 | | A.8.25 | Secure development lifecycle | Laravel framework with latest security patches, composer.lock pinned dependencies | composer.lock | | A.8.26 | Application security requirements | Form Request validation (e.g. LoginRequest) with rate-limit lockout | LoginRequest.php:40–76 | | A.8.28 | Secure coding | Eloquent ORM (parameterized queries), Blade auto-escape, CSRF token for all web forms | VerifyCsrfToken.php, Laravel framework |


3. Verified Control Details (with code evidence)

3.1 Authentication (A.8.5, A.5.17)

JWT for APIapp/Http/Middleware/JwtMiddleware.php

Validates every token and returns distinct responses for invalid, expired, blacklisted, or missing tokens. Failed attempts are logged via Log::warning.

if ($e instanceof TokenExpiredException) {
    return response()->json(['error' => 'Token Kadaluarsa', ...], 401);
} elseif ($e instanceof TokenBlacklistedException) {
    return response()->json(['error' => 'Token Diblacklist', ...], 401);
}

Session-based for adminAuthenticate.php redirects API clients to JSON 401 and web users to the login page.

Optional AuthOptionalAuth.php allows public endpoints to still recognize a logged-in user without enforcing authentication (used for features like can_edit).

3.2 Tiered Authorization (A.8.2, A.8.3)

Admin levelCheckAdminLevel.php

if (!in_array($user->level, [2, 3, 4])) {
    Log::warning('Unauthorized admin access attempt', [...]);
    Auth::logout();
    return redirect()->route('admin.login')->withErrors([...]);
}

Granular capabilityCheckCapability.php

Used as capability:publish_agenda,moderate_forum. Fail-safe behavior: if the argument list is empty → 403.

3.3 Brute-Force Protection (A.8.5)

Http/Requests/Auth/LoginRequest.php — Uses RateLimiter allowing 5 attempts per email|IP combination, triggers a Lockout event, and returns a retry-after message.

if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) return;
event(new Lockout($this));
throw ValidationException::withMessages([
    'email' => trans('auth.throttle', ['seconds' => $seconds, 'minutes' => ceil($seconds / 60)])
]);

3.4 Application-Level Rate Limiting (A.8.6)

app/Providers/RouteServiceProvider.php defines 8 distinct rate limiter profiles: api, api-polling, api-profile, api-taxa-search, api-observations, api-comments, api-public, api-cached. Each profile distinguishes authenticated vs guest requests and uses an SHA-1 hash for token keys (so raw tokens never leak into Redis).

3.5 HTTP Security Headers (A.8.21, A.8.23)

SecurityHeaders.php (global middleware) applies:

  • X-Frame-Options: SAMEORIGIN (anti-clickjacking)
  • X-Content-Type-Options: nosniff
  • X-XSS-Protection: 1; mode=block
  • Referrer-Policy: no-referrer-when-downgrade
  • CSP with strict CDN allow-list (cdn.jsdelivr.net, unpkg.com, cdnjs.cloudflare.com, cdn.tiny.cloud, code.jquery.com); object-src 'none'; base-uri 'self'; form-action 'self'
  • Strict-Transport-Security: max-age=31536000; includeSubDomains; preload (production + HTTPS only)
  • Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()

3.6 CORS & Origin Validation (A.8.20)

ValidateApiOrigin.php — In production, validates Origin and Referer headers against a whitelist (amaturalist.com, api.amaturalist.com, etc.). Untrusted foreign-origin requests are rejected with HTTP 403.

3.7 CSRF Protection (A.8.28)

VerifyCsrfToken.php is enabled for all web routes; only api/* routes are exempted because they use JWT bearer authentication (stateless). This matches the OWASP recommendation: CSRF tokens for state-changing forms, bearer tokens for API.

3.8 Data Leakage Prevention (A.8.12)

SecureApiResponse.php — Middleware in the api group that recursively strips sensitive fields from every JSON response:

protected $sensitiveFields = [
    'password', 'remember_token', 'api_token',
    'email_verification_token', 'password_reset_token',
    'two_factor_secret', 'two_factor_recovery_codes',
];

It also removes X-Powered-By and Server headers to reduce information leaks.

3.9 Audit Logging (A.8.15, A.8.16)

Admin activityLogAdminActivity.php records every POST/PUT/PATCH/DELETE into the admin_activity_logs table with: user_id, action, description (Indonesian labels), ip_address, user_agent, and metadata JSON. Passwords/tokens are excluded before persistence:

$formData = $request->except(['password', 'password_confirmation', '_token', '_method']);

Security eventsApp\Services\SecurityLogger broadcasts events to the security log channel and sends SecurityAlert notifications for 4 critical events: LOGIN_FAILED, RATE_LIMIT_EXCEEDED, UNAUTHORIZED_ACCESS, SUSPICIOUS_ACTIVITY.

3.10 Cryptography (A.8.24)

  • Passwords: Hash::make() (bcrypt) is called across 15 controller locations (verified via grep) — including AuthController, UserController, NewPasswordController, RegisteredUserController, ResetPasswordController.
  • Cookies: The EncryptCookies middleware encrypts all cookies using APP_KEY (AES-256-CBC via Laravel's Encrypter).
  • Session: session_regenerate is called on login/logout and on privilege elevation detection (CheckAdminLevel.php:31, 61).
  • Transport: HSTS is active in production, enforcing HTTPS.

3.11 Input Validation (A.8.28)

Verified: 229+ invocations of $request->validate() / Validator::make() across 82 controllers (via ripgrep). Uses Laravel Validation with type-safe rules such as email, exists:, array|min:1, image|max:2048, etc. The TrimStrings and ConvertEmptyStringsToNull traits are active globally.

3.12 Trusted Proxies (A.8.9)

TrustProxies.php accepts X-Forwarded-* headers from proxies (Cloudflare/AWS ELB), ensuring $request->ip() returns the real client IP for accurate logging & rate limiting.


4. Mapping to OWASP Top 10 2021

| OWASP Risk | Mitigation in Code | Status | |---|---|---| | A01 Broken Access Control | CheckAdminLevel, CheckCapability, Authenticate, capability guard | ✅ | | A02 Cryptographic Failures | bcrypt passwords, EncryptCookies, HSTS, APP_KEY | ✅ | | A03 Injection | Eloquent ORM (parameterized), $request->validate, Blade auto-escape | ✅ | | A04 Insecure Design | Rate-limit lockout, defense-in-depth middleware stack | ✅ | | A05 Security Misconfiguration | SecurityHeaders (CSP/HSTS/XFO), server headers hidden | ✅ | | A06 Vulnerable Components | composer.lock, Laravel LTS framework | ⚠️ periodic dependency scan needed | | A07 Identification & Auth Failures | JWT expiry/blacklist, session regen, login rate-limit | ✅ | | A08 Software & Data Integrity | CSRF token, signed URL (ValidateSignature) | ✅ | | A09 Logging & Monitoring | LogAdminActivity, SecurityLogger, SecurityAlert notifications | ✅ | | A10 SSRF | CSP CDN whitelist, no user-supplied URL fetching in middleware | ✅ |


5. Existing Operational Procedures

  1. Session timeout — every admin request refreshes last_activity; if the session is gone, the user is redirected to re-login with a flash message (CheckAdminLevel.php:76).
  2. Auto logout on privilege drop — if a user loses admin level, the session is invalidated and the token is regenerated (CheckAdminLevel.php:59–61).
  3. Email notifications for critical security events (via SecurityAlert notification).
  4. Immutable audit trail — the admin_activity_logs table is not exposed via any edit/delete endpoint.

6. Gaps & Recommendations (Roadmap toward ISO 27001)

| Area | Current Status | Recommendation | |---|---|---| | Dependency scanning | Manual | Integrate composer audit / Dependabot in CI | | Secret management | .env file | Vault / AWS Secrets Manager for production | | Admin MFA | Not present | Add TOTP 2FA for level ≥ 3 | | Backup & DR policy | Not documented in code | Create a DB backup SOP + restore drill | | Penetration testing | Not scheduled | Annual pentest by a 3rd-party | | Incident response plan | Not formalized | Formal IR document + SecurityAlert follow-up runbook | | Log retention | No automatic rotation | Configure 90-day log rotation + archival | | CSP unsafe-inline | Still enabled | Migrate to nonce-based CSP |


7. Source File Reference (Evidence Index)

| Control | File | |---|---| | Global middleware stack | app/Http/Kernel.php | | Security headers | app/Http/Middleware/SecurityHeaders.php | | API response sanitization | app/Http/Middleware/SecureApiResponse.php | | Origin validation | app/Http/Middleware/ValidateApiOrigin.php | | JWT auth | app/Http/Middleware/JwtMiddleware.php | | Admin level guard | app/Http/Middleware/CheckAdminLevel.php | | Capability guard | app/Http/Middleware/CheckCapability.php | | Optional auth | app/Http/Middleware/OptionalAuth.php | | Admin activity audit | app/Http/Middleware/LogAdminActivity.php | | CSRF | app/Http/Middleware/VerifyCsrfToken.php | | Cookie encryption | app/Http/Middleware/EncryptCookies.php | | Trust proxies | app/Http/Middleware/TrustProxies.php | | Rate limiter profiles | app/Providers/RouteServiceProvider.php | | Login lockout | app/Http/Requests/Auth/LoginRequest.php | | Security event logger | app/Services/SecurityLogger.php | | Additional docs | SECURITY_CHECKLIST.md, SECURITY_MEASURES.md, SECURITY_CONTROLS_ISO27001.md (Indonesian version) |


Prepared for: Internal audit, vendor documentation, security due diligence, and initial baseline toward ISO/IEC 27001 certification.

Reviewer: Amaturalist Engineering Team Review frequency: Every 6 months or after major changes to middleware/authentication.