Skip to content
Open
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
7 changes: 7 additions & 0 deletions .codespellrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[codespell]
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
skip = .git,.gitignore,.gitattributes,*.svg,vendor,*.lock,.codespellrc,CHANGELOG.md,*/dkim_signing/*,*/postfix-bounce.msg,*/signing.key
check-hidden = true
# ignore-regex =
# checkin - method name in connection_pool.rb (not "checking"/"check in")
ignore-words-list = checkin
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ If applicable, add screenshots to help explain your problem.

## Additional information/context

Add any other context about the problem here. It is particularily useful to include log extracts (after removing private information).
Add any other context about the problem here. It is particularly useful to include log extracts (after removing private information).
23 changes: 23 additions & 0 deletions .github/workflows/codespell.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Codespell configuration is within .codespellrc
---
name: Codespell

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
codespell:
name: Check for spelling errors
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
- name: Codespell
uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2.2
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ BRANCH
.rubocop-https*
.env*

node_modules
yarn.lock

2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "3.3.4"
".": "3.3.6"
}
4 changes: 2 additions & 2 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Layout/EmptyLinesAroundModuleBody:

# Space is required following -> when writing a lambda:
#
# somethign = -> (var) { block }
# something = -> (var) { block }
Layout/SpaceInLambdaLiteral:
EnforcedStyle: require_space

Expand Down Expand Up @@ -115,7 +115,7 @@ Lint/BooleanSymbol:
Style/SymbolProc:
Enabled: false

# Allow a maxmium of 5 arguments and don't include keyword arguments
# Allow a maximum of 5 arguments and don't include keyword arguments
Metrics/ParameterLists:
Max: 5
CountKeywordArgs: false
Expand Down
50 changes: 48 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,52 @@

This file contains all the latest changes and updates to Postal.

## [3.3.6](https://github.com/postalserver/postal/compare/3.3.5...3.3.6) (2026-04-28)


### Bug Fixes

