Skip to content

Instantly share code, notes, and snippets.

@jonasvanderhaegen
Created August 1, 2026 07:40
Show Gist options
  • Select an option

  • Save jonasvanderhaegen/e135478d7e7104f2e454294d013b2b9a to your computer and use it in GitHub Desktop.

Select an option

Save jonasvanderhaegen/e135478d7e7104f2e454294d013b2b9a to your computer and use it in GitHub Desktop.
ISO 27001 / 27701 / 42001 (+ CRA, GDPR, the rest) compliance for Laravel applications: control-by-control mappings, code patterns, evidence artifacts

ISO compliance for Laravel applications: the engineer's field guide

A file per standard, mapping every control theme that has an application layer to concrete Laravel implementations: packages, config, code patterns, tests, and the evidence an auditor or enterprise security questionnaire actually asks for.

File Standard One-liner
1-iso-27001-laravel.md ISO/IEC 27001:2022 Information security management; the Annex A control-by-control Laravel mapping
2-iso-27701-privacy-laravel.md ISO/IEC 27701:2025 Privacy information management, standalone since the 2025 revision; DSAR, retention, PII engineering
3-iso-42001-ai-laravel.md ISO/IEC 42001:2023 AI management systems; what to build when your Laravel app ships AI features
4-other-iso-eu-laws-laravel.md 27017, 27018, 22301, 9001, 20000-1 + GDPR, CRA, NIS2, AI Act What to skip, what is law rather than certificate

Read this first: what a certificate is and is not

  1. No composer package makes you compliant. Every management-system standard (the "-MS" in ISMS/PIMS/AIMS) certifies an organisation running a process: scope, risk assessment, treatment decisions, management review, internal audit, continual improvement. Code delivers the technical controls and, just as important, the evidence.
  2. The app layer is roughly a third of the work. Policies, people (joiner/mover/leaver, training), suppliers (DPAs, terms, reviews), physical/hosting, and incident process are the rest. These files cover the third you can ship as PRs, and name the rest so nobody pretends a middleware closed it.
  3. Evidence beats intention. Auditors and enterprise buyers want artifacts: an audit-log row, a restore-drill document with timestamps, a green CI run, a pruning schedule. Every control below states its evidence artifact.
  4. Build once, reuse everywhere. One honest control set feeds ISO 27001, SOC 2, ISO 27701, GDPR Art. 32, CRA technical files, and every security questionnaire. Do not build per-framework silos.

The Laravel baseline you already own

Before adding anything, document what the framework gives you; these count as implemented controls in any mapping:

Capability Where
Password hashing (bcrypt/argon2id) config/hashing.php
Encrypted values + encrypted casts Crypt, encrypted cast, APP_KEY
CSRF protection VerifyCsrfToken middleware
SQL injection resistance Eloquent/query builder bindings
XSS resistance Blade {{ }} escaping
Session hardening config/session.php (secure, http_only, same_site)
Signed + temporary URLs URL::signedRoute()
Rate limiting RateLimiter + throttle middleware
Authorisation Policies, gates
Password strength + breach check Password::min()->uncompromised()
Native 2FA/passkeys/verification flows Fortify (or Breeze/Jetstream variants)
Scheduled data pruning model:prune, scheduler
Maintenance + destructive-command guard DB::prohibitDestructiveCommands()

Disclaimer

Educational engineering notes, not legal advice and not a certification guarantee. Standards texts are copyrighted; buy the current editions (ISO 27001:2022 + Amd 1, 27701:2025, 42001:2023) before an actual certification project.

ISO/IEC 27001:2022 for Laravel applications

27001 has two halves. Clauses 4 to 10 define the management system (scope, leadership, risk assessment, objectives, competence, operation, performance evaluation, improvement); they are organisational and no code closes them. Annex A lists 93 controls in 4 themes (organisational, people, physical, technological); you select applicable ones via a Statement of Applicability (SoA) driven by your risk assessment. This file maps every Annex A control with an application layer to a Laravel implementation and its evidence artifact.

Part 1: the management system (no code, know it anyway)

