Skip to content

Release (2026-06-17)#176

Merged
dan2k3k4 merged 15 commits into
prodfrom
dev
Jun 17, 2026
Merged

Release (2026-06-17)#176
dan2k3k4 merged 15 commits into
prodfrom
dev

Conversation

@github-actions

@github-actions github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

Changes in this release:

550687c chore: bump deps
64f14b5 chore: country list
46bd5e6 chore: fix email blocker disposable domains json url
e01c3a9 sec(forms): harden security, resolve config caching issue, and enforce strict trial app db validation
5a1e06a feat(forms): implement secure, public hosted iframe forms with BannedEmail rule and status polling
6876a15 chore: prevent spam users
6265442 chore: tweak sorting, add guard to db seeders
6dd3852 chore: update deps
9e95d89 chore: improve update metadata calls
1d18086 chore: bump up timeouts

Greptile Summary

This release PR ships three related features: a public iframe-hosted trial-signup form (/f/{formSlug}), a layered email-ban system (BanEmailsCommand, EmailBlockerService, BannedEmail rule, polydock_banned_patterns migration), and a hardened RegisterController that applies both BannedEmail validation and SensitiveDataRedactor to every log call. The PR also guards the DatabaseSeeder behind an environment check and fixes the deploy-key loading to be safe in CI.

  • Hosted form flow (FormController, BaseHostedForm, DrupalAIDemoDrupalOrgForm, blade views): renders an iframe-safe form with strict CSP frame-ancestors, validates the trial_app UUID against a public-store subquery, calls the reCAPTCHA API, creates a UserRemoteRegistration, and returns a UUID for the client to poll.
  • Email ban system (BanEmailsCommand, EmailBlockerService, BannedEmail rule): domain/wildcard patterns stored in polydock_banned_patterns are matched via SQL LIKE with a safe escaping chain; a secondary disposable-domain file check is layered on top and cached as a singleton.
  • Security hardening (RegisterController, AuthenticatedApiController): credential fields in log output are now redacted via SensitiveDataRedactor; the BannedEmail rule is applied to all three registration entry points.

Confidence Score: 5/5

The PR is safe to merge; all new entry points validate input thoroughly and the credential-logging issue from the previous review has been addressed.

The three new entry points (FormController, RegisterController, AuthenticatedApiController) all apply the BannedEmail rule and run through the full validation pipeline before touching the database. The credential-logging concern raised in the last review round is now resolved via SensitiveDataRedactor. Remaining notes are forward-looking (the trial_app null-coalescing guard in BaseHostedForm for hypothetical future forms, the hardcoded poll URL, and the in-memory cache staleness for queue workers) and none block current functionality.

No files require special attention for merge. The BaseHostedForm::transformPayload null-coalescing gap is worth addressing before adding a second HostedForm implementation that omits trial_app.

Important Files Changed

