<div class="security-guide-layout">
    <aside class="security-guide-nav" aria-label="Security guide contents">
        <span class="security-guide-nav-label">In this guide</span>
        <a href="#securityGuideStart"><i class="bi bi-play-circle"></i> Quick start</a>
        <a href="#securityGuideIdentity"><i class="bi bi-person-vcard"></i> Identity & login</a>
        <a href="#securityGuideEmails"><i class="bi bi-envelope-paper"></i> Account emails</a>
        <a href="#securityGuideSocial"><i class="bi bi-person-badge"></i> Social providers</a>
        <a href="#securityGuideAccess"><i class="bi bi-key"></i> Access control</a>
        <a href="#securityGuideProtection"><i class="bi bi-shield-lock"></i> Strong protection</a>
        <a href="#securityGuideOperations"><i class="bi bi-activity"></i> Operations</a>
        <a href="#securityGuideBuild"><i class="bi bi-box-seam"></i> Build & deploy</a>
        <a href="#securityGuideTroubleshooting"><i class="bi bi-life-preserver"></i> Troubleshooting</a>
    </aside>

    <div class="security-guide-content">
        <section class="security-guide-hero" id="securityGuideStart">
            <span class="security-guide-level">Start here · Basic</span>
            <h6>A safe path from first login to production</h6>
            <p>Security settings belong to the current project. Configure identity first, grant only necessary access, add stronger controls, then rebuild and test the generated application.</p>
            <ol class="security-guide-steps">
                <li><span>1</span><div><strong>Connect users</strong><small>Enable Database authentication and map the account fields.</small></div></li>
                <li><span>2</span><div><strong>Choose entry paths</strong><small>Configure Login, Registration, password recovery, and optional social providers.</small></div></li>
                <li><span>3</span><div><strong>Organize access</strong><small>Create groups and assign users before defining permissions.</small></div></li>
                <li><span>4</span><div><strong>Harden & verify</strong><small>Add 2FA and advanced controls; Save, Build, and test each role.</small></div></li>
            </ol>
        </section>

        <section class="security-guide-section" id="securityGuideIdentity">
            <div class="security-guide-section-title"><i class="bi bi-person-vcard"></i><div><span>Foundation</span><h6>Identity, Login, Registration & Passwords</h6></div></div>
            <div class="security-guide-doc-grid">
                <article><h6>Authentication database</h6><p>Select an existing users table or create one, then map Username, Email, Password, Full name, Role, and Status. After saving the required mappings, use <strong>Add admin user</strong> to create the first generated-app administrator with a password you choose.</p><div class="security-guide-callout is-info"><i class="bi bi-info-circle"></i><span>The user table is the local identity source used by permissions, sessions, social-account links, and generated authentication pages. No known default administrator password is created.</span></div></article>
                <article><h6>Login security</h6><p>This page governs how sign-in <em>behaves</em>; how it <em>looks</em> belongs to Designer → Common Pages → Login, which holds the four sign-in templates. Keeping them apart is deliberate — two systems describing one page is how a chosen template ends up silently overridden.</p><dl><dt>Failed sign-ins</dt><dd>Attempts before lockout, the window they are counted over, how long the lock lasts, and a per-IP ceiling across all accounts.</dd><dt>Abuse throttles</dt><dd>Sign-ups and password-reset requests allowed per IP per window.</dd><dt>Sessions &amp; disclosure</dt><dd>Bounce signed-in users off the auth pages, keep error wording identical for unknown and real accounts, and set the “remember me” lifetime.</dd></dl><div class="security-guide-callout is-info"><i class="bi bi-info-circle"></i><span>Lockout counts live in the <code>auth_attempts</code> table, not the session — a session-based limit is defeated by simply discarding the cookie. If that table is unreachable the checks fail <em>open</em>, because locking every user out of their own application is worse than a slow guess.</span></div></article>
                <article><h6>Registration &amp; activation</h6><p>Public sign-up is closed until you open it, and the generated app refuses the POST outright rather than merely hiding the form.</p><dl><dt>Activation</dt><dd><strong>Active immediately</strong>, <strong>confirm email address</strong> (single-use link, lifetime you choose), or <strong>administrator approval</strong>. Anything other than the first parks the account as <code>pending</code>, and a pending account cannot sign in.</dd><dt>Who may sign up</dt><dd>Restrict by email domain — allow-list or block-list, subdomains included.</dd><dt>Bot defence</dt><dd>A hidden field plus a minimum time on the page. No third-party CAPTCHA, no external request, and nothing for a real user to solve.</dd><dt>Role for new accounts</dt><dd>Never an administrative role.</dd></dl></article>
                <article><h6>Password rules</h6><p>Applied identically on sign-up and on password reset, and enforced on the server — the browser check is only a courtesy.</p><ul><li>Minimum and maximum length. The maximum matters: bcrypt silently ignores everything past 72 bytes.</li><li>Require a number, mixed case, or a symbol.</li><li>Reject common passwords (the usual credential-stuffing list).</li><li>Reject passwords containing the user's own name or email.</li></ul><p>Use <strong>Try a password</strong> at the bottom of that page to see exactly what a user would be told.</p><div class="security-guide-callout is-warning"><i class="bi bi-exclamation-triangle"></i><span>Reset links are single-use, expire on the schedule you set, and revoke every “remember me” token for that account — a reset is how somebody recovers a <em>compromised</em> account, so old cookies must die with the old password.</span></div></article>
            </div>
        </section>

        <section class="security-guide-section" id="securityGuideEmails">
            <div class="security-guide-section-title"><i class="bi bi-envelope-paper"></i><div><span>Communication</span><h6>Account emails</h6></div></div>
            <div class="security-guide-doc-grid">
                <article><h6>What is sent, and when</h6><dl><dt>Verify email address</dt><dd>The confirmation link, when activation is set to email.</dd><dt>Welcome</dt><dd>Once an account becomes active and can sign in.</dd><dt>Awaiting approval / Approved / Declined</dt><dd>The administrator-approval path.</dd><dt>New sign-up</dt><dd>To the administrator address, when somebody is waiting.</dd><dt>Password reset / Password changed</dt><dd>Recovery, and the confirmation that makes an unexpected change noticeable.</dd><dt>Account locked</dt><dd>After repeated failed sign-ins.</dd></dl></article>
                <article><h6>Editing safely</h6><p>You edit the <em>inner</em> HTML only; the branded shell — header, footer, spacing — is shared by every message, so one edit cannot break the others' layout.</p><ul><li>Click a placeholder to insert it at the cursor.</li><li>The preview renders the real composed email with sample data as you type.</li><li><strong>Reset to default</strong> restores the shipped wording for that one template.</li></ul><div class="security-guide-callout is-info"><i class="bi bi-info-circle"></i><span>Resetting content deliberately keeps your on/off switch — restoring wording should not quietly start sending an email you had turned off.</span></div></article>
                <article><h6>Switching one off</h6><p>Each template has its own switch. A disabled template is simply not sent; the surrounding flow still works, so turning off “Welcome” does not stop accounts being created.</p><div class="security-guide-callout is-warning"><i class="bi bi-exclamation-triangle"></i><span>Email needs an SMTP account under <strong>Misc → Email</strong>. Without it the flows still run, but nothing is delivered — so email activation would leave users unable to confirm.</span></div></article>
            </div>
        </section>

        <section class="security-guide-section" id="securityGuideSocial">
            <div class="security-guide-section-title"><i class="bi bi-person-badge"></i><div><span>Connected identity · Intermediate</span><h6>Google, Microsoft, Apple, Facebook, GitHub &amp; LinkedIn</h6></div></div>
            <div class="security-guide-flow" aria-label="Social login flow">
                <span>Generated Login/Register</span><i class="bi bi-arrow-right"></i><span>Provider consent</span><i class="bi bi-arrow-right"></i><span>Verified callback</span><i class="bi bi-arrow-right"></i><span>Local user link</span>
            </div>
            <div class="security-guide-doc-grid">
                <article><h6>Common policy</h6><dl><dt>Social authentication</dt><dd>Master switch for all providers.</dd><dt>Sign in / Sign up</dt><dd>Choose the generated pages that display providers.</dd><dt>Create users</dt><dd>Create a local account when no provider link exists.</dd><dt>Link verified email</dt><dd>Connect to an existing account only when the provider verifies the email.</dd><dt>Default role</dt><dd>Applied only to newly created social users.</dd></dl></article>
                <article><h6>Provider console checklist</h6><ol class="security-guide-steps"><li><span>1</span><div><strong>Create the app</strong><small>A Web/OAuth application in the vendor's console.</small></div></li><li><span>2</span><div><strong>Copy the credentials</strong><small>Client/App/Services ID plus the server secret or signing key.</small></div></li><li><span>3</span><div><strong>Whitelist the callback</strong><small>Use the copy button on each card — the URL must match character for character.</small></div></li><li><span>4</span><div><strong>Enable and save</strong><small>The provider switch and the master switch, then Build.</small></div></li></ol><div class="security-guide-callout is-info"><i class="bi bi-info-circle"></i><span>Each card shows <strong>Ready</strong> or <strong>Incomplete</strong>. The build silently skips an incomplete provider, so a button that never appears on the sign-in page usually means a missing credential here.</span></div></article>
                <article class="security-guide-provider"><span class="is-google"><i class="bi bi-microsoft"></i></span><div><h6>Microsoft</h6><p>Register an application in Entra ID (formerly Azure AD) and add a client <em>secret value</em> — not the secret ID. The <strong>Tenant</strong> field decides who may sign in: <code>common</code> accepts work, school and personal accounts from any organisation, <code>organizations</code> any company, <code>consumers</code> personal only. Paste a tenant GUID to admit exactly one company.</p><a href="https://learn.microsoft.com/entra/identity-platform/quickstart-register-app" target="_blank" rel="noopener noreferrer">Official setup <i class="bi bi-box-arrow-up-right"></i></a></div></article>
                <article class="security-guide-provider"><span class="is-apple"><i class="bi bi-github"></i></span><div><h6>GitHub</h6><p>Create an OAuth app and paste its Client ID and secret. GitHub is not an OpenID Connect provider, so identity is read from its API using the freshly issued token; the primary address and its verified flag come from the user's email list.</p><a href="https://docs.github.com/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app" target="_blank" rel="noopener noreferrer">Official setup <i class="bi bi-box-arrow-up-right"></i></a></div></article>
                <article class="security-guide-provider"><span class="is-facebook"><i class="bi bi-linkedin"></i></span><div><h6>LinkedIn</h6><p>Enable the <strong>Sign In with LinkedIn using OpenID Connect</strong> product on your LinkedIn app, then copy the Client ID and secret from the Auth tab and add the callback under Authorized redirect URLs. Without that product the authorize call fails with an unauthorized-scope error.</p><a href="https://learn.microsoft.com/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2" target="_blank" rel="noopener noreferrer">Official setup <i class="bi bi-box-arrow-up-right"></i></a></div></article>
                <article class="security-guide-provider"><span class="is-google"><i class="bi bi-google"></i></span><div><h6>Google</h6><p>Use an OAuth Web client. Configure the exact authorized redirect URI. Production should use HTTPS; localhost can be used for development.</p><a href="https://developers.google.com/identity/openid-connect/openid-connect" target="_blank" rel="noopener noreferrer">Official setup <i class="bi bi-box-arrow-up-right"></i></a></div></article>
                <article class="security-guide-provider"><span class="is-facebook"><i class="bi bi-facebook"></i></span><div><h6>Facebook</h6><p>Add Facebook Login to a Meta app, enter the App ID/secret, select a supported Graph version, and register the exact Valid OAuth Redirect URI.</p><a href="https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow/" target="_blank" rel="noopener noreferrer">Official setup <i class="bi bi-box-arrow-up-right"></i></a></div></article>
                <article class="security-guide-provider"><span class="is-apple"><i class="bi bi-apple"></i></span><div><h6>Apple</h6><p>Associate a Services ID with a primary App ID and verified web domain. Supply Team ID, Key ID, and the downloaded .p8 key.</p><a href="https://developer.apple.com/documentation/signinwithapple/configuring-your-webpage-for-sign-in-with-apple" target="_blank" rel="noopener noreferrer">Official setup <i class="bi bi-box-arrow-up-right"></i></a></div></article>
                <article><h6>How accounts are linked</h6><p>The generated runtime stores the stable provider subject and local user ID in <code>social_auth_accounts</code>. The table is protected as system-generated. A changed email does not break an existing provider link.</p><div class="security-guide-callout is-success"><i class="bi bi-shield-check"></i><span>Saved secrets are redacted from the Security API and never returned to this screen — a blank secret field preserves the stored value. In the built application they live in <code>generated/auth/social/config.php</code>, which carries only what the social runtime needs and refuses to serve itself if requested directly.</span></div></article>
            </div>
        </section>

        <section class="security-guide-section" id="securityGuideAccess">
            <div class="security-guide-section-title"><i class="bi bi-key"></i><div><span>Authorization · Intermediate</span><h6>Users, Groups & Permissions</h6></div></div>
            <div class="security-guide-doc-grid">
                <article><h6>Users</h6><p>Create and maintain local accounts, status, role data, and group membership. Disabling an account is safer than deleting it when audit history must remain intact.</p></article>
                <article><h6>Groups</h6><p>Model job responsibilities such as Sales, Support, Manager, and Administrator. Assign people to groups instead of maintaining one-off permission sets. A user receives the <strong>combined</strong> access of every group they belong to; their role acts as an implicit group.</p></article>
                <article><h6>Table permissions</h6><p>Grant View, Add, Edit, Delete, Export/Print, or Import for each group and table. Begin with no access and add only required operations.</p></article>
                <article><h6>Page permissions</h6><p>Control access to application pages separately from data operations. Hiding a menu item is presentation—not authorization—so enforce the corresponding permission too.</p></article>
            </div>
            <div class="security-guide-doc-grid">
                <article><h6>Permission mode: Off / Static / Dynamic</h6><p>Choose how authorization is managed for the generated app.</p><dl><dt>Off</dt><dd>No enforcement — every signed-in user reaches every page (legacy behaviour).</dd><dt>Static</dt><dd>Permissions are set here in the console and <em>compiled into the build</em>. Editing them requires a rebuild. Best when access rarely changes.</dd><dt>Dynamic</dt><dd>Permissions live in the generated app's own database and are managed from the in-app <strong>Admin Area</strong>. Changes apply on the user's next request with no rebuild.</dd></dl></article>
                <article><h6>Deny by default</h6><p>When enforcement is on (static or dynamic), a group with no row for a table cannot touch it. Gates run on the <strong>page and the API</strong>, and unavailable row actions are hidden. Owner/superadmin roles and groups flagged as <em>administrator</em> bypass every check, so an app can never lock out its own admins.</p></article>
                <article><h6>In-app Admin Area (dynamic)</h6><p>In dynamic mode an administrator opens <strong>Admin Area</strong> from the user-account menu of the generated app. Five tabs — Permissions (group × table matrix), Users (account CRUD), <strong>Assign</strong> (a users × groups grid for bulk membership at scale), Groups, and a built-in Guide. Everything is protected by the same admin gate and a CSRF token, and <em>Exit Admin Area</em> returns to the app.</p></article>
            </div>
            <div class="security-guide-callout is-warning"><i class="bi bi-key"></i><span><strong>Least privilege:</strong> test with a normal user, a manager, and an administrator. Never validate permissions only while signed in as admin.</span></div>
        </section>

        <section class="security-guide-section" id="securityGuideProtection">
            <div class="security-guide-section-title"><i class="bi bi-shield-lock"></i><div><span>Defense in depth · Advanced</span><h6>2FA, Row-Level Security & Field Encryption</h6></div></div>
            <div class="security-guide-doc-grid">
                <article><h6>Two-factor authentication</h6><p><strong>2FA Setup</strong> is pure configuration: switch on the methods your app offers — authenticator, email, SMS — and map which user-table column holds the email / phone. It never enrols your own device. In the built app each user opens <strong>Two-Factor Authentication</strong> (user menu) — one page that lists the enabled methods and reveals the setup card for whichever they pick — and turns on <em>one</em> of them: scan a locally-rendered QR (the secret never leaves their browser), or enable email/SMS codes, then save one-time backup codes. At sign-in, after the password, the app asks for that method&rsquo;s code — or a backup code — before granting a session. Email/SMS need working SMTP / an SMS gateway; TOTP has no delivery dependency. The setup page&rsquo;s look (heading, subtitle, accent, method labels) <em>and</em> the sign-in challenge template — the same four styles as the Login page (Aurora, Halo, Executive, Verdant) — are chosen in <strong>Designer → Common Pages → Two-Factor</strong>.</p></article>
                <article><h6>Row-Level Security</h6><p>Restrict <em>which records</em> each group sees, after table permission has already allowed the table. <strong>Owner column</strong> rules compare a column to the signed-in user&rsquo;s ID (each user sees only their own rows); <strong>custom conditions</strong> implement departmental or status scopes (e.g. <code>region = 'APAC'</code>). Rules are read live by the generated app — <strong>changes take effect immediately, no rebuild</strong> — and are enforced on the list, view, edit, delete, print and export paths. Administrators and the owner are never restricted.</p><ul><li>Index the columns used in rules for good performance on large tables.</li><li>A user&rsquo;s groups combine with <strong>OR</strong>; a group with no rule grants full access, so a table with no rules shows all rows.</li><li>Always test with a normal (non-admin) user before going live.</li></ul></article>
                <article><h6>Field encryption</h6><p>Switch on any column to encrypt it <strong>at rest</strong>: the built app encrypts the value (<strong>AES-256-GCM</strong>, authenticated) before saving and decrypts it on read, so the list, view, edit, print and export screens show the real value while the database stores only ciphertext. It&rsquo;s <strong>live — no rebuild</strong>. The key lives in the app config (<code>PP_ENC_KEY</code>), never in the database, so a database leak alone can&rsquo;t decrypt it.</p><ul><li>Encrypted columns <strong>can&rsquo;t be searched, sorted, filtered or made unique</strong> — each value encrypts differently. Only encrypt fields you don&rsquo;t query on (IDs are blocked for this reason).</li><li>Give the column room for ciphertext — <code>VARCHAR(255)</code> or <code>TEXT</code>. A too-small column rejects the save; it never corrupts data.</li><li>Existing rows stay readable and are encrypted the next time they&rsquo;re saved — or click <strong>Encrypt existing rows</strong> to protect them all at once (safe to re-run; it skips anything already encrypted).</li><li><strong>Back up the key separately</strong> and keep it out of source control — losing it means losing the data.</li></ul></article>
                <article><h6>Choose the right control</h6><dl><dt>Permission</dt><dd>May this group perform the operation?</dd><dt>Row rule</dt><dd>Which records may the user access?</dd><dt>Encryption</dt><dd>How is a sensitive value stored?</dd><dt>2FA</dt><dd>How strongly is the user identity verified?</dd></dl></article>
            </div>
        </section>

        <section class="security-guide-section" id="securityGuideOperations">
            <div class="security-guide-section-title"><i class="bi bi-activity"></i><div><span>Operational security · Advanced</span><h6>Audit, Locks & Session Control</h6></div></div>
            <div class="security-guide-doc-grid">
                <article><h6>Audit trail</h6><p><strong>Misc → Advanced is the source of truth.</strong> Turn on <em>Enable Audit Trail</em> there (optionally naming the table) and rebuild — the built app then records events into that table and this screen reads them; until then you&rsquo;ll see a prompt to enable it.</p><p><strong>Static or Dynamic.</strong> Each screen carries a mode switch. <em>Static</em> (default) reviews the trail here in the console. <em>Dynamic</em> builds an admin-only Audit Log page <strong>into the generated app</strong> — your customer&rsquo;s administrators open it from the <em>user menu → Audit Log</em>, exactly like the Admin Area (rebuild to publish a mode change).</p><ul><li>Captured automatically: record <strong>create / update / delete</strong> (with a before/after snapshot), <strong>login / logout / failed login / account lock</strong>, and <strong>export</strong> (data leaving the app).</li><li>Filter by user, action and date; open any row to see the full field-level diff.</li><li>Sensitive fields (password, secret, token, api_key) are redacted to <code>***</code> before storage.</li><li>Logs are never purged automatically — archive or trim them with SQL per your retention needs.</li></ul></article>
                <article><h6>Record locks</h6><p>Also governed by <strong>Misc → Advanced</strong> (<em>Record Locking</em>). With it enabled and the app rebuilt, opening a record for edit takes a lock; anyone else who opens it sees a read-only notice naming the holder. A lock releases on save, on leaving the page, or after the configured timeout, and the edit page keeps its own lock alive with a heartbeat.</p><p>Like the Audit Log, this screen has a <strong>Static / Dynamic</strong> switch: Static manages locks here in the console; <em>Dynamic</em> ships an admin-only Record Locks page inside the app (<em>user menu → Record Locks</em>) so your customer can force-release stuck locks themselves.</p><ul><li>Locks protect against concurrent edits — they don&rsquo;t replace permissions or database transactions.</li><li>Fails <strong>open</strong>: if the lock store is unreachable, editing is still allowed rather than blocking users from their own data.</li><li>Force-release a stuck lock here only when necessary — the holder may lose unsaved changes.</li></ul></article>
                <article><h6>Sessions</h6><p><strong>Enforced live in the built app</strong> (rebuild to publish changes). Set the <em>inactivity timeout</em> (idle minutes → auto sign-out), the <em>absolute lifetime</em> (hard cap regardless of activity), and the <em>concurrent-session limit</em> (a new sign-in past the cap evicts the user&rsquo;s oldest session). The built app records every sign-in in <code>user_sessions</code>, so <strong>Active Sessions</strong> lists real live sessions and <em>Terminate</em> / <em>Kill all</em> sign those users out on their very next request. An optional custom <em>session cookie name</em> is applied at boot.</p><ul><li>Both timeouts fail <strong>open</strong> — a storage hiccup never locks a user out of their own app.</li><li>Defaults (30&nbsp;min idle · 24&nbsp;h lifetime · 5 concurrent) apply to every build unless you change them.</li></ul></article>
                <article><h6>Cross-project SSO <span class="badge bg-secondary">Planned</span></h6><p>The JWT secret and PHP session name are stored for future <strong>cross-project single sign-on</strong>, but SSO token exchange is <strong>not wired yet</strong> — setting a shared secret does not currently federate sign-in between projects. The session-name field, however, is applied to the built app&rsquo;s cookie.</p></article>
            </div>
        </section>

        <section class="security-guide-section" id="securityGuideBuild">
            <div class="security-guide-section-title"><i class="bi bi-box-seam"></i><div><span>Delivery</span><h6>Save, Build, Deploy & Verify</h6></div></div>
            <div class="security-guide-checklist">
                <label><input type="checkbox"> Save every changed Security page.</label>
                <label><input type="checkbox"> Build after changing generated authentication behavior.</label>
                <label><input type="checkbox"> Use HTTPS and exact social-provider callback URLs.</label>
                <label><input type="checkbox"> Keep database, OAuth, JWT, and encryption secrets outside public source control.</label>
                <label><input type="checkbox"> Test registration, login, logout, reset, disabled users, and each provider.</label>
                <label><input type="checkbox"> Test permissions and row rules with representative non-admin accounts.</label>
                <label><input type="checkbox"> Verify session expiry, audit entries, error pages, and recovery paths.</label>
                <label><input type="checkbox"> Back up configuration and data before production rollout.</label>
            </div>
            <div class="security-guide-callout is-info"><i class="bi bi-arrow-repeat"></i><span><strong>Save vs Build:</strong> Save stores project configuration. Build publishes relevant settings and provider modules into the generated application.</span></div>
        </section>

        <section class="security-guide-section" id="securityGuideTroubleshooting">
            <div class="security-guide-section-title"><i class="bi bi-life-preserver"></i><div><span>Reference</span><h6>Troubleshooting</h6></div></div>
            <div class="security-guide-troubleshooting">
                <details><summary>Authentication pages do not show my social provider</summary><p>Confirm Database authentication, Social authentication, the provider, and the relevant Sign in/Sign up policy are enabled. Supply all required credentials, save, then rebuild.</p></details>
                <details><summary>The provider reports a redirect mismatch</summary><p>Copy the callback exactly—including scheme, domain, application base path, and callback filename—into the vendor console and Security. Avoid unexpected HTTP-to-HTTPS or hostname changes.</p></details>
                <details><summary>Apple works once but the name is later empty</summary><p>Apple sends the user name only on the first successful authorization. The generated runtime preserves that first response when creating the local user; do not expect it on later callbacks.</p></details>
                <details><summary>A user can open a page but cannot see records</summary><p>Check group membership, page permission, table View permission, and then Row-Level Security. All relevant layers must permit the request.</p></details>
                <details><summary>Encrypted fields cannot be searched normally</summary><p>This is expected for many encryption modes. Use a separate searchable token/hash where appropriate, or avoid encrypting fields that require range queries, sorting, or partial matching.</p></details>
                <details><summary>A configuration appears unchanged in the generated app</summary><p>Save the current Security page, run Build again, deploy the new output, and clear any server opcode or application cache before retesting.</p></details>
            </div>
        </section>
    </div>
</div>
