Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
v5.2.0 (July 2026)
- UI: add dark mode support to the login and setup wizard pages
- Bugs fixes:
- Echo: stop broadcasting AI provider error responses to the browser
- Echo: restrict Anthropic provider requests to the fixed messages endpoint
- Upgraded gems:
- concurrent-ruby, crass, faraday, msgpack, net-imap, nokogiri, puma

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ def perform(agent_id:, prompt:, interaction_id:, response_id:)

Turbo::StreamsChannel.broadcast_append_to [interaction_id, 'prompts'], target: 'messages', html: '<p>Done.</p>'
rescue => e
msg = '<div class="alert alert-danger m-0">'
msg << ERB::Util.html_escape(e.message)
msg << '</div>'
Turbo::StreamsChannel.broadcast_update_to [interaction_id, 'prompts'], target: response_id, html: msg
Rails.logger.error("[Echo] interaction #{interaction_id} failed: #{e.message}")

html = '<div class="alert alert-danger m-0">Something went wrong.</div>'
Turbo::StreamsChannel.broadcast_update_to [interaction_id, 'prompts'], target: response_id, html: html
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class Provider::Anthropic < Provider
include Provider::HttpStreaming

API_VERSION = '2023-06-01'.freeze
DEFAULT_ADDRESS = 'https://api.anthropic.com/v1/messages'.freeze
DEFAULT_ADDRESS = 'https://api.anthropic.com/v1'.freeze
DEFAULT_MAX_TOKENS = 4096
DEFAULT_MODEL = 'claude-sonnet-4-6'.freeze

Expand All @@ -21,12 +21,12 @@ def build_body(prompt:, model:)
def build_headers
{
'anthropic-version' => API_VERSION,
'x-api-key' => api_key
'x-api-key' => api_key
}
end

def build_uri(_model)
URI(address)
URI("#{address}/messages")
end

# Anthropic sends several SSE event types; only content_block_delta carries text:
Expand Down
2 changes: 1 addition & 1 deletion engines/dradis-echo/spec/factories/providers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
model { 'qwen2.5:14b' }

factory :anthropic_provider, class: 'Dradis::Plugins::Echo::Provider::Anthropic' do
address { 'https://api.anthropic.com/v1/messages' }
address { 'https://api.anthropic.com/v1' }
api_key { 'sk-ant-test' }
model { 'claude-sonnet-4-6' }
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

describe Dradis::Plugins::Echo::InteractionJob do
let(:interaction_id) { 'project-1' }
let(:response_id) { 'response-1' }
let(:prompt) { 'Summarise this issue.' }
let(:agent) { create(:system_agent) }
let(:response_id) { 'response-1' }
let(:prompt) { 'Summarise this issue.' }
let(:agent) { create(:system_agent) }

def perform
described_class.perform_now(
Expand All @@ -26,25 +26,35 @@ def perform
describe 'when agent is not enabled' do
before { agent.update!(enabled: false) }

it 'broadcasts a user-friendly error' do
it 'broadcasts a generic error without leaking details' do
perform
expect(Turbo::StreamsChannel).to have_received(:broadcast_update_to) do |_, **kwargs|
expect(kwargs[:html]).to include('is not enabled')
expect(kwargs[:html]).to include('Something went wrong')
expect(kwargs[:html]).not_to include('is not enabled')
end
end
end

describe 'error message sanitisation' do
it 'HTML-escapes the error message before broadcasting' do
describe 'error message handling' do
it 'does not reflect the underlying error message to the browser' do
allow_any_instance_of(Dradis::Plugins::Echo::Provider::Ollama)
.to receive(:generate).and_raise('<script>alert(1)</script>')
.to receive(:generate).and_raise('<script>alert(1)</script> upstream-secret')

perform
expect(Turbo::StreamsChannel).to have_received(:broadcast_update_to) do |_, **kwargs|
expect(kwargs[:html]).to include('&lt;script&gt;')
expect(kwargs[:html]).not_to include('<script>')
expect(kwargs[:html]).to include('Something went wrong')
expect(kwargs[:html]).not_to include('script')
expect(kwargs[:html]).not_to include('upstream-secret')
end
end

it 'logs the full error message for operators' do
allow_any_instance_of(Dradis::Plugins::Echo::Provider::Ollama)
.to receive(:generate).and_raise('upstream-secret')

expect(Rails.logger).to receive(:error).with(/upstream-secret/)
perform
end
end

describe 'successful streaming' do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
end

describe '#build_uri' do
it 'returns the configured address' do
expect(provider.send(:build_uri, 'claude-sonnet-4-6').to_s).to eq(described_class::DEFAULT_ADDRESS)
it 'appends messages to the address' do
expect(provider.send(:build_uri, 'claude-sonnet-4-6').to_s)
.to eq("#{described_class::DEFAULT_ADDRESS}/messages")
end

it 'uses a custom address when set' do
provider.address = 'https://anthropic.proxy.example.com/v1/messages'
it 'appends messages to a custom address, preventing full-path control' do
provider.address = 'https://anthropic.proxy.example.com/v1'
expect(provider.send(:build_uri, 'claude-sonnet-4-6').to_s)
.to eq('https://anthropic.proxy.example.com/v1/messages')
end
Expand Down Expand Up @@ -46,7 +47,7 @@
describe '#extract_text' do
it 'extracts text from content_block_delta events' do
payload = {
'type' => 'content_block_delta',
'type' => 'content_block_delta',
'delta' => { 'type' => 'text_delta', 'text' => 'Hello' }
}
expect(provider.send(:extract_text, payload)).to eq('Hello')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
describe Dradis::Plugins::Echo::Provider::HttpStreaming do
let(:provider) do
Dradis::Plugins::Echo::Provider::Anthropic.new(
address: 'https://api.anthropic.com/v1/messages',
address: 'https://api.anthropic.com/v1',
api_key: 'sk-ant-test',
model: 'claude-sonnet-4-6',
name: 'Test'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
it 'returns the default for each subclass' do
expect(described_class::Ollama.default_address).to eq('http://localhost:11434')
expect(described_class::OpenAI.default_address).to eq('https://api.openai.com/v1')
expect(described_class::Anthropic.default_address).to eq('https://api.anthropic.com/v1/messages')
expect(described_class::Anthropic.default_address).to eq('https://api.anthropic.com/v1')
expect(described_class::Gemini.default_address).to include('generativelanguage.googleapis.com')
end
end
Expand Down
Loading