fix: sanitize control characters in team names (#14865)
Team names created via the API could contain control characters (for
example a trailing newline). Because the team-delete confirmation dialog
requires you to retype the team name and matches it against the stored
value, a hidden control character meant the typed name never matched —
leaving the team impossible to delete from the UI. This sanitizes team
names on save so they stay clean and deletable.
#### How to reproduce
1. Create a team via `POST /api/v1/accounts/{account_id}/teams` with
`{"name": "test\n"}`.
2. The team is created with the trailing newline stored in `name`.
3. In **Settings → Teams**, click delete and type the team name to
confirm — the match fails, so the team cannot be deleted.
#### What changed
- `app/models/team.rb`: the existing `before_validation` now strips
control characters and surrounding whitespace before downcasing the
name. Names that reduce to blank (e.g. only newlines/tabs) are rejected
loudly by the existing `presence` validation.
- Fixing at the model layer covers the API and every other create/update
path, rather than relying on the frontend confirm-dialog `.trim()`
(which only handles leading/trailing whitespace, not internal control
characters).
Note: this prevents new malformed names. Any team already saved with a
control character can be made deletable again simply by renaming it (an
update re-runs the same sanitization).
| Input | Stored as | Result |
|---|---|---|
| `"test\n"` | `"test"` | valid, deletable |
| `"te\nst"` (internal) | `"test"` | valid |
| `"\t\n "` (only control/ws) | — | rejected: "Name must not be blank" |
| `"Customer Support"` | `"customer support"` | unchanged behavior |
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Muhsin
Claude Opus 4.8
parent
29d0b92f1c
commit
ce8c8e9a11
+1
-1
@@ -30,7 +30,7 @@ class Team < ApplicationRecord
|
||||
uniqueness: { scope: :account_id }
|
||||
|
||||
before_validation do
|
||||
self.name = name.downcase if attribute_present?('name')
|
||||
self.name = name.gsub(/[[:cntrl:]]/, '').strip.downcase if attribute_present?('name')
|
||||
end
|
||||
|
||||
# Adds multiple members to the team
|
||||
|
||||
@@ -7,6 +7,31 @@ RSpec.describe Team do
|
||||
it { is_expected.to have_many(:team_members) }
|
||||
end
|
||||
|
||||
describe 'name normalization' do
|
||||
let(:account) { create(:account) }
|
||||
|
||||
it 'downcases the name' do
|
||||
team = create(:team, account: account, name: 'Customer Support')
|
||||
expect(team.name).to eq('customer support')
|
||||
end
|
||||
|
||||
it 'strips control characters and surrounding whitespace' do
|
||||
team = create(:team, account: account, name: " Sales\n")
|
||||
expect(team.name).to eq('sales')
|
||||
end
|
||||
|
||||
it 'removes control characters embedded within the name' do
|
||||
team = create(:team, account: account, name: "su\npport")
|
||||
expect(team.name).to eq('support')
|
||||
end
|
||||
|
||||
it 'is invalid when the name reduces to blank after sanitization' do
|
||||
team = build(:team, account: account, name: "\t\n ")
|
||||
expect(team).not_to be_valid
|
||||
expect(team.errors[:name]).to include(I18n.t('errors.validations.presence'))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#add_members' do
|
||||
let(:team) { FactoryBot.create(:team) }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user