Conversation
968a088 to
b58528c
Compare
fa5755c to
1800b34
Compare
|
Claude finished @Jarema's task in 2m 40s —— View job PR Review: Add Subject Validation
SummaryThe PR introduces client-side subject validation with an opt-out escape hatch ( Blocking (previously flagged, still not fixed)
Non-blocking
No new issues found in the options/jetstream/bench changes — those look correct. |
| && memchr::memchr3(b' ', b'\r', b'\n', bytes).is_none() | ||
| && memchr::memchr(b'\t', bytes).is_none() |
There was a problem hiding this comment.
Still not addressed from previous review: incomplete control character validation
This only rejects 4 whitespace bytes. The NATS server rejects any byte ≤ 0x1f or == 0x7f (DEL). Subjects like "foo\x00bar" or "foo\x1bbar" pass this check but are rejected server-side.
| && memchr::memchr3(b' ', b'\r', b'\n', bytes).is_none() | |
| && memchr::memchr(b'\t', bytes).is_none() | |
| && bytes.iter().all(|&b| b > 0x1f && b != 0x7f) |
This single iterator pass handles all whitespace (including space 0x20, tab 0x09, CR 0x0d, LF 0x0a) and all other control characters, so the two memchr lines can be removed entirely.
| let mut i = 0; | ||
| while i < len { | ||
| let c = bytes[i]; | ||
| if c == b' ' || c == b'\t' || c == b'\r' || c == b'\n' { |
There was a problem hiding this comment.
Still not addressed from previous review: incomplete control character check in from_static_validated
Only 4 whitespace bytes are rejected, but the panic message claims to also reject "control characters". Bytes in the range 0x00–0x1f (and 0x7f) will silently pass this const validation even though the server rejects them.
| if c == b' ' || c == b'\t' || c == b'\r' || c == b'\n' { | |
| if c <= 0x1f || c == 0x7f { | |
| panic!("subject cannot contain control characters or whitespace"); | |
| } |
This covers the 4 currently-checked bytes (0x09 tab, 0x0a LF, 0x0d CR, 0x20 space) and all others that the NATS server rejects.
| SubscribeError(Box::new(std::io::Error::new( | ||
| std::io::ErrorKind::InvalidInput, | ||
| e, | ||
| ))) |
There was a problem hiding this comment.
Subscribe error is opaque on bad subject; inconsistent with PublishError
PublishError exposes PublishErrorKind::BadSubject so callers can match on the cause. Here, the validation failure is buried inside a SubscribeError(Box<io::Error>) with no structured variant — callers cannot distinguish "subject was invalid" from a send failure.
SubscribeError is currently an opaque newtype (struct SubscribeError(#[source] crate::Error)), so adding a BadSubject variant here isn't straightforward without changing the error type. At a minimum, consider adding an InvalidInput-kind check in user-facing docs, or convert SubscribeError to also use a kind enum so this case is machine-readable.
Signed-off-by: Tomasz Pietrek <tomasz@nats.io> Signed-off-by: Tomasz Pietrek <tomasz@synadia.com>
1800b34 to
8707439
Compare
|
Claude finished @Jarema's task in 2m 50s —— View job PR Review: Add Subject Validation
SummaryThe PR adds client-side subject validation with a Blocking (flagged in both previous reviews — still not addressed)
Non-blocking
No new correctness issues found in options, JetStream, consumer, or benchmark changes. |
| && bytes[bytes.len() - 1] != b'.' | ||
| && memchr::memmem::find(bytes, b"..").is_none() | ||
| && memchr::memchr3(b' ', b'\r', b'\n', bytes).is_none() | ||
| && memchr::memchr(b'\t', bytes).is_none() |
There was a problem hiding this comment.
Still not addressed from two previous reviews: incomplete control character validation
Only 4 whitespace bytes (' ', '\r', '\n', '\t') are rejected. The NATS server rejects any byte ≤ 0x1f (e.g. \x00, \x01, \x08, \x0b, \x0c, \x0e–\x1f) and also \x7f (DEL). Subjects like "foo\x00bar" or "foo\x1bbar" pass this check but are rejected server-side.
The two memchr lines can be replaced with a single iterator:
| && memchr::memchr(b'\t', bytes).is_none() | |
| && bytes.iter().all(|&b| b > 0x1f && b != 0x7f && b != b' ') |
This single condition covers tab (0x09 ≤ 0x1f), CR (0x0d ≤ 0x1f), LF (0x0a ≤ 0x1f), space (explicit exclusion), DEL (0x7f), and all other control characters in one pass.
| let mut i = 0; | ||
| while i < len { | ||
| let c = bytes[i]; | ||
| if c == b' ' || c == b'\t' || c == b'\r' || c == b'\n' { |
There was a problem hiding this comment.
Still not addressed from two previous reviews: incomplete control character check in from_static_validated
Only 4 whitespace bytes are rejected, but the panic message already says "control characters". Bytes \x00–\x08, \x0b, \x0c, \x0e–\x1f, and \x7f silently pass this compile-time check even though the server rejects them.
| if c == b' ' || c == b'\t' || c == b'\r' || c == b'\n' { | |
| if c <= 0x1f || c == 0x7f { | |
| panic!("subject cannot contain control characters or whitespace"); | |
| } |
This covers all 4 currently-checked bytes (tab 0x09, LF 0x0a, CR 0x0d ≤ 0x1f) plus space needs a separate check since 0x20 > 0x1f:
| if c == b' ' || c == b'\t' || c == b'\r' || c == b'\n' { | |
| if c <= 0x1f || c == 0x7f || c == b' ' { | |
| panic!("subject cannot contain control characters or whitespace"); | |
| } |
There was a problem hiding this comment.
@claude but we want only protocol-breaking guards here.
| let subject = subject.to_subject(); | ||
| let subject = self | ||
| .maybe_validate_subject(subject) | ||
| .map_err(|e| RequestError::with_source(RequestErrorKind::Other, e))?; |
There was a problem hiding this comment.
RequestError uses Other for bad subject; inconsistent with PublishError
PublishError exposes PublishErrorKind::BadSubject so callers can match on the cause. Here a bad subject returns RequestErrorKind::Other, which is indistinguishable from send failures or other internal errors.
Options:
- Add
RequestErrorKind::BadSubject(most consistent — mirrorsPublishErrorKind::BadSubject). - Document in the method's doc comment that
RequestErrorKind::Othercovers invalid subjects.
Option 1 is cleaner for callers who want to give a specific user-facing message when the subject is malformed.
This is much simpler approach that the previous one, which was trying to have type-safe validation, but failed to be sound code due to need to break the API.
Signed-off-by: Tomasz Pietrek tomasz@synadia.com