* **messages:** sandbox rendered email HTML as extra XSS defence ([cad2aa6](https://github.com/postalserver/postal/commit/cad2aa6808519a3ff25215f09f4966d9fa3bb372))


### Miscellaneous Chores

* ignore node modules and yarn.lock ([b611d57](https://github.com/postalserver/postal/commit/b611d577af79b8e1e75b6d47fa04d1ba03e34eec))


### Code Refactoring

* **auth:** tighten return_to validation ([84f4e20](https://github.com/postalserver/postal/commit/84f4e20f05db2d11b0144f95960c956f8221e657))
* **helpers:** escape interpolated values in select options ([9243524](https://github.com/postalserver/postal/commit/924352403553dcfcc569876ca76c219493fac9d6))
* **tracking:** remove unused src image proxy ([dca7f90](https://github.com/postalserver/postal/commit/dca7f90b9046247c0d953567be35921167e79d87))

## [3.3.5](https://github.com/postalserver/postal/compare/3.3.4...3.3.5) (2026-02-01)


### Bug Fixes

* **deliveries:** escape delivery details to prevent HTML injection ([11419f9](https://github.com/postalserver/postal/commit/11419f99140e13688a9613cab3ee03f8d3cbae45))
* **health_server:** use rackup handler instead of rack handler ([7c47422](https://github.com/postalserver/postal/commit/7c47422c865e738c4d6af0fed1cca4405288341f))
* oidc scopes are invalid when concatenated ([#3332](https://github.com/postalserver/postal/issues/3332)) ([9c5f96a](https://github.com/postalserver/postal/commit/9c5f96ae90cf06dcd5db776806865752f667bd95))
* typo in process logging ([#3212](https://github.com/postalserver/postal/issues/3212)) ([b7e5232](https://github.com/postalserver/postal/commit/b7e5232e077b3c9b7a999dcb6676fba0ec61458e))
* typo in the credentials page ([fd3c7cc](https://github.com/postalserver/postal/commit/fd3c7ccdf6dc4ee0a76c9523cbd735159e4b8000))
* update url for v2 config ([#3225](https://github.com/postalserver/postal/issues/3225)) ([e00098b](https://github.com/postalserver/postal/commit/e00098b8003cf37f2708f536871b3ade377aed2d))


### Documentation

* **process.rb:** add help about time unit used by metric ([#3339](https://github.com/postalserver/postal/issues/3339)) ([f5325c4](https://github.com/postalserver/postal/commit/f5325c49ff1152ad53eaaec98717ad3412d379ae))


### Miscellaneous Chores

* **deps:** upgrade puma, net-imap and other deps ([c03c44b](https://github.com/postalserver/postal/commit/c03c44b442a29aa9881c1e1aae60bead9776a6b6))
* **dockerfile:** reduce container size ([86de372](https://github.com/postalserver/postal/commit/86de372382bd62bdd5d1372254f8817b0360bd56))
* remove version from docker-compose.yml ([c78000c](https://github.com/postalserver/postal/commit/c78000ca8f2998aa04648f465060768db6467de6))
* upgrade resolv to 0.6.2 ([d00d978](https://github.com/postalserver/postal/commit/d00d978872a96369544303d08f6a9d11cdf56b62))
* upgrade to rails 7.1 and ruby 3.4 ([#3457](https://github.com/postalserver/postal/issues/3457)) ([ab6d443](https://github.com/postalserver/postal/commit/ab6d4430baa33a05f1aa66e776cc2a5bcaa0ede8))
* upgrade uri gem to 1.0.3 ([f193b8e](https://github.com/postalserver/postal/commit/f193b8e77fc096382ab7aaa6a2c29641b4cb12df))

## [3.3.4](https://github.com/postalserver/postal/compare/3.3.3...3.3.4) (2024-06-20)


Expand Down Expand Up @@ -287,7 +333,7 @@ This version of Postal introduces a number of larger changes. Please be sure to
* **rubocop:** Lint/UselessAssignment ([7590a46](https://github.com/postalserver/postal/commit/7590a462341bddd412e660db9546ba1909aea9d7))
* **rubocop:** Naming/FileName ([919a601](https://github.com/postalserver/postal/commit/919a60116c5d81ed787061ff4614da4f1e067d4e))
* **rubocop:** Naming/MemoizedInstanceVariableName ([9563f30](https://github.com/postalserver/postal/commit/9563f30c96fba12073e845319b8d79a542d88109))
* **rubocop:** relax method length and block nexting for now ([b0ac9ef](https://github.com/postalserver/postal/commit/b0ac9ef0b96ab78c2961f45b6e9f20f87a6f1d07))
* **rubocop:** relax method length and block nesting for now ([b0ac9ef](https://github.com/postalserver/postal/commit/b0ac9ef0b96ab78c2961f45b6e9f20f87a6f1d07))
* **rubocop:** remaining offences ([ec63666](https://github.com/postalserver/postal/commit/ec636661d5c4b9e8f48e6f263ffef834acb68b39))
* **rubocop:** Security/YAMLLoad ([389ea77](https://github.com/postalserver/postal/commit/389ea7705047bf8700836137514b2497af3c6c01))
* **rubocop:** Style/AndOr ([b9f3f31](https://github.com/postalserver/postal/commit/b9f3f313f8ec992917bad3a51f0481f89675e935))
Expand Down Expand Up @@ -423,7 +469,7 @@ This version of Postal introduces a number of larger changes. Please be sure to
- Fix to newline conversion process ([9f4ef8](https://github.com/postalserver/postal/commit/9f4ef8f57a839c5529b4f00a36b832740386b4ed))
- Remove custom scrollbars ([b22f1b](https://github.com/postalserver/postal/commit/b22f1bdb2e2d66b096ca993d6a5f4f708274a4a2))
- Truncate 'output' field to avoid overflowing varchar(512) in database ([a188a1](https://github.com/postalserver/postal/commit/a188a161cbdcfd70158b09b53cef622842357c26))
- Fix link replacement in multipart messsages ([7ea00d](https://github.com/postalserver/postal/commit/7ea00dfa3bc3c7650cc2b134beacbff22101a913))
- Fix link replacement in multipart messages ([7ea00d](https://github.com/postalserver/postal/commit/7ea00dfa3bc3c7650cc2b134beacbff22101a913))
- Fix confusing error message when deleting IP pools ([cefc7d](https://github.com/postalserver/postal/commit/cefc7d17b82f610001859a8e323ee1dfde149ba5))
- Connect to correct IP rather than hostname suring SMTP delivery ([159509](https://github.com/postalserver/postal/commit/159509a3ed29ae33cba522b255904992922dcfdf))
- Change retry timings to avoid re-sending messages too early ([c8d27b](https://github.com/postalserver/postal/commit/c8d27b2963af122d6555abdf0742d2d2d6f11ce5))
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ openssl genrsa -out config/postal/signing.key 2048

If you're running the tests (and you probably should be), you'll find an example file for test configuration in `config/examples/test.yml`. This should be placed in `config/postal/postal.test.yml` with the appropriate values.

If you prefer, you can configure Postal using environment variables. These should be placed in `.env` or `.env.test` as apprpriate.
If you prefer, you can configure Postal using environment variables. These should be placed in `.env` or `.env.test` as appropriate.

## Running

Expand Down
7 changes: 5 additions & 2 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,13 @@ def append_info_to_payload(payload)
end

def url_with_return_to(url)
if params[:return_to].blank? || !params[:return_to].starts_with?("/")
return_to = params[:return_to]
if return_to.blank? ||
!return_to.start_with?("/") ||
return_to.start_with?("//", "/\\")
url_for(url)
else
params[:return_to]
return_to
end
end

Expand Down
12 changes: 12 additions & 0 deletions app/controllers/messages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@ def deliveries
end

def html_raw
override_content_security_policy_directives(
default_src: %w('none'),
script_src: %w('none'),
style_src: %w('unsafe-inline'),
img_src: %w(* data:),
font_src: %w(*),
frame_ancestors: %w('self'),
form_action: %w('none'),
base_uri: %w('none')
)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["Referrer-Policy"] = "no-referrer"
render html: @message.html_body_without_tracking_image.html_safe
end

Expand Down
13 changes: 7 additions & 6 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module ApplicationHelper

def format_delivery_details(server, text)
text = h(text)
text.gsub!(/<msg:(\d+)>/) do
id = ::Regexp.last_match(1).to_i
link_to("message ##{id}", organization_server_message_path(server.organization, server, id), class: "u-link")
Expand Down Expand Up @@ -32,7 +33,7 @@ def domain_options_for_select(server, selected_domain = nil, options = {})
s << "<optgroup label='Server Domains'>"
server_domains.each do |domain|
selected = domain == selected_domain ? "selected='selected'" : ""
s << "<option value='#{domain.id}' #{selected}>#{domain.name}</option>"
s << "<option value='#{h(domain.id)}' #{selected}>#{h(domain.name)}</option>"
end
s << "</optgroup>"
end
Expand All @@ -42,7 +43,7 @@ def domain_options_for_select(server, selected_domain = nil, options = {})
s << "<optgroup label='Organization Domains'>"
organization_domains.each do |domain|
selected = domain == selected_domain ? "selected='selected'" : ""
s << "<option value='#{domain.id}' #{selected}>#{domain.name}</option>"
s << "<option value='#{h(domain.id)}' #{selected}>#{h(domain.name)}</option>"
end
s << "</optgroup>"
end
Expand All @@ -59,7 +60,7 @@ def endpoint_options_for_select(server, selected_value = nil, options = {})
http_endpoints.each do |endpoint|
value = "#{endpoint.class}##{endpoint.uuid}"
selected = value == selected_value ? "selected='selected'" : ""
s << "<option value='#{value}' #{selected}>#{endpoint.description}</option>"
s << "<option value='#{h(value)}' #{selected}>#{h(endpoint.description)}</option>"
end
s << "</optgroup>"
end
Expand All @@ -70,7 +71,7 @@ def endpoint_options_for_select(server, selected_value = nil, options = {})
smtp_endpoints.each do |endpoint|
value = "#{endpoint.class}##{endpoint.uuid}"
selected = value == selected_value ? "selected='selected'" : ""
s << "<option value='#{value}' #{selected}>#{endpoint.description}</option>"
s << "<option value='#{h(value)}' #{selected}>#{h(endpoint.description)}</option>"
end
s << "</optgroup>"
end
Expand All @@ -81,7 +82,7 @@ def endpoint_options_for_select(server, selected_value = nil, options = {})
address_endpoints.each do |endpoint|
value = "#{endpoint.class}##{endpoint.uuid}"
selected = value == selected_value ? "selected='selected'" : ""
s << "<option value='#{value}' #{selected}>#{endpoint.address}</option>"
s << "<option value='#{h(value)}' #{selected}>#{h(endpoint.address)}</option>"
end
s << "</optgroup>"
end
Expand All @@ -93,7 +94,7 @@ def endpoint_options_for_select(server, selected_value = nil, options = {})

selected = (selected_value == mode ? "selected='selected'" : "")
text = t("route_modes.#{mode.underscore}")
s << "<option value='#{mode}' #{selected}>#{text}</option>"
s << "<option value='#{h(mode)}' #{selected}>#{h(text)}</option>"
end
s << "</optgroup>"
end
Expand Down
2 changes: 1 addition & 1 deletion app/lib/message_dequeuer/incoming_message_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def inspect_message
def fail_if_spam
return if queued_message.message.spam_score < queued_message.server.spam_failure_threshold

log "message has a spam score higher than the server's maxmimum, hard failing", server_threshold: queued_message.server.spam_failure_threshold
log "message has a spam score higher than the server's maximum, hard failing", server_threshold: queued_message.server.spam_failure_threshold
create_delivery "HardFail",
details: "Message's spam score is higher than the failure threshold for this server. " \
"Threshold is currently #{queued_message.server.spam_failure_threshold}."
Expand Down
2 changes: 1 addition & 1 deletion app/lib/smtp_client/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def start_smtp_session(source_ip_address: nil, allow_ssl: true)
end

# Send a message to the current SMTP session (or create one if there isn't one for this endpoint).
# If sending messsage encouters some connection errors, retry again after re-establishing the SMTP
# If sending message encounters some connection errors, retry again after re-establishing the SMTP
# session.
#
# @param raw_message [String] the raw message to send
Expand Down
2 changes: 1 addition & 1 deletion app/lib/smtp_server/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ def finished
msg.bounce = 1
end
else
# There's no return path route, we just need to insert the mesage
# There's no return path route, we just need to insert the message
# without going through the route.
message = server.message_db.new_message
message.rcpt_to = rcpt_to
Expand Down
2 changes: 1 addition & 1 deletion app/lib/smtp_server/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ def register_prometheus_metrics
labels: [:type, :error]

register_prometheus_counter :postal_smtp_server_tls_connections_total,
docstring: "The number of successfuly TLS connections established"
docstring: "The number of successfully TLS connections established"

Client.register_prometheus_metrics
end
Expand Down
2 changes: 1 addition & 1 deletion app/models/bounce_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def body
For further assistance please contact #{postmaster_address}. Please include the details below to help us identify the issue.
Message Token: #{@message.token}@#{@server.token}
Orginal Message ID: #{@message.message_id}
Original Message ID: #{@message.message_id}
Mail from: #{@message.mail_from}
Rcpt To: #{@message.rcpt_to}
BODY
Expand Down
8 changes: 4 additions & 4 deletions app/senders/smtp_sender.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class SMTPSender < BaseSender

attr_reader :endpoints

# @param domain [String] the domain to send mesages to
# @param domain [String] the domain to send messages to
# @param source_ip_address [IPAddress] the IP address to send messages from
# @param log_id [String] an ID to use when logging requests
def initialize(domain, source_ip_address = nil, servers: nil, log_id: nil, rcpt_to: nil)
Expand All @@ -15,7 +15,7 @@ def initialize(domain, source_ip_address = nil, servers: nil, log_id: nil, rcpt_

# An array of servers to forcefully send the message to
@servers = servers
# Stores all connection errors which we have seen during this send sesssion.
# Stores all connection errors which we have seen during this send session.
@connection_errors = []
# Stores all endpoints that we have attempted to deliver mail to
@endpoints = []
Expand Down Expand Up @@ -57,7 +57,7 @@ def send_message(message)
mail_from = determine_mail_from_for_message(message)
raw_message = message.raw_message

# Append the Resent-Sender header to the mesage to include the
# Append the Resent-Sender header to the message to include the
# MAIL FROM if the installation is configured to use that?
if Postal::Config.postal.use_resent_sender_header?
raw_message = "Resent-Sender: #{mail_from}\r\n" + raw_message
Expand Down Expand Up @@ -168,7 +168,7 @@ def resolve_mx_records_for_domain
hostnames.map { |hostname| SMTPClient::Server.new(hostname) }
end

# Attempt to begin an SMTP sesssion for the given endpoint. If successful, this endpoint
# Attempt to begin an SMTP session for the given endpoint. If successful, this endpoint
# becomes the current endpoints for the SMTP sender.
#
# Returns true if the session was established.
Expand Down
2 changes: 1 addition & 1 deletion app/views/domains/new.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
= f.select :verification_method, Domain::VERIFICATION_METHODS, {}, :class => 'input input--select'
.fieldSet__text
Choose how you'd like to verify your ownership of this domain. If you choose <b>E-Mail</b> we can send you
an email with a code whcih you'll need to enter - you can choose from a set of pre-defined addresses for
an email with a code which you'll need to enter - you can choose from a set of pre-defined addresses for
the domain. Using <b>DNS</b> you'll need to add a TXT record on this domain using your DNS provider.

.fieldSetSubmit
Expand Down
2 changes: 1 addition & 1 deletion app/views/messages/html.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
This means that we no longer store the raw data for this e-mail
or the e-mail didn't include a HTML part.
- else
%iframe{:width => "100%", :height => "100%", :src => html_raw_organization_server_message_path(organization, @server, @message.id)}
%iframe{:width => "100%", :height => "100%", :sandbox => "allow-popups allow-popups-to-escape-sandbox", :referrerpolicy => "no-referrer", :src => html_raw_organization_server_message_path(organization, @server, @message.id)}
2 changes: 1 addition & 1 deletion app/views/messages/spam_checks.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
.noData.noData--clean
%h2.noData__title This message doesn't have any spam checks.
%p.noData__text
This likely means we haven't scanned this message to determine its likelyhood
This likely means we haven't scanned this message to determine its likelihood
of being spam. It may take a few seconds to appear after a new message is
received.

Expand Down
2 changes: 1 addition & 1 deletion doc/config/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ By default, tests will use the `config/postal/postal.test.yml` configuration fil

## Containers

Within a container, Postal will for a config file in `/config/postal.yml` unless overriden by the `POSTAL_CONFIG_FILE_PATH` environment variable.
Within a container, Postal will for a config file in `/config/postal.yml` unless overridden by the `POSTAL_CONFIG_FILE_PATH` environment variable.

## Ports & Bind Addresses

Expand Down
Loading
Loading