Skip to content

DO NOT MERGE - Track to upstream#3

Open
dnplkndll wants to merge 16 commits intomasterfrom
kencove
Open

DO NOT MERGE - Track to upstream#3
dnplkndll wants to merge 16 commits intomasterfrom
kencove

Conversation

@dnplkndll
Copy link

@dnplkndll dnplkndll commented Feb 16, 2026

Summary by CodeRabbit

  • New Features

    • Google OAuth2 sign-in, SMS invitations, webhook signing & verification, company logo upload by URL, SMS settings panel
  • Improvements

    • Product renamed to Kencove eSign; streamlined UI by removing gated/promotional prompts; automatic webhook signing-key handling; added i18n messages
  • Infrastructure

    • Docker multi-arch build & publish workflow via Cloud Build; removed Rollbar client in favor of Rails internal logging

dnplkndll and others added 13 commits February 15, 2026 09:37
Enable Google OAuth2 login for self-hosted DocuSeal when GOOGLE_CLIENT_ID
env var is present. Adds GitHub Actions workflow to build amd64 images and
push to Google Artifact Registry via Workload Identity Federation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove all "Unlock with DocuSeal Pro" placeholder gates from views
- Enable previously gated features: editor/viewer roles, email reminders,
  reply-to fields, esign preferences, SMTP security options
- Remove enterprise path blocking from errors controller
- Remove Rollbar error tracking (JS client, meta tags, all Ruby calls)
  and replace with Rails.logger equivalents
- Gut newsletter and enquiries phone-home controllers
- Gut console redirect controller and remove upgrade/plans links
- Remove "Powered by DocuSeal" attribution and demo signup links
- Remove pricing links from Vue components (fields, formulas, conditions)
- Remove upgrade button from navbar and Plans/Pro badge from settings
- Clean up docuseal.rb constants (NEWSLETTER_URL, ENQUIRIES_URL)
- Remove docuseal.com domain handling from application controller

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add COMPANY_LOGO_URL_KEY to AccountConfig model
- Add logo URL to PersonalizationSettingsController ALLOWED_KEYS
- Implement logo URL form in personalization settings with preview
- Display custom logo in signing form banner when configured
- Display custom logo in submission detail view when configured
- Falls back to default DocuSeal logo when no URL is set

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make omniauth_callbacks route conditional on GOOGLE_CLIENT_ID presence
to match the User model's conditional devise :omniauthable. Fix erblint
offenses: self-closing img tags, rescue modifier, and safe navigation
chain length.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…etry

Remove Pro feature gates, subscription code, and Rollbar telemetry
Use only short SHA and semver tags for immutable image references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Every outgoing webhook request is now signed with HMAC-SHA256 using a
per-webhook signing key. Receivers can verify authenticity via three new
headers: X-Webhook-Signature (sha256=<digest>), X-Webhook-Timestamp,
and X-Webhook-Request-Id. Signing keys are auto-generated for new
webhooks and lazily provisioned for existing ones. The webhook secret
UI now displays the signing key with a verification example and
regenerate option.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace DocuSeal orange quill logo with Kencove fence-post icon in
dark blue (#1E3A5F). Update product name from DocuSeal to Kencove eSign.
Applied across shared logo partial, public logo.svg, Vue logo component,
and navbar title.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire up the existing SMS plumbing with actual Twilio delivery: SendSms
module, SendSubmitterInvitationSmsJob, SMS settings form, re-send SMS
button, and automatic dispatch alongside email in send_signature_requests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

Adds Google OAuth2 sign-in, Twilio SMS invitations and settings, HMAC-SHA256-signed webhooks, removes Rollbar client/server integration, updates branding to "Kencove eSign", simplifies promotional/permission UI, adds Google Artifact Registry Docker CI, and multiple related controller/view/model updates.

Changes

Cohort / File(s) Summary
CI / Cloud Build
/.github/workflows/docker-gar.yml, cloudbuild.yaml
Add GitHub Actions workflow and Cloud Build config to build and push multi-arch Docker images to Google Artifact Registry (tagging, auth, buildx, caching).
Dependencies & JS
Gemfile, package.json, app/javascript/rollbar.js
Add omniauth gems; remove rollbar npm dependency and delete client-side Rollbar integration file.
Devise / OmniAuth integration
config/initializers/devise.rb, config/routes.rb, app/controllers/omniauth_callbacks_controller.rb, lib/users.rb, app/models/user.rb
Conditional Google OAuth2 setup (env-gated), dynamic devise routes, OmniauthCallbacksController, and Users.from_omniauth user-lookup/auto-create logic; User model opts into omniauthable when configured.
SMS: send, job, settings, views, routes
lib/send_sms.rb, app/jobs/send_submitter_invitation_sms_job.rb, app/controllers/submitters_send_sms_controller.rb, app/controllers/sms_settings_controller.rb, lib/accounts.rb, app/models/account_config.rb, app/models/encrypted_config.rb, app/views/sms_settings/*, app/views/submissions/_send_sms*.erb, config/routes.rb
Introduce SendSms module, SMS job, controller, settings form and storage, account SMS template key, can_send_sms? helper, new routes and submission views for SMS sending and toggles.
Webhook signing: model, migration, lib, job, UI, locales
app/models/webhook_url.rb, db/migrate/*_add_signing_key_to_webhook_urls.rb, lib/send_webhook_request.rb, app/jobs/send_test_webhook_request_job.rb, app/views/webhook_secret/show.erb, config/locales/i18n.yml
Add signing_key column, generation/ensure callbacks, encrypt signing_key, sign_request HMAC-SHA256 header scheme, update test job to include signed headers, UI to view/regenerate key and i18n strings.
Rollbar removal / server logging changes
many files: app/controllers/**, lib/**, app/jobs/**, config/sidekiq.yml, app/views/layouts/*
Remove Rollbar queue and client includes; replace server Rollbar.* calls with Rails.logger.* across controllers, jobs, and libs; remove Rollbar asset/meta tags and related conditional loading.
Branding & logo
lib/docuseal.rb, app/views/shared/_logo.html.erb, app/javascript/template_builder/logo.vue, app/views/shared/_title.html.erb, app/views/submissions/_logo.html.erb, app/views/submit_form/_docuseal_logo.html.erb
Change product name to "Kencove eSign", update shared SVG/logo assets, add account-configurable company logo URL fallback and form.
UI: remove promos, gating, simplify flows
many frontend/view files app/javascript/*, app/views/*, app/controllers/console_redirect_controller.rb, app/controllers/newsletters_controller.rb, app/controllers/enquiries_controller.rb
Remove upgrade/demo promotional banners, “learn more” external links, enterprise gating and many placeholders; simplify console redirect, newsletter and enquiry endpoints (no external POSTs).
Personalization / permissions simplification
app/controllers/personalization_settings_controller.rb, app/views/personalization_settings/*, app/views/*settings_nav*, users/_role_select.html.erb, app/views/*
Permit company logo key, render previously gated forms and options unconditionally in multiple views, enable role options and simplify settings nav (remove pro/tenant blocks).
Misc. features & fixes
app/jobs/send_test_webhook_request_job.rb, app/jobs/send_submitter_invitation_email_job.rb, lib/send_webhook_request.rb, lib/templates/*, lib/params/*
Add request signing, logging replacements, minor control-flow simplifications, PDF handling logging changes, and related localized strings.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Devise as Devise/OmniAuth
    participant Google as Google OAuth2
    participant OmniauthCB as OmniauthCallbacksController
    participant UsersLib as Users Module
    participant DB as Database

    User->>Devise: Click "Sign in with Google"
    Devise->>Google: Redirect to Google auth
    Google->>User: Authorize app
    Google->>Devise: Callback with OAuth data
    Devise->>OmniauthCB: Route to google_oauth2
    OmniauthCB->>UsersLib: Users.from_omniauth(oauth)
    UsersLib->>DB: Find or create user by email (domain checks)
    DB-->>UsersLib: user or nil
    UsersLib-->>OmniauthCB: user or nil
    alt user found & active
        OmniauthCB->>Devise: sign_in(user)
        OmniauthCB-->>User: Redirect to dashboard
    else
        OmniauthCB-->>User: Redirect to sign_in with alert
    end
Loading
sequenceDiagram
    participant Submitter
    participant Controller as SubmittersSendSmsController
    participant DB as Database
    participant Job as SendSubmitterInvitationSmsJob
    participant SMS as SendSms
    participant Twilio as Twilio API

    Submitter->>Controller: POST /submitters/:slug/send_sms
    Controller->>DB: Load submitter & check recent send_sms events
    alt recent send exists (<=10h)
        DB-->>Controller: recent event found
        Controller-->>Submitter: Redirect back with alert
    else
        DB-->>Controller: no recent event
        Controller->>Job: Enqueue job (submitter_id)
        Controller->>DB: update submitter.sent_at if nil
        Controller-->>Submitter: Redirect back with notice
        Job->>DB: Load submitter & account SMS config
        Job->>SMS: SendSms.call(...)
        SMS->>Twilio: POST /Messages (Basic Auth)
        Twilio-->>SMS: 200/4xx
        alt Success
            SMS-->>Job: success
        else Failure
            SMS-->>Job: raise SmsError
        end
        Job->>DB: Log SubmissionEvent(type: send_sms) and update sent_at
    end
Loading
sequenceDiagram
    participant App
    participant Job as SendTestWebhookRequestJob
    participant Webhook as WebhookUrl Model
    participant Signer as SendWebhookRequest
    participant Endpoint as External Webhook

    App->>Job: Enqueue with webhook_url_id & payload
    Job->>Webhook: Load webhook_url (includes signing_key)
    Job->>Signer: call(webhook_url, payload)
    Signer->>Signer: build JSON body
    Signer->>Signer: compute timestamp and request-id
    Signer->>Signer: compute HMAC-SHA256("timestamp.body", signing_key)
    Signer->>Endpoint: POST body with headers X-Webhook-Signature, X-Webhook-Timestamp, X-Webhook-Request-Id
    Endpoint-->>Signer: response
    alt success
        Signer-->>Job: success
    else
        Signer-->>Job: raise / retry
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I hopped through diffs with eager cheer,

Swapped Rollbar for Rails, the logs now clear,
SMS wings flutter, Google doors swing wide,
Webhooks wear signatures, logos swell with pride,
Kencove bounces forward — onward we glide! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title "DO NOT MERGE - Track to upstream" does not describe the actual changes in the PR. The changeset implements significant feature additions (Google OAuth2, SMS functionality, webhooks signing) and removals (Rollbar integration, pro features), but the title provides no meaningful information about these changes. Replace with a descriptive title summarizing the main changes, such as: "Remove Rollbar integration and add Google OAuth2 + SMS support" or "Migrate from Rollbar to Rails logger and add SMS/OAuth2 features".
Docstring Coverage ⚠️ Warning Docstring coverage is 7.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kencove

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/javascript/submission_form/signature_step.vue (1)

290-300: ⚠️ Potential issue | 🟠 Major

href="#" with target="_blank" opens a useless blank tab.

The disclosure link now points to "#" but still carries target="_blank", so clicking it opens a new browser tab at the current page URL—confusing for users expecting to read an e-signature disclosure.

Beyond the UX issue, if this disclosure link is required for e-signature legal compliance (ESIGN Act / UETA), rendering a non-functional link may not satisfy the disclosure obligation.

Consider either:

  1. Pointing to an actual disclosure page hosted in-app, or
  2. Removing the link (and target="_blank") entirely if no disclosure is needed.
Proposed fix (option 2 — remove dead link)
-      {{ t('by_clicking_you_agree_to_the').replace('{button}', buttonText.charAt(0).toUpperCase() + buttonText.slice(1)) }} <a
-        href="#"
-        target="_blank"
-      >
-        <span class="inline md:hidden">
-          {{ t('esignature_disclosure') }}
-        </span>
-        <span class="hidden md:inline">
-          {{ t('electronic_signature_disclosure') }}
-        </span>
-      </a>
+      {{ t('by_clicking_you_agree_to_the').replace('{button}', buttonText.charAt(0).toUpperCase() + buttonText.slice(1)) }}
+      {{ t('electronic_signature_disclosure') }}
app/views/shared/_settings_nav.html.erb (1)

26-30: ⚠️ Potential issue | 🔴 Critical

Use EncryptedConfig::SMS_KEY constant instead of hardcoded string 'submitter_invitation_sms'.

The code checks for 'submitter_invitation_sms', which is an AccountConfig constant used for message templates, not the actual SMS configuration storage key. Per EncryptedConfig model, SMS settings are stored under EncryptedConfig::SMS_KEY ('sms_configs'). Replace with the correct constant to ensure the permission check validates against the actual SMS configuration.

Also note: Line 68 has the same hardcoding issue with 'saml_configs' which lacks a defined constant.

🤖 Fix all issues with AI agents
In @.github/workflows/docker-gar.yml:
- Around line 25-31: The "Create .version file" step is vulnerable because
github.ref_name is interpolated directly into the shell; change the step to pass
github.ref_name into an environment variable (e.g., REF_NAME="${{
github.ref_name }}" ) and then write that variable to .version using a safe
write (e.g., printf '%s' "$REF_NAME" > .version) and similarly handle github.sha
via an env var (e.g., SHA="${{ github.sha }}" ) so the shell never directly
evaluates untrusted tag names; update the logic in the step (the block that
references github.ref_name and github.sha) to use these env vars and safe
printing instead of direct interpolation.

In `@app/controllers/sessions_controller.rb`:
- Around line 27-31: The after_sign_in_path_for method currently returns
params[:redir] unvalidated which allows open redirect; fix by validating
params[:redir] before returning: only accept a safe relative path (e.g. begins
with "/" but not "//" or contains a scheme/host) or parse it with URI.parse and
ensure its host is blank or matches request.host/your trusted host list; if
validation fails, fall back to super. Update the logic in after_sign_in_path_for
to perform this whitelist check on params[:redir] (referencing params[:redir]
and after_sign_in_path_for) and return super when the value is unsafe.

In `@app/controllers/sms_settings_controller.rb`:
- Around line 16-19: The rescue block in SmsSettingsController currently rescues
StandardError (rescue StandardError => e) and exposes e.message to the user;
change this to rescue only the expected exceptions (for example
Twilio::REST::RestError and ActiveRecord::RecordInvalid) or replace the flashed
e.message with a generic user-facing message (e.g., "An error occurred while
updating SMS settings") while logging the real error internally (use
Rails.logger.error or similar) before rendering :index with status
:unprocessable_content; update the rescue clause to list specific exception
classes or add a fallback rescue that logs the exception and sets a generic
flash[:alert] instead of e.message.

In `@app/controllers/submitters_send_sms_controller.rb`:
- Around line 7-17: The job is enqueued before persisting `@submitter` so if
`@submitter.save`! fails the SMS still sends; move the persistence step before
enqueuing (set `@submitter.sent_at` ||= Time.current and call `@submitter.save`!
first), then enqueue with
SendSubmitterInvitationSmsJob.perform_async('submitter_id' => `@submitter.id`)
only after a successful save, or alternatively use an after_commit/transaction
hook to enqueue the job so the job runs only if the record was committed.

In `@app/javascript/template_builder/payment_settings.vue`:
- Around line 197-203: The anchor rendered in payment_settings.vue when
!isConnected uses href="#" with target="_blank" and data-turbo="false", which
opens a confusing blank tab; either remove the dead link or make it inert:
change the v-if block that renders the anchor (the element using
v-if="!isConnected") to not render an anchor at all (replace with a plain
span/button or remove the block) or, if you must keep it visible, remove
target="_blank" and data-turbo="false" and set href to a real URL or to
"javascript:void(0)"/null so it doesn't open a new tab -- update the template
where the anchor is declared accordingly.

In `@app/jobs/send_submitter_invitation_sms_job.rb`:
- Line 7: The job currently calls Submitter.find(params['submitter_id']) which
will raise ActiveRecord::RecordNotFound if the record was deleted and cause
repeated Sidekiq retries; change this to use Submitter.find_by(id:
params['submitter_id']) and return early when nil (mirroring
SendTestWebhookRequestJob's pattern) so the job gracefully exits when the
submitter no longer exists.

In `@app/views/personalization_settings/_logo_form.html.erb`:
- Around line 13-15: Replace the hardcoded "Current logo" string in the span
inside the _logo_form.html.erb partial with a call to the Rails I18n helper (use
t()) so it follows the same translation pattern as the rest of the form; update
the locale YAML with an appropriate key (e.g.
views.personalization_settings._logo_form.current_logo or use the view-scoped
key via t('.current_logo')) and ensure the span text uses that translation.

In `@app/views/shared/_logo.html.erb`:
- Around line 2-3: ERB linter flags "No space detected where there should be a
single space" for the two long SVG <path> elements in _logo.html.erb (the <path
fill="currentColor" d="M45.92..." /> and the second <path fill="currentColor"
d="M47.19..." />); fix by either inserting the expected single-space(s) in the
offending attribute value(s) so the linter rule is satisfied, or wrap only those
two <path> lines with a targeted erb-lint disable/enable comment for the
specific rule (i.e., add an erb-lint disable before the first <path> and
re-enable after the second) to avoid changing SVG data.

In `@app/views/submissions/_send_sms_button.html.erb`:
- Line 3: The SMS button currently uses submitter.sent_at? to choose its label
which is incorrect because sent_at is set for any message type; update the view
logic that builds the button label (the call to button_title in
_send_sms_button.html.erb) to check SubmissionEvent records for this submitter
with event_type == 'send_sms' (e.g., SubmissionEvent.exists?(submitter_id:
submitter.id, event_type: 'send_sms')) and use that to decide between
t('re_send_sms') and t('send_sms'); keep the existing path helper
submitter_send_sms_index_path and disabled_with behavior unchanged.

In `@app/views/submissions/_send_sms.html.erb`:
- Around line 9-23: Replace the three hardcoded English strings in the
_send_sms.html.erb partial ("SMS not configured", "Configure SMS settings to
send text messages.", "Go to SMS settings") with i18n lookups using t(...) and
unique keys (e.g. use keys like submissions.sms.not_configured,
submissions.sms.configure_prompt, submissions.sms.go_to_settings) so the view
uses t('...') consistently; also add those keys and English values to the locale
file to avoid missing translation errors and ensure the link text uses the
t(...) result inside the anchor (refer to the partial and existing uses of t()
to mirror style).

In `@config/locales/i18n.yml`:
- Around line 356-357: Remove the duplicate yaml key by deleting the earlier
`sms_has_been_sent` entry (the one paired with `sms_has_been_sent_already`) and
keep the existing `sms_has_been_sent` definition that appears later; ensure only
a single `sms_has_been_sent` key remains and verify `sms_has_been_sent_already`
is still present and correctly placed to avoid YAML key collisions.
- Around line 649-650: The YAML file contains duplicate locale keys re_send_sms
and send_sms (previously defined earlier), which causes YAML linting errors;
remove the duplicate entries (or rename them if they are meant for a different
context) and ensure any code/view references use the single canonical key
(re_send_sms, send_sms) or the new name you choose so translations remain
consistent; update any tests/templates that referenced the removed duplicates to
point at the retained key.

In `@lib/send_sms.rb`:
- Around line 13-20: The Faraday connection in send_sms.rb (the conn created by
Faraday.new and used to post the Twilio request) lacks timeouts; update the
Faraday client construction to set reasonable open and read timeouts (e.g.
open_timeout: 8, read_timeout: 15) to mirror SendWebhookRequest, so the Sidekiq
worker won’t block indefinitely when Twilio is slow or unresponsive; modify the
Faraday.new options passed to conn (or its request options) to include these
timeout values before calling conn.basic_auth and conn.post.
- Line 18: The call to conn.basic_auth is invalid for Faraday 2.x; remove the
conn.basic_auth(account_sid, auth_token) call and instead configure basic auth
in the Faraday connection setup by adding the authorization middleware inside
the Faraday.new block (e.g. use conn.request :authorization, :basic,
account_sid, auth_token when building the Faraday connection object named conn
in lib/send_sms.rb) so authentication is applied via middleware rather than
calling basic_auth.

In `@lib/submissions/generate_result_attachments.rb`:
- Line 280: Replace the bare Rails.logger.error(e) calls with logging that
includes the full exception class, message and backtrace so you don't lose stack
traces; for example, change the Rails.logger.error(e) occurrences in
generate_result_attachments.rb (the error handlers around the PDF generation
paths and the Rollbar→Rails.logger replacements at the locations noted) to log
something like "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}" (or use
e.full_message if available) and apply the same replacement for all other
instances referenced (the other Rails.logger.error(e) occurrences in this file).
🧹 Nitpick comments (26)
app/controllers/console_redirect_controller.rb (1)

3-9: Consider whether this controller is still needed.

With the action reduced to a bare redirect_to root_path, this controller is essentially a no-op redirect. If the route can be replaced with a simple route-level redirect (e.g., get '/console', to: redirect('/') in routes.rb), the controller could be removed entirely.

Not blocking — the current code is correct and harmless.

lib/submissions/timestamp_handler.rb (1)

56-59: Include the backtrace when logging the exception.

Rails.logger.error(e) only logs e.message (via to_s), discarding the backtrace. Since this rescue catches all StandardErrors and silently falls back to a local timestamp, losing the stack trace will make production debugging difficult.

♻️ Proposed fix
     rescue StandardError => e
-      Rails.logger.error(e)
+      Rails.logger.error("#{e.class}: #{e.message}\n#{e.backtrace&.join("\n")}")
lib/action_mailer_events_observer.rb (1)

29-32: Log the full backtrace, not just the exception message.

Rails.logger.error(e) calls e.to_s, which logs only the message string and discards the backtrace. Since this is a rescue StandardError in a mail delivery callback, you'll want the full stack trace for debugging production failures.

🔧 Suggested fix
-    Rails.logger.error(e)
+    Rails.logger.error("#{e.class}: #{e.message}\n#{e.backtrace&.join("\n")}")
package.json (1)

36-37: Stray blank line left after removing rollbar.

Nit: there's a trailing blank line at line 36 between "qr-creator" and "sass".

Suggested fix
     "qr-creator": "^1.0.0",
-
     "sass": "^1.62.1",
lib/templates/process_document.rb (1)

158-161: Consider logging the backtrace along with the exception message.

Rails.logger.warn(e) only logs e.to_s (the message). The backtrace is lost, which can make debugging Vips/Pdfium rendering failures difficult. Since this rescue returns nil and silently skips the page, having the backtrace would be valuable.

This applies to all the Rollbar → Rails.logger replacements across the PR.

Suggested improvement
-      Rails.logger.warn(e)
+      Rails.logger.warn("#{e.class}: #{e.message}\n#{e.backtrace&.first(5)&.join("\n")}")
app/views/accounts/show.html.erb (1)

219-237: Remove dead if true conditionals instead of leaving them as no-ops.

Lines 219 and 238 use <% if true %> which is functionally meaningless. If the intent is to make these blocks unconditional, remove the if/end wrapper entirely for clarity.

♻️ Suggested fix (line 219 example)
-        <% if true %>
           <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ENFORCE_SIGNING_ORDER_KEY) %>
           ...
-        <% end %>

Apply the same pattern at line 238.

lib/params/base_validator.rb (1)

17-18: Consider logging the backtrace for unexpected StandardError.

Rails.logger.error(e) only logs e.to_s (the message). Unlike Rollbar, no backtrace is captured, making production debugging harder for unexpected errors.

♻️ Suggested fix
-      Rails.logger.error(e)
+      Rails.logger.error("#{e.class}: #{e.message}\n#{e.backtrace&.join("\n")}")
app/views/email_smtp_settings/index.html.erb (1)

38-50: Same if true dead conditional — remove the wrapper entirely.

Same pattern as in accounts/show.html.erb. Remove the no-op <% if true %> / <% end %> to keep the template clean.

lib/docuseal.rb (1)

5-14: Stale branding constants — several URLs and identifiers still reference DocuSeal.

PRODUCT_NAME was updated to 'Kencove eSign', but PRODUCT_URL, SUPPORT_EMAIL, GITHUB_URL, DISCORD_URL, TWITTER_URL, and TWITTER_HANDLE still point to DocuSeal resources. If this is a white-label deployment, these should be updated or made configurable (e.g., via ENV vars) to avoid exposing end-users to upstream branding.

app/views/shared/_settings_nav.html.erb (1)

68-68: Extract 'saml_configs' to a constant for consistency.

Lines 16, 21, and 37 use EncryptedConfig::*_KEY constants for configuration keys. Line 68 should follow the same pattern to reduce risk of typos and ensure maintainability. Note: This requires creating the EncryptedConfig::SAML_CONFIGS_KEY constant, which is also referenced in app/controllers/sso_settings_controller.rb as a hardcoded string.

app/views/personalization_settings/_documents_copy_email_form.html.erb (2)

17-22: Remove dead if true conditional.

<% if true %> is dead code — the block always renders. Strip the if/end wrapper entirely rather than leaving a no-op guard.

♻️ Suggested cleanup
-        <% if true %>
          <div class="form-control">
            <%= ff.label :reply_to, t('reply_to'), class: 'label' %>
            <%= ff.email_field :reply_to, class: 'base-input', dir: 'auto', placeholder: t(:email) %>
          </div>
-        <% end %>

44-51: Same dead if true conditional — remove the wrapper.

♻️ Suggested cleanup
-          <% if true %>
            <div class="flex items-center justify-between mx-1">
              <span>
                <%= t('send_emails_automatically_on_completion') %>
              </span>
              <%= ff.check_box :enabled, { checked: ff.object.enabled != false, class: 'toggle' }, 'true', 'false' %>
            </div>
-          <% end %>
app/views/personalization_settings/_logo_form.html.erb (1)

6-6: No server-side URL validation on the logo value.

The url_field provides client-side validation only. The controller (PersonalizationSettingsController#create) accepts any string value for this key. Consider adding a server-side format check (e.g., must start with https://) to prevent storing invalid or potentially abusive URIs (e.g., http:// internal addresses).

app/views/templates_preferences/_recipients.html.erb (1)

83-96: Remove dead if true conditional — same pattern as in the other modified views.

♻️ Suggested cleanup
-  <% if true %>
     <%= form_for template, url: template_preferences_path(template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
       ...
     <% end %>
-  <% end %>
app/views/templates_preferences/_submitter_documents_copy_email_form.html.erb (1)

33-38: Remove dead if true conditional — same cleanup as flagged in the other views.

.github/workflows/docker-gar.yml (1)

3-8: Tag pattern *.*.* is very permissive.

This matches any tag containing two dots (e.g., foo.bar.baz). If the intent is to trigger only on semver tags, consider tightening the pattern (e.g., v[0-9]*.[0-9]*.[0-9]* or [0-9]*.[0-9]*.[0-9]*).

app/controllers/embed_scripts_controller.rb (1)

4-4: .freeze was removed from the DUMMY_SCRIPT constant.

The previous version froze this heredoc string. Freezing string constants is a Ruby best practice — it enables string deduplication and prevents accidental mutation. Consider restoring it.

♻️ Suggested fix
-  DUMMY_SCRIPT = <<~JAVASCRIPT
+  DUMMY_SCRIPT = <<~JAVASCRIPT.freeze
app/jobs/send_test_webhook_request_job.rb (1)

39-48: Duplicated signing logic — consider reusing SendWebhookRequest.sign_request.

The HMAC signing (key retrieval, timestamp, signature computation, header assignment) is duplicated between here and lib/send_webhook_request.rb lines 60–68. If the signing scheme changes, both sites must be updated in lockstep.

Consider extracting a shared helper that returns the signing headers hash, usable by both the block-style and positional-argument-style Faraday calls:

♻️ Suggested refactor

In lib/send_webhook_request.rb, add a method that returns headers:

def signing_headers(body, webhook_url)
  key = webhook_url.ensure_signing_key!
  timestamp = Time.current.to_i.to_s
  signature = OpenSSL::HMAC.hexdigest('SHA256', key, "#{timestamp}.#{body}")

  {
    'X-Webhook-Signature' => "sha256=#{signature}",
    'X-Webhook-Timestamp' => timestamp,
    'X-Webhook-Request-Id' => SecureRandom.uuid
  }
end

Then in this job:

-    key = webhook_url.ensure_signing_key!
-    timestamp = Time.current.to_i.to_s
-    signature = OpenSSL::HMAC.hexdigest('SHA256', key, "#{timestamp}.#{body}")
-
     Faraday.post(webhook_url.url, body,
                  'Content-Type' => 'application/json',
                  'User-Agent' => USER_AGENT,
-                 'X-Webhook-Signature' => "sha256=#{signature}",
-                 'X-Webhook-Timestamp' => timestamp,
-                 'X-Webhook-Request-Id' => SecureRandom.uuid,
+                 **SendWebhookRequest.signing_headers(body, webhook_url),
                  **webhook_url.secret.to_h)
app/views/esign_settings/show.html.erb (1)

137-144: Redundant authorization check — Line 144 is always true inside the Line 137 guard.

The outer if can?(:manage, account_config) on Line 137 already ensures authorization. The identical check on Line 144 is redundant since account_config hasn't changed between the two checks.

♻️ Simplification
-        <% if can?(:manage, account_config) %>
+        <%# Authorization already checked on line 137 %>
           <%= form_for account_config, url: account_configs_path, method: :post do |f| %>

Or simply remove the inner if/end wrapper (lines 144 and 156) entirely.

app/views/submissions/_logo.html.erb (1)

1-6: Consider moving the DB query out of the view partial.

The find_by query on Line 1 runs a DB hit each time this partial is rendered. If the account configs are already eager-loaded elsewhere or this partial is rendered in a loop (e.g., listing multiple submissions), this becomes an N+1. Consider passing logo_url as a local variable from the controller or parent view.

app/views/submit_form/_docuseal_logo.html.erb (1)

1-9: Duplicated logo-fetching logic across partials.

The logo URL retrieval pattern (query AccountConfig::COMPANY_LOGO_URL_KEY, conditional render) is duplicated between this file and app/views/submissions/_logo.html.erb. Consider extracting a shared helper (e.g., company_logo_url_for(account)) or a single partial to keep this DRY.

app/controllers/application_controller.rb (1)

30-34: Consider preserving structured error context when logging.

Rails.logger.error(e) only logs e.to_s (the message). You lose the backtrace and any Rollbar-style metadata. For rate-limit errors surfaced to users this is likely fine, but worth being intentional about.

lib/submitters.rb (1)

170-190: Asymmetric guard-clause style between email and SMS paths.

The email path uses an if block (lines 172-178) while the SMS path uses next statements (lines 180-182). The next calls will skip any future code appended after the SMS block for the current iteration. This is fine today but fragile if more per-submitter logic is added later. Consider wrapping the SMS path in an if block to match the email pattern.

Suggested refactor
-      next if submitter.phone.blank?
-      next if submitter.declined_at?
-      next if submitter.preferences['send_sms'] == false
-
-      if delay_seconds
-        SendSubmitterInvitationSmsJob.perform_in((delay_seconds + index).seconds, 'submitter_id' => submitter.id)
-      else
-        SendSubmitterInvitationSmsJob.perform_async('submitter_id' => submitter.id)
-      end
+      if submitter.phone.present? && !submitter.declined_at? && submitter.preferences['send_sms'] != false
+        if delay_seconds
+          SendSubmitterInvitationSmsJob.perform_in((delay_seconds + index).seconds, 'submitter_id' => submitter.id)
+        else
+          SendSubmitterInvitationSmsJob.perform_async('submitter_id' => submitter.id)
+        end
+      end
app/views/webhook_secret/show.erb (1)

36-36: Struct construction from potentially nil secret is safe but brittle.

@webhook_url.secret.to_a.first yields nil when secret is empty, and *nil splats to nothing, giving a Struct with nil fields. This works but is non-obvious. A brief comment or explicit fallback (|| ['', '']) would improve clarity.

app/controllers/webhook_secret_controller.rb (1)

6-8: Side-effectful show action performs a write on GET request.

ensure_signing_key! calls update! on the model when no key exists. While this is a pragmatic lazy-initialization pattern, a GET action with a database write can be surprising and isn't idempotent from a caching/proxy perspective. Consider moving the initialization to the update action or a dedicated create-style endpoint if feasible.

app/controllers/submitters_send_sms_controller.rb (1)

7-12: Minor TOCTOU window on the rate-limit check.

The SubmissionEvent.exists? check and the actual job enqueue are not atomic. Concurrent requests could both pass the check and enqueue duplicate SMS jobs. The practical risk is low (duplicate SMS, not data corruption), but worth noting if this endpoint is exposed to automated triggers.

Comment on lines 27 to 31
def after_sign_in_path_for(...)
if params[:redir].present?
return console_redirect_index_path(redir: params[:redir]) if params[:redir].starts_with?(Docuseal::CONSOLE_URL)

return params[:redir]
end
return params[:redir] if params[:redir].present?

super
end
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Open redirect vulnerability — params[:redir] is used without validation.

An attacker can craft a login link like ?redir=https://evil.com and redirect users to a malicious site after authentication. This is a classic phishing vector (CWE-601).

Validate that the redirect target is a relative path or belongs to a trusted host before using it.

🛡️ Proposed fix
  def after_sign_in_path_for(...)
-   return params[:redir] if params[:redir].present?
+   if params[:redir].present?
+     uri = URI.parse(params[:redir])
+     return params[:redir] if uri.relative? || uri.host == request.host
+   end

    super
  end
🤖 Prompt for AI Agents
In `@app/controllers/sessions_controller.rb` around lines 27 - 31, The
after_sign_in_path_for method currently returns params[:redir] unvalidated which
allows open redirect; fix by validating params[:redir] before returning: only
accept a safe relative path (e.g. begins with "/" but not "//" or contains a
scheme/host) or parse it with URI.parse and ensure its host is blank or matches
request.host/your trusted host list; if validation fails, fall back to super.
Update the logic in after_sign_in_path_for to perform this whitelist check on
params[:redir] (referencing params[:redir] and after_sign_in_path_for) and
return super when the value is unsafe.

Comment on lines +16 to +19
rescue StandardError => e
flash[:alert] = e.message

render :index, status: :unprocessable_content
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Rescuing StandardError is too broad and may leak internal error details.

This catches all exceptions including programming errors (e.g., NoMethodError, NameError) and exposes e.message directly to the user via flash. Narrow the rescue to the specific exception types you expect (e.g., Twilio API errors or validation errors), or use a generic user-facing message instead of e.message.

Suggested fix
-  rescue StandardError => e
-    flash[:alert] = e.message
+  rescue Twilio::REST::TwilioError, ActiveRecord::RecordInvalid => e
+    Rails.logger.error(e)
+    flash[:alert] = I18n.t('unable_to_save_sms_settings')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
rescue StandardError => e
flash[:alert] = e.message
render :index, status: :unprocessable_content
rescue Twilio::REST::TwilioError, ActiveRecord::RecordInvalid => e
Rails.logger.error(e)
flash[:alert] = I18n.t('unable_to_save_sms_settings')
render :index, status: :unprocessable_content
🤖 Prompt for AI Agents
In `@app/controllers/sms_settings_controller.rb` around lines 16 - 19, The rescue
block in SmsSettingsController currently rescues StandardError (rescue
StandardError => e) and exposes e.message to the user; change this to rescue
only the expected exceptions (for example Twilio::REST::RestError and
ActiveRecord::RecordInvalid) or replace the flashed e.message with a generic
user-facing message (e.g., "An error occurred while updating SMS settings")
while logging the real error internally (use Rails.logger.error or similar)
before rendering :index with status :unprocessable_content; update the rescue
clause to list specific exception classes or add a fallback rescue that logs the
exception and sets a generic flash[:alert] instead of e.message.

Comment on lines +7 to +17
if SubmissionEvent.exists?(submitter: @submitter,
event_type: 'send_sms',
created_at: 10.hours.ago..Time.current)
return redirect_back(fallback_location: submission_path(@submitter.submission),
alert: I18n.t('sms_has_been_sent_already'))
end

SendSubmitterInvitationSmsJob.perform_async('submitter_id' => @submitter.id)

@submitter.sent_at ||= Time.current
@submitter.save!
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Job is enqueued before the model is persisted — if save! raises, the SMS is still sent.

If @submitter.save! on line 17 fails (e.g., validation error), the job enqueued on line 14 will still execute and send the SMS. Consider reordering so the model is saved first, or wrapping both in a transaction with the job enqueued after commit.

Proposed fix: reorder to save before enqueue
-    SendSubmitterInvitationSmsJob.perform_async('submitter_id' => `@submitter.id`)
-
     `@submitter.sent_at` ||= Time.current
     `@submitter.save`!
+
+    SendSubmitterInvitationSmsJob.perform_async('submitter_id' => `@submitter.id`)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if SubmissionEvent.exists?(submitter: @submitter,
event_type: 'send_sms',
created_at: 10.hours.ago..Time.current)
return redirect_back(fallback_location: submission_path(@submitter.submission),
alert: I18n.t('sms_has_been_sent_already'))
end
SendSubmitterInvitationSmsJob.perform_async('submitter_id' => @submitter.id)
@submitter.sent_at ||= Time.current
@submitter.save!
if SubmissionEvent.exists?(submitter: `@submitter`,
event_type: 'send_sms',
created_at: 10.hours.ago..Time.current)
return redirect_back(fallback_location: submission_path(`@submitter.submission`),
alert: I18n.t('sms_has_been_sent_already'))
end
`@submitter.sent_at` ||= Time.current
`@submitter.save`!
SendSubmitterInvitationSmsJob.perform_async('submitter_id' => `@submitter.id`)
🤖 Prompt for AI Agents
In `@app/controllers/submitters_send_sms_controller.rb` around lines 7 - 17, The
job is enqueued before persisting `@submitter` so if `@submitter.save`! fails the
SMS still sends; move the persistence step before enqueuing (set
`@submitter.sent_at` ||= Time.current and call `@submitter.save`! first), then
enqueue with SendSubmitterInvitationSmsJob.perform_async('submitter_id' =>
`@submitter.id`) only after a successful save, or alternatively use an
after_commit/transaction hook to enqueue the job so the job runs only if the
record was committed.

Comment on lines 197 to 203
<a
v-if="!isConnected"
class="block link text-center mt-1"
href="https://www.docuseal.com/blog/accept-payments-and-request-signatures-with-ease"
href="#"
target="_blank"
data-turbo="false"
>{{ t('learn_more') }}</a>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Dead "Learn More" link with stale target="_blank" will open a confusing blank tab.

href="#" combined with target="_blank" opens a new browser tab pointing at the current page. Either hide/remove the link entirely until a real destination is available, or at minimum drop target="_blank" and data-turbo="false".

Suggested fix

Option A – remove the link:

-        <a
-          v-if="!isConnected"
-          class="block link text-center mt-1"
-          href="#"
-          target="_blank"
-          data-turbo="false"
-        >{{ t('learn_more') }}</a>

Option B – keep the link but strip stale attributes:

         <a
           v-if="!isConnected"
           class="block link text-center mt-1"
           href="#"
-          target="_blank"
-          data-turbo="false"
         >{{ t('learn_more') }}</a>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a
v-if="!isConnected"
class="block link text-center mt-1"
href="https://www.docuseal.com/blog/accept-payments-and-request-signatures-with-ease"
href="#"
target="_blank"
data-turbo="false"
>{{ t('learn_more') }}</a>
Suggested change
<a
v-if="!isConnected"
class="block link text-center mt-1"
href="https://www.docuseal.com/blog/accept-payments-and-request-signatures-with-ease"
href="#"
target="_blank"
data-turbo="false"
>{{ t('learn_more') }}</a>
<a
v-if="!isConnected"
class="block link text-center mt-1"
href="#"
>{{ t('learn_more') }}</a>
🤖 Prompt for AI Agents
In `@app/javascript/template_builder/payment_settings.vue` around lines 197 - 203,
The anchor rendered in payment_settings.vue when !isConnected uses href="#" with
target="_blank" and data-turbo="false", which opens a confusing blank tab;
either remove the dead link or make it inert: change the v-if block that renders
the anchor (the element using v-if="!isConnected") to not render an anchor at
all (replace with a plain span/button or remove the block) or, if you must keep
it visible, remove target="_blank" and data-turbo="false" and set href to a real
URL or to "javascript:void(0)"/null so it doesn't open a new tab -- update the
template where the anchor is declared accordingly.

Comment on lines +356 to +357
sms_has_been_sent: SMS has been sent.
sms_has_been_sent_already: SMS has been sent already.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove duplicate sms_has_been_sent key.

There is already an sms_has_been_sent entry later in the same locale (Line 594). Duplicate YAML keys can override or fail lint; please consolidate to a single key.

🧹 Suggested fix (keep the existing definition later)
-  sms_has_been_sent: SMS has been sent.
   sms_has_been_sent_already: SMS has been sent already.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sms_has_been_sent: SMS has been sent.
sms_has_been_sent_already: SMS has been sent already.
sms_has_been_sent_already: SMS has been sent already.
🤖 Prompt for AI Agents
In `@config/locales/i18n.yml` around lines 356 - 357, Remove the duplicate yaml
key by deleting the earlier `sms_has_been_sent` entry (the one paired with
`sms_has_been_sent_already`) and keep the existing `sms_has_been_sent`
definition that appears later; ensure only a single `sms_has_been_sent` key
remains and verify `sms_has_been_sent_already` is still present and correctly
placed to avoid YAML key collisions.

Comment on lines +649 to +650
re_send_sms: Re-send SMS
send_sms: Send SMS
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

YAML key duplication (re_send_sms, send_sms).

These keys already exist earlier in the same locale (Line 417-418), and YAMLlint flags duplicates here. Keep a single definition (or rename for different contexts).

🧹 Suggested fix (remove duplicates)
-  re_send_sms: Re-send SMS
-  send_sms: Send SMS
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
re_send_sms: Re-send SMS
send_sms: Send SMS
🧰 Tools
🪛 YAMLlint (1.38.0)

[error] 649-649: duplication of key "re_send_sms" in mapping

(key-duplicates)


[error] 650-650: duplication of key "send_sms" in mapping

(key-duplicates)

🤖 Prompt for AI Agents
In `@config/locales/i18n.yml` around lines 649 - 650, The YAML file contains
duplicate locale keys re_send_sms and send_sms (previously defined earlier),
which causes YAML linting errors; remove the duplicate entries (or rename them
if they are meant for a different context) and ensure any code/view references
use the single canonical key (re_send_sms, send_sms) or the new name you choose
so translations remain consistent; update any tests/templates that referenced
the removed duplicates to point at the retained key.

Comment on lines +13 to +20
conn = Faraday.new do |f|
f.request :url_encoded
f.adapter Faraday.default_adapter
end

conn.basic_auth(account_sid, auth_token)

response = conn.post(url, { 'To' => to, 'From' => from_number, 'Body' => body })
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

No timeouts configured on the Faraday connection.

Unlike SendWebhookRequest which sets read_timeout: 15 and open_timeout: 8, this Twilio call has no timeouts. A slow or unresponsive Twilio endpoint could block the Sidekiq worker indefinitely.

🛡️ Proposed fix
     conn = Faraday.new do |f|
       f.request :url_encoded
       f.adapter Faraday.default_adapter
+      f.options.open_timeout = 10
+      f.options.read_timeout = 15
     end
🤖 Prompt for AI Agents
In `@lib/send_sms.rb` around lines 13 - 20, The Faraday connection in send_sms.rb
(the conn created by Faraday.new and used to post the Twilio request) lacks
timeouts; update the Faraday client construction to set reasonable open and read
timeouts (e.g. open_timeout: 8, read_timeout: 15) to mirror SendWebhookRequest,
so the Sidekiq worker won’t block indefinitely when Twilio is slow or
unresponsive; modify the Faraday.new options passed to conn (or its request
options) to include these timeout values before calling conn.basic_auth and
conn.post.

f.adapter Faraday.default_adapter
end

conn.basic_auth(account_sid, auth_token)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cd /tmp && find . -maxdepth 3 -name "Gemfile*" 2>/dev/null | head -5

Repository: kencove/docuseal

Length of output: 42


🏁 Script executed:

cd /tmp && git ls-files | grep -E "Gemfile|gemspec" | head -10

Repository: kencove/docuseal

Length of output: 128


🏁 Script executed:

cat Gemfile 2>/dev/null || cat Gemfile.lock 2>/dev/null | head -100

Repository: kencove/docuseal

Length of output: 1928


🏁 Script executed:

cat lib/send_sms.rb

Repository: kencove/docuseal

Length of output: 831


🌐 Web query:

Faraday basic_auth method removed Faraday 2.0 API changes

💡 Result:

In Faraday 2.0, the Faraday::Connection auth helper methods (notably #basic_auth and #token_auth) were dropped as part of the 2.0 API cleanup/refactor. The project consolidated the old auth middlewares into a single Authorization request middleware. [1], [2]

What to change

Old (Faraday 1.x helper; removed in 2.x):

conn = Faraday.new(url: base_url)
conn.basic_auth(username, password)   # removed in Faraday 2.x

Faraday 2.x (use Authorization middleware):

conn = Faraday.new(url: base_url) do |f|
  f.request :authorization, :basic, username, password
end

This uses Faraday::Request::Authorization, which builds the Authorization header; for :basic it expects username, password, otherwise it generally expects a single value (and can take a Proc/lambda). [3]

Why you saw “use #request(:basic_auth, ...) instead”

In late Faraday 1.x, Connection#basic_auth emitted a deprecation warning suggesting middleware usage; however Faraday 2.x ultimately standardized on :authorization, not :basic_auth. The Faraday maintainers explicitly note that Faraday 2.x will only allow the :authorization middleware for these auth cases. [1], [2]

Sources:
[1] Faraday 2.0 discussion (deprecations + “Faraday 2.x will only allow :authorization”) — GitHub Discussion #1358. (github.com)
[2] PR removing Connection auth helpers + merging auth middleware — GitHub PR #1308. (github.com)
[3] Faraday::Request::Authorization API (params for :basic, Proc support, etc.) — RubyDoc for faraday 2.x. (rubydoc.info)

Citations:


Faraday::Connection#basic_auth was removed in Faraday 2.x.

If this project installs Faraday 2.0 or later, line 18 will raise NoMethodError at runtime. Faraday 2.x consolidates authentication into the :authorization middleware. Move basic auth to the middleware block instead:

Recommended fix
     conn = Faraday.new do |f|
       f.request :url_encoded
+      f.request :authorization, :basic, account_sid, auth_token
       f.adapter Faraday.default_adapter
     end

-    conn.basic_auth(account_sid, auth_token)
-
     response = conn.post(url, { 'To' => to, 'From' => from_number, 'Body' => body })
🤖 Prompt for AI Agents
In `@lib/send_sms.rb` at line 18, The call to conn.basic_auth is invalid for
Faraday 2.x; remove the conn.basic_auth(account_sid, auth_token) call and
instead configure basic auth in the Faraday connection setup by adding the
authorization middleware inside the Faraday.new block (e.g. use conn.request
:authorization, :basic, account_sid, auth_token when building the Faraday
connection object named conn in lib/send_sms.rb) so authentication is applied
via middleware rather than calling basic_auth.

page.flatten_annotations
rescue StandardError => e
Rollbar.error(e) if defined?(Rollbar)
Rails.logger.error(e)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Rails.logger.error(e) loses the backtrace — log the full exception.

When you pass an exception object to Rails.logger.error, Ruby calls .to_s on it, which yields only the message — the backtrace is silently dropped. This applies to all the Rollbar→Rails.logger replacements in this file (lines 280, 734, 749, 834, 856). Since these are error-recovery paths in PDF generation, losing stack traces will make production debugging significantly harder.

Proposed fix (apply the same pattern at lines 734, 749, 834, 856)
-              Rails.logger.error(e)
+              Rails.logger.error("#{e.class}: #{e.message}\n#{e.backtrace&.join("\n")}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Rails.logger.error(e)
Rails.logger.error("#{e.class}: #{e.message}\n#{e.backtrace&.join("\n")}")
🤖 Prompt for AI Agents
In `@lib/submissions/generate_result_attachments.rb` at line 280, Replace the bare
Rails.logger.error(e) calls with logging that includes the full exception class,
message and backtrace so you don't lose stack traces; for example, change the
Rails.logger.error(e) occurrences in generate_result_attachments.rb (the error
handlers around the PDF generation paths and the Rollbar→Rails.logger
replacements at the locations noted) to log something like "#{e.class}:
#{e.message}\n#{e.backtrace.join("\n")}" (or use e.full_message if available)
and apply the same replacement for all other instances referenced (the other
Rails.logger.error(e) occurrences in this file).

Add QEMU setup and linux/arm64 platform to the GAR build workflow
so the image runs natively on Apple Silicon for local dev.
Bump timeout to 60m for cross-platform QEMU builds.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
.github/workflows/docker-gar.yml (2)

64-72: docker/build-push-action@v6 adds provenance=mode=max by default for public repositories, exposing build argument values.

If the GitHub repository is public, provenance attestations with mode=max are automatically added to the image. If you're using docker/build-push-action to build images for code in a public GitHub repository, the provenance attestations attached to your image by default contains the values of build arguments. If the Dockerfile passes any secrets or internal identifiers via ARG, they will be included in the publicly readable provenance manifest.

Additionally, for multi-platform builds, the provenance attestations create extra manifest entries that appear as unknown/unknown architecture entries alongside linux/arm64 and linux/amd64 in the GAR UI.

Consider explicitly configuring provenance to avoid unintentional data exposure:

🛠️ Proposed fix
       with:
         context: .
         push: true
         platforms: linux/amd64,linux/arm64
         tags: ${{ steps.meta.outputs.tags }}
+        provenance: mode=min
         cache-from: type=gha
         cache-to: type=gha,mode=max

mode=min retains SLSA compliance without including build arg values or rich layer metadata.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/docker-gar.yml around lines 64 - 72, The GitHub Actions
step using docker/build-push-action@v6 ("Build and push Docker image") currently
relies on the default provenance behavior which can leak ARG values for public
repos; update that step's with: block to explicitly set provenance to a safer
value (e.g., provenance: mode=min) to avoid including build-arg and rich layer
metadata in public attestations (especially important for multi-platform builds
that create extra unknown/unknown entries).

20-23: All third-party actions are pinned to mutable version tags, not immutable commit SHAs.

If an attacker gains access to a GitHub account that publishes actions, they can commit malicious code and then update existing Git tags to point to a new malicious commit SHA. This exact scenario happened in March 2025 when tj-actions/changed-files was compromised — more than 350 Git tags were retargeted, affecting over 23,000 repositories.

"Pinning an action to a full-length commit SHA is currently the only way to use an action as an immutable release."

♻️ Proposed fix (add SHA pins; populate the SHAs for the exact versions in use)
-      uses: actions/checkout@v4
+      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4
-      uses: google-github-actions/auth@v2
+      uses: google-github-actions/auth@<SHA>  # v2
-      uses: docker/login-action@v3
+      uses: docker/login-action@<SHA>  # v3
-      uses: docker/metadata-action@v5
+      uses: docker/metadata-action@<SHA>  # v5
-      uses: docker/setup-qemu-action@v3
+      uses: docker/setup-qemu-action@<SHA>  # v3
-      uses: docker/setup-buildx-action@v3
+      uses: docker/setup-buildx-action@<SHA>  # v3
-      uses: docker/build-push-action@v6
+      uses: docker/build-push-action@<SHA>  # v6

Populate each <SHA> with the full 40-character commit SHA for the pinned version. Tools like Renovate (helpers:pinGitHubActionDigestsToSemver) or caarlos0/pinata automate this and keep the human-readable tag in a trailing comment for Dependabot/Renovate auto-updates.

Also applies to: 35-35, 42-42, 50-50, 59-59, 62-62, 65-65

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/docker-gar.yml around lines 20 - 23, The workflow uses
third-party actions pinned to mutable tags (e.g., uses: actions/checkout@v4);
replace each mutable tag reference with the corresponding full 40-character
commit SHA (for every "uses:" entry such as actions/checkout@v4 and the other
third-party actions referenced on the workflow lines called out) so the action
references are immutable — keep the human-readable tag as a trailing comment if
desired and populate each <SHA> with the exact commit SHA for the pinned
version.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/docker-gar.yml:
- Around line 53-56: The `type=raw,value=latest,enable=${{ github.ref ==
'refs/heads/kencove' }}` condition only enables the latest tag for branch
pushes, so pushing a semver tag (e.g. refs/tags/v1.2.3) won't update :latest;
change the enable expression to also allow tag pushes (for example: enable when
github.ref == 'refs/heads/kencove' || startsWith(github.ref, 'refs/tags/'), or
use github.ref_type == 'tag') so the `latest` raw tag is published on
release/tag pushes as well as the kencove branch.
- Around line 5-6: The tags glob "*.*.*" is too broad and matches non-semver
tags; update the tags entry so it only matches semantic versions (for example
replace "*.*.*" with a stricter pattern like "v?\\d+\\.\\d+\\.\\d*(?:[-+].*)?"
or explicit "v?\\d+\\.\\d+\\.\\d" depending on your allowed variants), or remove
the manual filter and use the docker/metadata-action semver filter instead so
the tags: field only triggers for valid semver releases.

---

Duplicate comments:
In @.github/workflows/docker-gar.yml:
- Around line 25-31: The Create .version file step is vulnerable because it
writes unescaped github.ref_name/github.sha into the shell causing possible
command substitution; update the step to avoid shell interpretation by quoting
the values and using a safe printer (e.g., use printf '%s' with the variable
expansions) instead of unquoted echo, and apply this change for both branches
(when github.ref_type == "tag" and the else branch) so github.ref_name and
github.sha cannot execute as shell commands.

---

Nitpick comments:
In @.github/workflows/docker-gar.yml:
- Around line 64-72: The GitHub Actions step using docker/build-push-action@v6
("Build and push Docker image") currently relies on the default provenance
behavior which can leak ARG values for public repos; update that step's with:
block to explicitly set provenance to a safer value (e.g., provenance: mode=min)
to avoid including build-arg and rich layer metadata in public attestations
(especially important for multi-platform builds that create extra
unknown/unknown entries).
- Around line 20-23: The workflow uses third-party actions pinned to mutable
tags (e.g., uses: actions/checkout@v4); replace each mutable tag reference with
the corresponding full 40-character commit SHA (for every "uses:" entry such as
actions/checkout@v4 and the other third-party actions referenced on the workflow
lines called out) so the action references are immutable — keep the
human-readable tag as a trailing comment if desired and populate each <SHA> with
the exact commit SHA for the pinned version.

Comment on lines +5 to +6
tags:
- "*.*.*"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Overly broad tag glob may fire on non-semver tags.

The pattern *.*.* matches any tag with two dots (e.g., release.candidate.1, hotfix.pr.42). Restrict to semver if that is the intent:

🛠️ Tighter semver pattern
     tags:
-      - "*.*.*"
+      - "v[0-9]+.[0-9]+.[0-9]+"

Or use the semver filter from docker/metadata-action and drop this restriction entirely.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tags:
- "*.*.*"
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/docker-gar.yml around lines 5 - 6, The tags glob "*.*.*"
is too broad and matches non-semver tags; update the tags entry so it only
matches semantic versions (for example replace "*.*.*" with a stricter pattern
like "v?\\d+\\.\\d+\\.\\d*(?:[-+].*)?" or explicit "v?\\d+\\.\\d+\\.\\d"
depending on your allowed variants), or remove the manual filter and use the
docker/metadata-action semver filter instead so the tags: field only triggers
for valid semver releases.

Comment on lines 53 to 56
tags: |
type=semver,pattern={{version}}
type=sha,prefix=,format=short
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/kencove' }}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

latest tag is never updated when a semver release tag is pushed.

The condition enable=${{ github.ref == 'refs/heads/kencove' }} means latest is only published on branch pushes, not on tag pushes. When a v1.2.3 tag is pushed, the workflow runs but the refs/heads/kencove condition is false, so latest is never refreshed to point at the new versioned image. This is likely unintentional — consumers pulling :latest will get the branch-tip image, not the most recent tagged release.

If the intent is for latest to track the newest tagged release, update the condition:

🛠️ Proposed fix
         tags: |
           type=semver,pattern={{version}}
           type=sha,prefix=,format=short
-          type=raw,value=latest,enable=${{ github.ref == 'refs/heads/kencove' }}
+          type=raw,value=latest,enable=${{ github.ref_type == 'tag' }}

If branch-tip tracking is the intent (e.g., latest == current kencove state), document this clearly to avoid consumer confusion.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tags: |
type=semver,pattern={{version}}
type=sha,prefix=,format=short
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/kencove' }}
tags: |
type=semver,pattern={{version}}
type=sha,prefix=,format=short
type=raw,value=latest,enable=${{ github.ref_type == 'tag' }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/docker-gar.yml around lines 53 - 56, The
`type=raw,value=latest,enable=${{ github.ref == 'refs/heads/kencove' }}`
condition only enables the latest tag for branch pushes, so pushing a semver tag
(e.g. refs/tags/v1.2.3) won't update :latest; change the enable expression to
also allow tag pushes (for example: enable when github.ref ==
'refs/heads/kencove' || startsWith(github.ref, 'refs/tags/'), or use
github.ref_type == 'tag') so the `latest` raw tag is published on release/tag
pushes as well as the kencove branch.

dnplkndll and others added 2 commits February 19, 2026 09:51
- Add configurable auto-provisioning for Google OAuth users
  (GOOGLE_AUTO_CREATE, GOOGLE_ALLOWED_DOMAIN, GOOGLE_AUTO_CREATE_ROLE)
- Replace hd: 'kencove.com' with env-driven GOOGLE_ALLOWED_DOMAIN
- Users.from_omniauth now creates accounts on first login when enabled
- Switch Docker image build from GitHub Actions QEMU to Cloud Build
  E2_HIGHCPU_32 (18 min vs 60+ min timeout on GH Actions)
- GitHub Actions workflow now triggers Cloud Build instead of
  building directly

Co-authored-by: Cursor <cursoragent@cursor.com>
Registry cache avoids rebuilding unchanged layers across builds.
Increase timeout from 1h to 2h for uncached cold builds.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
config/initializers/devise.rb (1)

7-7: Rails.logger.warn log message lacks non-PII diagnostic context.

'Invalid password' is a static string with no request metadata (e.g., remote IP, request.request_id). This makes it hard to correlate entries during a brute-force investigation. Adding request.remote_ip or Rack's X-Request-Id would improve usefulness without introducing PII.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/initializers/devise.rb` at line 7, The log call that currently does
Rails.logger.warn('Invalid password') when warden_message == :invalid is missing
non-PII diagnostic context; update the warning to include request-scoped non-PII
identifiers (for example request.remote_ip and/or request.request_id or the Rack
X-Request-Id header) along with a short message so entries can be correlated
during investigations—modify the conditional around warden_message == :invalid
and ensure the Rails.logger.warn invocation includes those request values (but
not user-identifying data).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/docker-gar.yml:
- Around line 42-48: The gcloud invocation uses --async (in gcloud builds
submit) which hides build failures and also passes an unquoted --substitutions
value using the GitHub Actions expression steps.tag.outputs.img_tag that can
break gcloud parsing; remove --async (or replace with logic that waits/polls for
the build result) so the workflow blocks and fails on build errors, and wrap the
substitutions value in quotes (i.e. quote the --substitutions argument) so the
expanded ${ { steps.tag.outputs.img_tag } } is passed safely even if it contains
commas/spaces/equals.

In `@cloudbuild.yaml`:
- Around line 31-32: Remove the unconditional
'--tag=us-central1-docker.pkg.dev/$PROJECT_ID/docuseal/docuseal:latest' argument
in cloudbuild.yaml and make pushing the :latest tag conditional: add a new
substitution (e.g., _PUSH_LATEST) with a default false and change the build step
that currently uses '${_IMG_TAG}' to only include the :latest tag when
_PUSH_LATEST=true; then update the caller (.github/workflows/docker-gar.yml) to
pass _PUSH_LATEST=true only for tagged/release builds (leave it false for branch
pushes) so consumers pulling :latest only get release images.

In `@config/initializers/devise.rb`:
- Around line 341-344: The omniauth configuration calls
ENV.fetch('GOOGLE_CLIENT_SECRET') which raises at boot if the secret is missing;
update the guard around config.omniauth (the block that sets config.omniauth
:google_oauth2, ENV.fetch('GOOGLE_CLIENT_ID'),
ENV.fetch('GOOGLE_CLIENT_SECRET'), oauth_opts) so it only runs when both
GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are present (or alternatively use
ENV['GOOGLE_CLIENT_SECRET'] with a nil-safe fallback and delay failure until
sign-in); ensure you reference the GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET
environment variables and the config.omniauth :google_oauth2 entry when applying
the change.

---

Duplicate comments:
In @.github/workflows/docker-gar.yml:
- Around line 5-6: The workflow's tag trigger uses an overly broad pattern
("*.*.*") under the tags key which matches unintended image tags; update the
tags pattern to a more specific, intentional matcher (for example use a prefix
like "v*.*.*" or a constrained regex-equivalent) so only release-version tags
trigger this workflow and replace the current "*.*.*" value in the tags list
accordingly.
- Around line 36-40: The workflow directly interpolates github.ref_name into a
shell echo which allows shell injection; instead write the output value to
GITHUB_OUTPUT using the safe delimiter/heredoc form (write a delimiter marker,
then the raw github.ref_name, then end delimiter) rather than inline echo
interpolation, and do the same for the img_tag derived from github.sha; update
the block that currently uses echo "img_tag=${{ github.ref_name }}" and echo
"img_tag=$(echo ${{ github.sha }} | cut -c1-8)" to use the GITHUB_OUTPUT heredoc
pattern so the raw values are written without being interpreted by the shell.

---

Nitpick comments:
In `@config/initializers/devise.rb`:
- Line 7: The log call that currently does Rails.logger.warn('Invalid password')
when warden_message == :invalid is missing non-PII diagnostic context; update
the warning to include request-scoped non-PII identifiers (for example
request.remote_ip and/or request.request_id or the Rack X-Request-Id header)
along with a short message so entries can be correlated during
investigations—modify the conditional around warden_message == :invalid and
ensure the Rails.logger.warn invocation includes those request values (but not
user-identifying data).

Comment on lines +42 to +48
- name: Trigger Cloud Build
run: |
gcloud builds submit \
--config=cloudbuild.yaml \
--project=kencove-prod \
--substitutions=_IMG_TAG=${{ steps.tag.outputs.img_tag }} \
--async
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

--async silences build failures; unquoted substitution value can break parsing.

Two distinct issues in this block:

  1. No build-failure signal (Line 44–48): --async causes gcloud builds submit to return immediately after queuing the job. If the Cloud Build run fails (e.g., Buildx error, registry push failure, OOM on the worker), the GitHub Actions job exits with ✅, the commit gets a green check, and the broken image is silently never produced. Consider removing --async and letting the job block until the build completes, or poll for the result. The timeout-minutes: 5 on the job is consistent with --async (submission itself is fast), but that timeout would need to be increased (or removed) if the job blocks.

  2. Unquoted substitution value (Line 47): --substitutions uses comma-separated key=value pairs — a value containing a comma causes a parse error. The expression ${{ steps.tag.outputs.img_tag }} is interpolated by GitHub Actions before the shell runs, so any special character in the resolved value (comma, space, equals) will corrupt the gcloud command.

🛠️ Proposed fix — block on result and quote the substitution
     - name: Trigger Cloud Build
+      timeout-minutes: 120
       run: |
         gcloud builds submit \
           --config=cloudbuild.yaml \
           --project=kencove-prod \
-          --substitutions=_IMG_TAG=${{ steps.tag.outputs.img_tag }} \
-          --async
+          --substitutions="_IMG_TAG=${{ steps.tag.outputs.img_tag }}"
-
-    timeout-minutes: 5
+    timeout-minutes: 130

If keeping async is a deliberate choice (e.g., to avoid long-running Actions minutes), at minimum add the quote and document that build status must be monitored in Cloud Build console.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/docker-gar.yml around lines 42 - 48, The gcloud invocation
uses --async (in gcloud builds submit) which hides build failures and also
passes an unquoted --substitutions value using the GitHub Actions expression
steps.tag.outputs.img_tag that can break gcloud parsing; remove --async (or
replace with logic that waits/polls for the build result) so the workflow blocks
and fails on build errors, and wrap the substitutions value in quotes (i.e.
quote the --substitutions argument) so the expanded ${ {
steps.tag.outputs.img_tag } } is passed safely even if it contains
commas/spaces/equals.

Comment on lines +31 to +32
- '--tag=us-central1-docker.pkg.dev/$PROJECT_ID/docuseal/docuseal:${_IMG_TAG}'
- '--tag=us-central1-docker.pkg.dev/$PROJECT_ID/docuseal/docuseal:latest'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

:latest is always overwritten on every build, including branch pushes.

Both a semver tag push (e.g., 1.2.3) and a branch push to kencove will overwrite :latest with whatever was just built, because --tag=...docuseal:latest is unconditionally present. If a branch push lands a work-in-progress state, consumers pulling :latest will get the non-release image with no warning. Consider conditionally pushing :latest only for tagged builds by factoring the latest tag into the substitution or by removing the hard-coded tag and relying on ${_IMG_TAG} alone when the caller is not a release tag.

🛠️ Conditional latest via extra substitution

Add a _PUSH_LATEST substitution in cloudbuild.yaml:

 substitutions:
   _IMG_TAG: 'latest'
+  _PUSH_LATEST: 'false'

And use it in the build step (Cloud Build supports bash-style conditionals in args via a shell wrapper, or you can split the step):

-      - '--tag=us-central1-docker.pkg.dev/$PROJECT_ID/docuseal/docuseal:latest'

Then in .github/workflows/docker-gar.yml, pass _PUSH_LATEST=true only when building from a tag:

-          --substitutions=_IMG_TAG=${{ steps.tag.outputs.img_tag }} \
+          --substitutions=_IMG_TAG=${{ steps.tag.outputs.img_tag }},_PUSH_LATEST=${{ github.ref_type == 'tag' }} \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cloudbuild.yaml` around lines 31 - 32, Remove the unconditional
'--tag=us-central1-docker.pkg.dev/$PROJECT_ID/docuseal/docuseal:latest' argument
in cloudbuild.yaml and make pushing the :latest tag conditional: add a new
substitution (e.g., _PUSH_LATEST) with a default false and change the build step
that currently uses '${_IMG_TAG}' to only include the :latest tag when
_PUSH_LATEST=true; then update the caller (.github/workflows/docker-gar.yml) to
pass _PUSH_LATEST=true only for tagged/release builds (leave it false for branch
pushes) so consumers pulling :latest only get release images.

Comment on lines +341 to +344
config.omniauth :google_oauth2,
ENV.fetch('GOOGLE_CLIENT_ID'),
ENV.fetch('GOOGLE_CLIENT_SECRET'),
oauth_opts
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing guard on GOOGLE_CLIENT_SECRET can crash app boot.

Line 343 calls ENV.fetch('GOOGLE_CLIENT_SECRET') which raises KeyError at initialization time if the key is absent from the environment — even though no corresponding presence check exists for it. If an operator sets GOOGLE_CLIENT_ID but omits GOOGLE_CLIENT_SECRET, the Rails app will fail to start.

Fix: extend the outer guard to require both variables, or use a nil-safe fallback and fail at sign-in time rather than boot time.

🛡️ Proposed fix — guard both credentials
-  if ENV['GOOGLE_CLIENT_ID'].present?
+  if ENV['GOOGLE_CLIENT_ID'].present? && ENV['GOOGLE_CLIENT_SECRET'].present?
     oauth_opts = {}
     oauth_opts[:hd] = ENV['GOOGLE_ALLOWED_DOMAIN'] if ENV['GOOGLE_ALLOWED_DOMAIN'].present?

     config.omniauth :google_oauth2,
                     ENV.fetch('GOOGLE_CLIENT_ID'),
                     ENV.fetch('GOOGLE_CLIENT_SECRET'),
                     oauth_opts
   end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/initializers/devise.rb` around lines 341 - 344, The omniauth
configuration calls ENV.fetch('GOOGLE_CLIENT_SECRET') which raises at boot if
the secret is missing; update the guard around config.omniauth (the block that
sets config.omniauth :google_oauth2, ENV.fetch('GOOGLE_CLIENT_ID'),
ENV.fetch('GOOGLE_CLIENT_SECRET'), oauth_opts) so it only runs when both
GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are present (or alternatively use
ENV['GOOGLE_CLIENT_SECRET'] with a nil-safe fallback and delay failure until
sign-in); ensure you reference the GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET
environment variables and the config.omniauth :google_oauth2 entry when applying
the change.

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