Skip to content

Conversation

@akshayutture-augment
Copy link
Contributor

@akshayutture-augment
Copy link
Contributor Author

@claude review this PR

1 similar comment
@akshayutture-augment
Copy link
Contributor Author

@claude review this PR

@claude
Copy link

claude bot commented Nov 20, 2025

Claude finished @akshayutture-augment's task —— View job


Code Review Complete

I've completed a thorough review of this PR that introduces a new feature for managing embeddable host-to-category relationships. Here's my detailed feedback:


Critical Issues 🔴

1. Wrong File Content in embeddable_host_fabricator.rb

File: spec/fabricators/embeddable_host_fabricator.rb:1-27

The new file contains Category fabricators instead of EmbeddableHost fabricators. Based on the diff, this appears to be a copy-paste error from category_fabricator.rb.

Expected content:

Fabricator(:embeddable_host) do
  host "eviltrout.com"
  category
end

Actual content: Contains Category fabricators (Fabricator(:category), Fabricator(:diff_category), etc.)

This will cause test failures as the tests expect an :embeddable_host fabricator but it doesn't exist.

2. SQL Injection Vulnerability in Migration

File: db/migrate/20150818190757_create_embeddable_hosts.rb:25

execute "INSERT INTO embeddable_hosts (host, category_id, created_at, updated_at) VALUES ('#{h}', #{category_id}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"

Direct string interpolation of user data (#{h}) creates a SQL injection risk. Even in a migration, if the site_settings table contains malicious data, this could be exploited.

Fix: Use parameterized queries or properly escape the values:

execute sanitize_sql_array(["INSERT INTO embeddable_hosts (host, category_id, created_at, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)", h, category_id])

Or use ActiveRecord:

EmbeddableHost.create!(host: h, category_id: category_id)

High Priority Issues 🟡

3. Missing Authorization Check in destroy

File: app/controllers/admin/embeddable_hosts_controller.rb:14-18

def destroy
  host = EmbeddableHost.where(id: params[:id]).first
  host.destroy
  render json: success_json
end

No check for nil if the host doesn't exist. This will raise a NoMethodError.

Fix:

def destroy
  host = EmbeddableHost.find_by(id: params[:id])
  return render_json_error("Host not found", status: 404) unless host
  host.destroy
  render json: success_json
end

4. Redundant Code in SiteSetting Model

File: app/models/site_setting.rb:68-82 (removed code)

The removed method allows_embeddable_host? had dead code that would never execute:

!!embeddable_hosts.split("\n").detect {|h| h.sub(/^https?\:\/\//, '') == host }

hosts = embeddable_hosts.split("\n").map {|h| (URI(h).host rescue nil) || h  }  # Unreachable!
!!hosts.detect {|h| h == host}  # Unreachable!

Good that this was removed in favor of the cleaner EmbeddableHost model approach.

5. Regex Vulnerability in Host Validation

File: app/models/embeddable_host.rb:2

validates_format_of :host, :with => /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\Z/i

Issues:

  • TLD limit of 5 characters ([a-z]{2,5}) is outdated - newer TLDs like .museum (6 chars) exist
  • The regex allows paths ((\/.*)?) but the before_validation strips them anyway
  • Doesn't validate against localhost or IP addresses (might be intentional)

Suggestion:

validates_format_of :host, :with => /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}(:[0-9]{1,5})?\Z/i

6. Missing Error Handling in Model

File: app/models/embeddable_host.rb:6

self.host.sub!(/^https?:\/\//, '')

This will raise an error if self.host is nil.

Fix:

self.host = self.host.to_s.sub(/^https?:\/\//, '').sub(/\/.*$/, '')

Medium Priority Issues 🟢

7. Inconsistent Method Naming

File: app/models/embeddable_host.rb:20-22

def self.host_allowed?(host)
  record_for_host(host).present?
end

This mirrors the old SiteSetting.allows_embeddable_host? but with a different naming pattern. Consider naming consistency:

  • EmbeddableHost.allowed?(host) or
  • EmbeddableHost.allows?(host)

8. Migration Query Could Fail

File: db/migrate/20150818190757_create_embeddable_hosts.rb:9-11

category_id = execute("SELECT c.id FROM categories AS c
                    INNER JOIN site_settings AS s ON s.value = c.name
                    WHERE s.name = 'embed_category'")[0]['id'].to_i

If the join returns no results, [0] will be nil and calling ['id'] will raise an error.

Fix:

result = execute("SELECT c.id FROM categories AS c
                  INNER JOIN site_settings AS s ON s.value = c.name
                  WHERE s.name = 'embed_category'").to_a
category_id = result.first&.fetch('id', 0).to_i

9. Overly Verbose Serializer

File: app/serializers/embeddable_host_serializer.rb:1-15

All methods just return the object attribute directly, which is the default behavior.

Can be simplified to:

class EmbeddableHostSerializer < ApplicationSerializer
  attributes :id, :host, :category_id
end

10. Missing Validation

File: app/models/embeddable_host.rb

Missing validation for category_id presence and existence:

validates :category_id, presence: true
validates :category, presence: true

11. Test Coverage Gaps

File: spec/controllers/admin/embeddable_hosts_controller_spec.rb:1-9

Only tests inheritance, not the actual CRUD operations. Should add tests for:

  • Creating a host
  • Updating a host
  • Deleting a host
  • Error cases (validation failures, not found, etc.)

File: spec/controllers/admin/embedding_controller_spec.rb:1-9

Same issue - only tests inheritance.

12. Frontend: Potential Race Condition

File: app/assets/javascripts/admin/components/embeddable-host.js.es6:37-40

host.save(props).then(() => {
  host.set('category', Discourse.Category.findById(this.get('categoryId')));
  this.set('editToggled', false);
}).catch(popupAjaxError);

After saving, it manually sets the category by looking it up. The server should return the full object with the relationship already loaded. This could cause issues if findById hasn't loaded that category yet.


Minor Issues / Suggestions 💡

13. Magic Number

File: app/controllers/admin/embeddable_hosts_controller.rb:25

host.category_id = SiteSetting.uncategorized_category_id if host.category_id.blank?

Consider if this default should be explicit in the model or documented why uncategorized is chosen as the default.

14. Unused Route Parameter

File: config/routes.rb:138-139

put "customize/embedding" => "embedding#update", constraints: AdminConstraint.new

The update action in EmbeddingController doesn't actually update anything - it just returns the same data as show. Either implement the update logic or remove this route.

15. JavaScript Store Enhancement

File: app/assets/javascripts/discourse/models/store.js.es6:196

The regex change /(.*_id(s?)$/ is clever for handling both singular and plural ID hydration. However, the logic is getting complex. Consider adding comments explaining this hydration mechanism.


Positive Aspects ✅

  1. Good migration strategy - Migrates data from old site_settings to new table
  2. Clean separation - Moving logic from SiteSetting to a proper model is the right approach
  3. Proper validation - Host formatting is validated and normalized
  4. Test migration - Updated tests to use the new model instead of site settings
  5. UI implementation - Full CRUD interface with proper Ember patterns
  6. Security improvement - Replaces text-based setting with database records

Summary

This is a solid refactoring that moves embeddable host management from site settings to a proper database model with admin UI. The main concerns are:

  1. Critical: Wrong fabricator content will break tests
  2. Critical: SQL injection in migration
  3. High: Missing nil checks and validation
  4. Medium: Various edge cases and test coverage gaps

Recommendation: Fix the critical issues before merging, particularly the fabricator file and SQL injection vulnerability.


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.

3 participants