Clause What the auditor wants Engineer's contribution
4 Context + scope One-page scope naming systems, people, suppliers System inventory, data-flow diagram
5 Leadership + policy Signed infosec policy, roles none
6 Risk assessment + SoA Risk register with treatments; SoA over all 93 controls Feed real technical risks (dependency compromise, key leak, runner compromise)
7 Support Training records, docs Runbooks
8 Operation Risk treatment executed The PRs below
9 Performance Internal audit, mgmt review minutes Metrics from health/log tooling
10 Improvement Nonconformity + corrective action log Incident postmortems

Part 2: Annex A, control by control

Legend: App = implement in Laravel; Infra = hosting/platform; Org = policy/process only. Controls that are purely Org/Physical are listed compressed at the end so your SoA can still cite this mapping.

A.5 Organisational controls with app hooks

Control Laravel implementation Evidence
5.15 Access control Policies + gates for every non-public resource; deny-by-default: register a Gate::before only for super-admin, never a permissive fallback. Route middleware groups (auth, verified, custom team/tenant middleware). php artisan route:list export showing middleware per route; policy test suite
5.16 Identity management One identity per human; no shared accounts: enforce unique emails, log admin grants. Machine identities = Passport clients / Sanctum tokens with named scopes. users + oauth_clients inventory query
5.17 Authentication information Password::defaults() with min(12)->mixedCase()->numbers()->symbols()->uncompromised(); Fortify 2FA (TOTP + recovery codes) and passkeys; force password confirmation (password.confirm middleware) before sensitive actions. config diff + feature tests
5.18 Access rights Provisioning/deprovisioning path: model events revoking tokens/sessions on role change; scheduled report of dormant accounts. listener code + monthly access-review artifact
5.23 Cloud services security Supplier register row per cloud service the app calls; keys scoped least-privilege. register document
5.28 Collection of evidence Never truncate audit tables casually; document retention of activity_log. retention schedule
5.33 Protection of records Backups (8.13) + restrict who can delete audit rows (no delete policy on the log model). policy test asserting deletion forbidden
5.34 Privacy/PII Hand off to ISO 27701 file. cross-ref

A.8 Technological controls (the core of the app work)

8.2 Privileged access rights. Admin ability behind explicit role + 2FA-required + password.confirm on destructive routes. Log every privileged action (see 8.15).

Route::middleware(['auth', 'verified', 'can:admin', 'password.confirm'])
    ->prefix('admin')->group(...);

Evidence: route list + activity log rows for admin actions.