Filename Overview
app/Http/Controllers/FormController.php New public hosted-form controller: handles GET (renders form with CSP headers) and POST (validates, reCAPTCHA, creates UserRemoteRegistration). Logic is solid; minor concern that $regions and $regionsData are both passed when only $regionsData is needed by JS.
app/Services/EmailBlockerService.php New service: checks emails against DB banned patterns (SQL LIKE) and a local disposable-domains JSON file. Singleton-bound; in-memory cache is safe for PHP-FPM but won't reflect file updates until queue workers restart.
app/Console/Commands/BanEmailsCommand.php New admin command: normalizes ban patterns, finds affected users/registrations/app instances, then atomically bans patterns, marks registrations failed, queues purges, and deletes empty groups inside a DB transaction.
app/Forms/BaseHostedForm.php Abstract base class for hosted forms: builds allowed embed origins (with localhost wildcard for non-prod), reads reCAPTCHA config, and transforms validated data. The trial_app key in transformPayload is accessed without null-coalescing.
resources/views/forms/drupal-ai-demo.blade.php New form blade: dynamically populates app dropdown via JS, submits via fetch with CSRF token, polls /api/register/{uuid} every 5 s to show credentials. Polling URL is hardcoded rather than generated from a named route.
app/Http/Controllers/Api/RegisterController.php Added BannedEmail validation to processRegister, and SensitiveDataRedactor to all three log calls. Credential exposure concern from the previous polling-based flow has been addressed by redaction.
database/seeders/DatabaseSeeder.php All seed data now guarded by environment check (non-prod only); deploy-key loading made safe with file_exists guard to avoid fatal errors in test environments.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant IframeForm as /f/{formSlug}
    participant FormController
    participant Validator
    participant EmailBlockerService
    participant PolydockBannedPattern as DB: polydock_banned_patterns
    participant UserRemoteRegistration as DB: user_remote_registrations
    participant RegisterController as /api/register/{uuid}

    User->>IframeForm: GET /f/drupal-ai-demo
    IframeForm->>FormController: show()
    FormController->>FormController: Query public stores + trial apps
    FormController-->>User: Render form (CSP frame-ancestors set)

    User->>IframeForm: POST submit
    IframeForm->>FormController: submit()
    FormController->>Validator: Validate fields + BannedEmail rule
    Validator->>EmailBlockerService: checkEmail(email)
    EmailBlockerService->>PolydockBannedPattern: SQL LIKE match
    EmailBlockerService->>EmailBlockerService: Check disposable domains file
    EmailBlockerService-->>Validator: EmailBlockerResult
    alt Email blocked
        Validator-->>User: 422 Unprocessable
    end
    FormController->>FormController: reCAPTCHA verification
    FormController->>UserRemoteRegistration: create(email, request_data)
    FormController-->>User: "202 Accepted {status: pending, id: uuid}"

    loop Poll every 5s (up to 7 min)
        User->>RegisterController: "GET /api/register/{uuid}"
        RegisterController->>UserRemoteRegistration: find by uuid
        RegisterController-->>User: "{status, result_data}"
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant IframeForm as /f/{formSlug}
    participant FormController
    participant Validator
    participant EmailBlockerService
    participant PolydockBannedPattern as DB: polydock_banned_patterns
    participant UserRemoteRegistration as DB: user_remote_registrations
    participant RegisterController as /api/register/{uuid}

    User->>IframeForm: GET /f/drupal-ai-demo
    IframeForm->>FormController: show()
    FormController->>FormController: Query public stores + trial apps
    FormController-->>User: Render form (CSP frame-ancestors set)

    User->>IframeForm: POST submit
    IframeForm->>FormController: submit()
    FormController->>Validator: Validate fields + BannedEmail rule
    Validator->>EmailBlockerService: checkEmail(email)
    EmailBlockerService->>PolydockBannedPattern: SQL LIKE match
    EmailBlockerService->>EmailBlockerService: Check disposable domains file
    EmailBlockerService-->>Validator: EmailBlockerResult
    alt Email blocked
        Validator-->>User: 422 Unprocessable
    end
    FormController->>FormController: reCAPTCHA verification
    FormController->>UserRemoteRegistration: create(email, request_data)
    FormController-->>User: "202 Accepted {status: pending, id: uuid}"

    loop Poll every 5s (up to 7 min)
        User->>RegisterController: "GET /api/register/{uuid}"
        RegisterController->>UserRemoteRegistration: find by uuid
        RegisterController-->>User: "{status, result_data}"
    end
Loading

Comments Outside Diff (1)

  1. app/Http/Controllers/Api/RegisterController.php, line 107-112 (link)

    P1 security showRegister logs full registration record including credentials on every poll

    Line 111 calls Log::info('Showing user remote registration', ['registration' => $registration->toArray()]). After a successful trial provision, result_data contains app_admin_password and app_admin_username. Since the new form polls this endpoint every 5 seconds until success (up to 7 minutes), credentials will be written to application logs repeatedly. This is a pre-existing call, but the new public polling flow dramatically increases the frequency of credential exposure in logs.

Reviews (5): Last reviewed commit: "chore: singleton" | Re-trigger Greptile

Comment thread resources/views/forms/drupal-ai-demo.blade.php
Comment thread routes/web.php
Comment thread resources/views/layouts/form-iframe.blade.php
@dan2k3k4 dan2k3k4 merged commit ee05a18 into prod Jun 17, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant