fix(security): bound bet-notes length + harden EventId against path/control chars

Two defense-in-depth findings from the I-series security review (both safe today,
neither currently exploitable):
- AddBetForm.Notes was unbounded free-text into SQLite; add a 2000-char sanity cap
  in IsValid (covers both the add and edit paths), alongside the existing stake/rate
  caps.
- EventId only rejected empty/whitespace; now also reject path separators, '..'
  traversal, control/newline chars and over-length input so no current-or-future
  consumer that builds a path/filename/log line from an id can be tricked. The
  charset stays open for forward-compat with non-numeric bookmaker ids.
This commit is contained in:
2026-05-29 14:14:12 +03:00
parent 690d98d194
commit 0683e348ba
4 changed files with 116 additions and 0 deletions
@@ -0,0 +1,55 @@
using FluentAssertions;
using Marathon.Domain.Enums;
using Marathon.UI.Services;
namespace Marathon.UI.Tests.Services;
public sealed class AddBetFormTests
{
private static AddBetForm Valid() => new()
{
EventId = "26456117",
Type = BetType.Win,
Side = Side.Side1,
Rate = 1.90m,
Stake = 100m,
Notes = "ok",
};
[Fact]
public void IsValid_ReturnsTrue_ForAWellFormedForm()
{
Valid().IsValid(out var error).Should().BeTrue();
error.Should().BeNull();
}
[Fact]
public void IsValid_AllowsNullNotes()
{
var form = Valid();
form.Notes = null;
form.IsValid(out var error).Should().BeTrue();
error.Should().BeNull();
}
[Fact]
public void IsValid_AllowsNotesAtTheMaxLength()
{
var form = Valid();
form.Notes = new string('x', AddBetForm.MaxNotesLength);
form.IsValid(out var error).Should().BeTrue();
error.Should().BeNull();
}
[Fact]
public void IsValid_RejectsNotesOverTheMaxLength()
{
var form = Valid();
form.Notes = new string('x', AddBetForm.MaxNotesLength + 1);
form.IsValid(out var error).Should().BeFalse();
error.Should().Contain("Notes");
}
}