8.3 Information access restriction. Multi-tenant isolation as code, not convention: global scopes or explicit tenant middleware; policies test the tenant edge (user from team A requesting team B's resource gets 403, proven by a test per resource). Evidence: the cross-tenant test file.

8.5 Secure authentication. Fortify (or equivalent) with: email verification, 2FA confirm-before-enable, passkeys where possible, session regeneration on login (framework default), remember-me cookie rotation, and layered rate limits. Rate limits belong at ROUTE registration, dual-keyed:

RateLimiter::for('login', fn (Request $r) => [
    Limit::perMinute(5)->by('ip:'.$r->ip()),
    Limit::perHour(15)->by('email:'.Str::lower((string) $r->input('email'))),
]);

Hard-won gotchas: attach throttles at route definition (post-hoc Route::getRoutes()->getByName(...)->middleware(...) in a booted callback silently no-ops and breaks under route:cache); clear identity buckets on successful login via a Login event listener or legitimate users lock themselves out; write an HTTP test per throttled endpoint plus a gatherMiddleware() assertion so a silently detached throttle fails CI. Evidence: the throttle test suite; route:list --json middleware dump.

8.6 Capacity management. Queue workers monitored (Horizon or health checks), scheduled-task overlap prevention (withoutOverlapping), DB connection limits documented. Evidence: health-check output (see 8.16).

8.7 Malware protection. For uploads: validate mime + extension allow-list, store outside webroot or on object storage, never execute, randomized names; consider clamav via queue for high-risk intake. Evidence: upload FormRequest rules + test with a disguised payload.

8.8 Technical vulnerability management.

composer audit --locked        # CI step, fails on known CVEs
npm audit --omit=dev --audit-level=high

Plus Dependabot/Renovate and a supply-chain gate (Socket or similar) on PRs. Patch SLA in policy (e.g. critical 72h). Evidence: CI config + a merged security-bump PR trail.

8.9 Configuration management. All config through config/ + env; config:cache in prod; no env() outside config files (add a phpstan rule or grep gate); document prod-vs-dev divergences (debug off, secure cookies on). Evidence: deploy script + a CI grep asserting env( only under config/.

8.10 Information deletion. model:prune / scheduled jobs per retention schedule; erasure service for user data (see 27701 file for the full pattern). Evidence: schedule table + pruner tests.

8.11 Data masking. Non-prod uses factories/seeders, never prod dumps; if a prod copy is unavoidable, a scrubbing command rewrites PII columns before restore. Evidence: the seeding policy + scrub command.

8.12 Data leakage prevention. Log hygiene: never log request bodies wholesale; redact via Str::mask in log context; set $hidden on models (password, tokens, secrets) so accidental serialization leaks nothing; APP_DEBUG=false in prod (assert in a deploy check). Evidence: grep audit of log calls; a test asserting a serialized User exposes no secret fields.

8.13 Information backup. spatie/laravel-backup (DB + selected storage to off-site disk, encrypted, with notifications) or platform-native snapshots; either way the control is the tested restore, not the backup:

// config/backup.php: 'destination' => ['disks' => ['s3-backups']], encryption + notifications on

Evidence: backup config + a dated restore-drill document (quarterly).

8.15 Logging. Two layers. (1) Security events: listeners on Login, Logout, Failed, Lockout, PasswordReset, token issuance/revocation, writing structured log or audit rows. (2) Data-change audit: spatie/laravel-activitylog (or owen-it/laravel-auditing) on security-relevant models: users, roles, billing state, entitlements:

class User extends Authenticatable
{
    use LogsActivity;
    public function getActivitylogOptions(): LogOptions
    {
        return LogOptions::defaults()
            ->logOnly(['name', 'email'])          // never secrets
            ->logOnlyDirty()->dontSubmitEmptyLogs();
    }
}

Include actor id, ip, ua via a tap/causer resolver. Protect the log (no update/delete policy), prune per schedule (5.28/5.33 tension: keep long enough for forensics). Evidence: audit rows asserted in feature tests; retention entry.

8.16 Monitoring activities. spatie/laravel-health checks (db, cache/redis, queue heartbeat, scheduler heartbeat, disk, backup freshness) exposed on an authenticated or token-gated endpoint; external uptime monitor pointed at it; alert channel named. Evidence: monitor screenshots/alert history.

8.17 Clock synchronisation. Infra (NTP); in-app, always store UTC (CarbonImmutable, Date::use).

8.18 Privileged utility programs. Tinker in production is a privileged utility: forbid or gate it (deployment image without tinker, or documented break-glass with logging). Same for artisan db. Evidence: prod image manifest or policy statement.

8.24 Cryptography. Key inventory: APP_KEY (rotation procedure that re-encrypts encrypted casts; test it in staging), OAuth keys (Passport keys custody + rotation), webhook secrets, any KMS keys. Field-level: encrypted casts for sensitive columns; blind indexes (ciphersweet) only if you must query them. Evidence: key inventory doc + one executed rotation drill.

8.25/8.27/8.28 Secure development lifecycle, architecture, coding. The pipeline is the control: static analysis (PHPStan/Larastan at a pinned level), style (Pint), refactoring safety (Rector dry-run), tests with a coverage floor (and a type-coverage floor if you use Pest), all enforced pre-push and in CI. Document it once as your "secure development procedure": the doc mostly screenshots the pipeline. Evidence: CI config + a red-then-green PR showing the gate biting.

8.26 Application security requirements. Every input through FormRequests/validated Livewire properties; authorisation in the request (authorize()) or controller policy call; output escaping by default (no {!! !!} without a sanitizer note). Evidence: a grep gate for {!! occurrences with justifications.

8.29 Security testing. Beyond unit tests: per-surface abuse tests (throttle exhaustion, cross-tenant access, IDOR probes on route-model-binding endpoints, webhook signature failure paths). Schedule an external pentest when customer count justifies it; fix-verify loop recorded. Evidence: the abuse-test files + pentest report/actions.

8.31 Separation of environments.

DB::prohibitDestructiveCommands(app()->isProduction());

Separate keys/secrets per env, no shared DB, .env.production never in repo, staging data synthetic (8.33). Evidence: env inventory.

8.32 Change management. PRs only, protected main (required green checks, no force push), squash with traceable messages, release/rollback runbook. Evidence: branch-protection settings export + PR history.

8.34 Protection during audit testing. Auditor/pentest access via time-boxed accounts on staging with synthetic data.

Controls with no app layer (SoA still needs a line each)

A.5: 5.1 to 5.14, 5.19 to 5.22 (supplier process), 5.24 to 5.27 (incident process), 5.29 to 5.32, 5.35 to 5.37: policies, threat intel, continuity, legal register, IP, independent review. A.6 (people, 8 controls): screening, terms, awareness, disciplinary, NDA, remote work, reporting channel. A.7 (physical, 14 controls): hosting provider inherits most; your office/laptops remain. A.8 leftovers: 8.1 endpoint devices (MDM/disk encryption), 8.4 source-code access (Git provider settings: 2FA enforced, least-privilege teams), 8.14 redundancy (infra), 8.19 to 8.23 (installation, networks, DNS/web filtering: infra + provider).

Part 3: the minimum credible package list

Need Package Alternative
Model + event audit trail spatie/laravel-activitylog owen-it/laravel-auditing; tamper-evident: a hash-chained ledger only if demanded
Backups spatie/laravel-backup platform snapshots (then document them instead)
Health/monitoring spatie/laravel-health custom /up + external checks
Searchable field encryption spatie/laravel-ciphersweet plain encrypted casts when you never query the field

Everything else in this file is framework-native or process. Resist compliance-package shopping sprees: every dependency is itself an 8.8 liability.

ISO/IEC 27701:2025 (privacy / PIMS) for Laravel applications

The 2025 revision (published 2025-10-14) made 27701 a standalone certifiable Privacy Information Management System: you no longer need an ISO 27001 certificate first. It absorbs the security controls it depends on and adds privacy-specific controls for PII controllers and processors. If your legal driver is GDPR and your buyers are European, this is arguably the first certificate worth holding: it attests the privacy programme directly.

Mental model: GDPR tells you what must be true; 27701 is the management system proving you keep it true; Laravel is where half the "keeping" happens.

Part 1: scoping questions that change the build

  1. Controller, processor, or both? A SaaS holding user accounts is a controller for account data and often a processor for customer-content data. Controls differ (consent + purposes vs instructions + subprocessor register). Most Laravel SaaS: both.
  2. PII inventory before code. You cannot minimize, retain, or export what you have not enumerated. Deliverable: a table of every column/log/queue payload holding PII, its purpose, lawful basis, retention, recipients.
  3. One privacy config as the machine-readable record. Keep controller identity, processor list, retention numbers in config/privacy.php (or gdpr.php) and render the human documents from it, so notice, export payload, and code never drift.

Part 2: controller controls mapped to Laravel

Purpose limitation + lawful basis (in code, not just in a doc)

Ship the purposes table inside the app and include it in every data export:

public function processingPurposes(): array
{
    return [
        ['purpose' => 'Account registration and authentication',
         'legal_basis' => 'Contract (Art. 6(1)(b))',
         'categories' => 'Identity, credentials, security factors'],
        ['purpose' => 'Security, fraud prevention, service integrity',
         'legal_basis' => 'Legitimate interests (Art. 6(1)(f))',
         'categories' => 'IP address, user agent, security logs'],
        // one row per real purpose; adding a purpose = a PR = a reviewable event
    ];
}

Evidence: the export payload containing it; a drift test asserting notice page and config agree.

Consent (only where consent is actually the basis)

Do not consent-wash contract-based processing. Where consent applies (marketing, non-essential cookies, optional telemetry): store granular, timestamped, revocable records:

Schema::create('consents', function (Blueprint $t) {
    $t->id(); $t->foreignId('user_id')->constrained()->cascadeOnDelete();
    $t->string('purpose');            // 'marketing_email', 'product_telemetry'
    $t->timestamp('granted_at')->nullable();
    $t->timestamp('withdrawn_at')->nullable();
    $t->string('notice_version');     // which text they saw
    $t->string('ip')->nullable(); $t->timestamps();
});

Withdrawal must be as easy as granting (a toggle, no email tennis). Evidence: consent rows + the toggle test.

Data subject rights: the DSAR engine

Build one audited pipeline; every right is a request type flowing through it:

enum PrivacyRequestType: string {
    case Access = 'access'; case Export = 'export'; case Erasure = 'erasure';
    case Rectification = 'rectification'; case Restriction = 'restriction';
    case Objection = 'objection';
}

Request log table: type, status (pending/completed/failed), hashed subject email (hash('sha256', $email) so the trail survives erasure without retaining the address), ip, ua, due date (30 days), completion note.

Export (portability). A service that loads every relation and emits versioned JSON: subject profile, memberships, invitations, billing mirrors, tokens/passkey metadata, plus the purposes table and processor list. Rate-limit it (RateLimiter per user per hour). Self-service download beats email delivery (no misdelivery risk).

Erasure. Transactional, complete, verified:

DB::transaction(function () use ($user) {
    // 1. access artifacts: oauth tokens + refresh tokens, auth codes,
    //    device codes, personal access tokens, passkeys
    // 2. billing mirrors (local copies; merchant-of-record keeps its own
    //    records under its legal duties: say so in the confirmation UX)
    // 3. owned personal teams + invitations; detach memberships
    // 4. sessions + password reset tokens
    // 5. null the FK on the request-log row, then delete the user row
    // 6. assert the row is gone; throw if not
});

Backups: erased data ages out with rotation; document the window in the notice. Evidence: erasure feature test asserting every table, plus the runbook for out-of-band requests (verify identity via a challenge to the account email, never act on a bare inbound address).

Rectification/restriction/objection. Rectification = profile edit paths (document them as the mechanism); restriction = a restricted_at flag honored by outbound processing (mail, telemetry) via a global check; objection = consent withdrawal or a tracked manual request.

Privacy by design defaults

Principle Laravel pattern
Minimisation Migration review question "why this column"; nullable analytics FKs; no full request-body logging
Storage limitation Prunable models + scheduler per the retention schedule; prune ip/ua columns off audit rows after N months while keeping the row
Accuracy Email re-verification on change (MustVerifyEmail + change flow)
Confidentiality encrypted casts on sensitive columns; $hidden everywhere secrets live; TLS + secure cookies
Pseudonymisation Hashed identifiers in logs/analytics (hash('sha256', $id.$pepper)); ciphersweet blind indexes when you must search encrypted PII
Transparency Notice rendered from config; changelog section in the notice
// Retention as executable schedule
protected function schedule(Schedule $schedule): void
{
    $schedule->command('model:prune')->daily();          // Prunable models
    $schedule->job(new PrunePrivacyRequestMetadata)->monthly(); // strip ip/ua > 12mo
    $schedule->job(new PruneExpiredInvitations)->daily();
}

Part 3: processor controls (when customers' data flows through you)

Control theme Laravel/ops implementation
Process only on documented instructions Your ToS/DPA defines instructions; feature flags gate anything beyond them
Subprocessor register + notification Public subprocessors page rendered from the same config; mail subscribers on change
Assist the controller with DSARs Tenant-scoped export/delete endpoints or admin tooling per tenant
Return/delete at contract end Tenant offboarding command: export bundle + destructive wipe + certificate of deletion output
Breach notification to controllers Incident runbook includes per-tenant notification step with contractual clocks

Part 4: breach support (Art. 33/34 shape)

The app's contribution to the 72-hour clock is detectability and forensics: security-event logging (auth failures, lockouts, token anomalies), audit trail integrity, and an incident register template. Prewrite the assessment form: what data, whose, how many, risk level, notify-or-not rationale.

Part 5: evidence pack checklist

  • PII inventory table (columns, purposes, bases, retention, recipients)
  • config/privacy.php + drift test (notice == config == export payload)
  • DSAR request log with hashed subjects; SLA field; completed examples
  • Export + erasure feature tests green in CI
  • Retention schedule + pruner jobs + their tests
  • Consent table (where applicable) + withdrawal test
  • Processor/subprocessor register with DPA links + transfer mechanisms
  • Breach runbook + empty register
  • Cookie inventory (if essential-only, a paragraph saying so beats a banner)

Packages

Need Package Note
Export bundling spatie/laravel-personal-data-export Or hand-rolled JSON service (more control over relations)
Cookie consent (only if non-essential cookies exist) whitecube/laravel-cookie-consent Category-level consent; skip banners entirely when essential-only
Retention Prunable + scheduler Retention packages exist but the native pattern is simpler to audit
Field encryption + searchability spatie/laravel-ciphersweet Only when queries on encrypted PII are unavoidable

The controller legal analysis, DPAs, and the notice text remain human work; the app makes them true and provable.

ISO/IEC 42001:2023 (AI management / AIMS) for Laravel applications

42001 certifies an organisation's AI management system: governance of how AI systems are developed, deployed, and operated responsibly. Companion documents: ISO/IEC 42005 (AI system impact assessment method), ISO/IEC 23894 (AI risk management, the ISO 31000 extension), ISO/IEC 42006 (requirements for the bodies that audit you). None of them are code; all of them dictate code you should have.

Part 0: are you even in scope?

Your Laravel app is AIMS-relevant when it does any of: calls LLM/model APIs (chat, summarisation, extraction, embeddings/RAG), hosts agentic behavior (model output triggers tool/function execution), makes or supports automated decisions about people, or ships AI features customers rely on. A billing portal with zero model calls is a supporting asset, not an AI system; say exactly that in your scope statement and move the AIMS boundary to the products that are.

Part 1: the AI system inventory (the artifact everything else hangs on)

Keep it in code so it cannot rot. A config-driven registry doubles as a runtime gate:

// config/ai.php
return [
    'systems' => [
        'support-summarizer' => [
            'purpose' => 'Summarize support threads for staff',
            'provider' => 'anthropic', 'model' => 'claude-sonnet-5',
            'data_in' => ['ticket bodies (may contain PII)'],
            'data_out' => ['summary shown to staff only'],
            'autonomy' => 'assistive',      // assistive | supervised | autonomous
            'decision_about_people' => false,
            'human_oversight' => 'staff reads before acting',
            'enabled' => env('AI_SUPPORT_SUMMARIZER', false),
        ],
        // one entry per AI feature: adding a feature = a PR touching this file
    ],
];

Rules that make this a control: every model call goes through a service that requires a registry key; unknown key throws; enabled false kills the feature (pair with a feature-flag system like Pennant for gradual rollout and instant kill-switch). Evidence: the registry file's git history is your AI change log.

Part 2: 42001 control themes mapped to Laravel

Governance + policy

Org writes the AI policy (approved uses, prohibited uses, autonomy defaults, egress rules). App enforces the enforceable subset: the registry above, plus provider allow-list:

// AiGateway resolves only allow-listed providers; anything else throws
$response = app(AiGateway::class)->for('support-summarizer')->prompt($input);

Impact assessment (ISO 42005 method)

One assessment per registry entry before first enable, re-run on capability change: affected parties, benefits, harms (wrong output, bias, privacy, security), likelihood, mitigations, residual risk, sign-off. Store them versioned in the repo (docs/ai-impact/<system>.md); a CI check can require the file to exist for every registry key:

test('every AI system has an impact assessment', function () {
    foreach (array_keys(config('ai.systems')) as $key) {
        expect(file_exists(base_path("docs/ai-impact/{$key}.md")))->toBeTrue();
    }
});

AI risk management (ISO 23894 method)

The risks worth engineering controls in a Laravel context:

Risk Control in code
Prompt injection via user content Treat model output as untrusted input: never pipe it into {!! !!}, shell, SQL, or Eloquent without validation; strip/deny tool-call syntax from user-supplied text where feasible; system prompts server-side only
Output acted on without review Autonomy tiers in the registry; supervised tier requires an approval record before side effects (an ai_approvals table: who approved which output, when)
PII leaking to model suppliers Pre-call redaction pass for known PII fields; registry data_in documents what may flow; suppliers become processor rows in your privacy register (27701 file)
Cost/abuse runaway Budget guards: per-user and global rate limits on AI routes (RateLimiter), monthly spend counter in cache with a hard cutoff, alerting at 80%
Model/provider drift Pin model versions in the registry; a canary eval (below) runs on version bumps
Hallucinated references shown as fact UI labeling (see transparency) + retrieval-grounded responses where accuracy matters, with source links rendered from your own data

Data governance

For RAG/embeddings: document the corpus (what, whose, lawful basis), respect tenant boundaries in vector queries (tenant id in metadata filters, tested like any cross-tenant control), re-embed on erasure (a user's erased content must leave the vector store too: hook the erasure service).

Logging + traceability of AI interactions

Every call through the gateway writes an interaction record: system key, model + version, hashed user id, token counts, latency, refusal/error class, and (policy decision) prompt/response bodies or only hashes. If bodies are stored: they are PII-bearing, so retention + access control + inclusion in DSAR export/erasure all apply (27701 file patterns).

Schema::create('ai_interactions', function (Blueprint $t) {
    $t->id(); $t->string('system_key'); $t->string('model');
    $t->string('user_hash')->nullable(); $t->unsignedInteger('input_tokens');
    $t->unsignedInteger('output_tokens'); $t->unsignedInteger('latency_ms');
    $t->string('outcome'); // ok | refused | error | filtered
    $t->timestamps(); $t->index(['system_key', 'created_at']);
});

Human oversight

Make oversight a state machine, not a habit: supervised systems produce pending_review artifacts; only a human action transitions them to applied; the transition is audit-logged (activitylog from the 27001 file). Provide the operator an override/disable path that works in seconds (feature flag), and test it.

Transparency

UI labels AI-generated content; docs page lists AI features, providers, and what data leaves your infrastructure (this page is also your GDPR notice input and your sales-questionnaire answer). Registry-rendered so it cannot drift:

Route::view('/ai-disclosure', 'legal.ai', ['systems' => config('ai.systems')]);

Supplier management

Each provider row: terms/DPA link, data-use commitments (training on your inputs? retention?), region, fallback plan. A provider change is a registry PR + impact-assessment delta, not a config hotfix.

Performance evaluation (the AIMS "check" step)

Minimal credible eval loop in a Laravel app: a seeded eval set per system (inputs + expected properties), run as a scheduled job or CI step against the pinned model; failures alert; results retained:

$schedule->job(new RunAiEvals('support-summarizer'))->weekly();

Track over time: refusal rate, error rate, human-override rate from ai_interactions + ai_approvals; that trio is your management-review dashboard.

Incidents + nonconformity

AI-specific incident classes in the runbook: harmful output shipped, data sent to a wrong provider, injection exploited, cost blowout. Same register as security incidents, extra fields: system key, model version, eval added afterward (every AI incident should mint a regression eval).

Part 3: agentic systems (tool-calling) extras

If model output can trigger actions (function calls, MCP tools, code execution):

  1. Capability allow-list per system: the executable tool set is declared next to the registry entry; the executor refuses anything else.
  2. Blast-radius tiers: read-only tools auto-run; mutating tools require the approval state machine; destructive tools are never model-triggerable.
  3. Sandboxing: execution in throwaway environments (containers/workspaces), never the production runtime user.
  4. Full action audit: every tool invocation logged with arguments (redacted per policy) and outcome; the audit stream is append-only.
  5. Deterministic gates: after any judge/validator model, a plain-code check (grep, schema validation, threshold) makes the final accept/reject; a model never solely approves its own work.

Part 4: evidence pack checklist

  • AI system registry (config) + gateway enforcing it, with tests
  • Impact assessment file per system (42005-shaped) + the CI existence test
  • Interaction + approval tables with retention entries
  • Provider register with data-use terms
  • Transparency page rendered from the registry
  • Eval job + result history; override/disable drill (flip the flag, screenshot)
  • AI incident register (even if empty) + one tabletop exercise note

Packages / tools

Need Option Note
LLM integration prism-php/prism, openai-php/laravel, anthropic SDKs Wrap behind your own gateway regardless
Feature kill-switches laravel/pennant Per-system flags + gradual rollout
Audit of approvals/actions spatie/laravel-activitylog Same instance as the 27001 trail
Queue/budget guards native RateLimiter + cache counters No package needed

No package delivers 42001. The registry, the assessments, and the oversight state machine are the certificate-shaped part; they are all small Laravel code.

The rest of the alphabet: other ISO standards and the EU laws that outrank them

ISO/IEC 27017 (cloud security) and 27018 (PII in public cloud)

Written primarily for cloud providers. As a Laravel shop you are usually a cloud customer; your duties collapse into: pick providers holding these certificates (check their trust pages), configure your side correctly (bucket privacy, key scoping, region pinning), and record shared-responsibility per service in your supplier register. Get certified against these yourself only if you operate a multi-tenant platform marketed as cloud infrastructure. App-layer touchpoints: encrypt before upload for sensitive objects (Storage::put of Crypt-ed payloads or SSE settings), signed temporary URLs over public buckets, per-tenant path prefixes with authorisation tests.

ISO 22301 (business continuity)

A full BCMS certificate is overkill below serious enterprise scale; the substance for a Laravel product is four artifacts: (1) RTO/RPO declared per system; (2) tested backups (restore drill with timestamps, quarterly); (3) a degraded-mode story (queue backlog handling, maintenance mode with php artisan down --secret=... for staff access, feature flags to shed load); (4) a dependency-outage playbook per critical supplier (payment provider down, mail down, model provider down). Fold these into the 27001 evidence; cite 22301 alignment in questionnaires.

ISO 9001 (quality) and ISO/IEC 20000-1 (service management)

RFP-driven only. If forced: 9001 maps onto your existing SDLC (PRs, reviews, CI gates, release notes, defect tracking = "quality records"); 20000-1 maps onto support/SLA process. Neither has meaningful Laravel-specific controls beyond what 27001's 8.25 to 8.32 already produced. Do not pursue speculatively.

The EU laws (these are not optional and not certificates)

GDPR

Law, applies now to any EU-relevant processing of personal data. The entire 27701 file doubles as your GDPR technical implementation guide: DSAR engine, retention, consent where applicable, processor register, breach support. Legal deliverables no framework provides: the notice text, the roles analysis (controller/processor per flow), DPAs with every processor, transfer mechanisms for non-EEA suppliers (DPF/SCCs).

Cyber Resilience Act (CRA, Regulation (EU) 2024/2847)

Applies to products with digital elements placed on the EU market: shipped binaries, installable software, and (nuance) the remote services integral to the product's function can be pulled into the product boundary. Pure SaaS is largely out; a downloadable CLI/agent/desktop app is in.

Date Duty
2026-09-11 Report actively exploited vulnerabilities: early warning 24h, notification 72h, report 14d, to your national CSIRT + ENISA; applies to products already on the market
2027-12-11 Full essential requirements, conformity assessment (self-assessment for default-category products), CE marking, technical documentation

Laravel/product-adjacent work: coordinated vulnerability disclosure policy (SECURITY.md + contact), SBOM per release (composer licenses/CycloneDX generators, cyclonedx/cyclonedx-php-composer), signed update channels with rollback, declared support period, secure defaults. If you only run SaaS, you still meet CRA indirectly when your customers ask for your components' SBOMs.

NIS2

Sector + size thresholds (generally 50+ staff or 10M+ EUR revenue in listed sectors, with carve-ins for some digital providers). Most small SaaS vendors: out of scope; write the one-line applicability decision down, revisit at each size jump or if you become a managed/cloud/DNS service provider.

EU AI Act

Risk-tiered law, phased 2025 to 2027. Selling developer tooling or embedding LLM features rarely lands in high-risk Annex III; general-purpose model duties sit with model providers, not integrators. Integrator duties that do reach a Laravel app: transparency (users must know they interact with AI or AI-generated content: the labeling + disclosure page from the 42001 file), and avoiding prohibited practices (no emotion recognition at work, no social scoring). Track the harmonised standards as they land; your 42001-shaped registry + impact assessments are exactly the preparation.

Priority order for a small EU Laravel SaaS/product vendor

  1. GDPR: law, now (27701 file patterns).
  2. CRA: law with hard dates if you ship product binaries (2026-09-11 reporting readiness first).
  3. 27001-shaped controls without certification: evidence for every questionnaire.
  4. 27701:2025 certification: first certificate if buyers need paper and privacy is your story.
  5. 42001: registry + impact assessments now if you ship AI features; certificate only on buyer demand.
  6. Everything else: when a contract names it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment