@@ -71,15 +71,8 @@ public void testSnapshotNameValidation()
7171 assertThatCode (() -> cfs .snapshot ("a_tag_1and_something2-more" )).doesNotThrowAnyException ();
7272 assertThatCode (() -> cfs .snapshot (repeat ('a' , SchemaConstants .FILENAME_LENGTH ))).doesNotThrowAnyException ();
7373
74- // AWS S3 "Safe characters" accepted by the relaxed allowlist:
75- // ! . * ' ( )
76- // See https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html#object-key-guidelines
74+ // Only alphanumerics, '-', '_' and '.' are accepted.
7775 assertThatCode (() -> cfs .snapshot ("snap.2026-05-20" )).doesNotThrowAnyException ();
78- assertThatCode (() -> cfs .snapshot ("important!" )).doesNotThrowAnyException ();
79- assertThatCode (() -> cfs .snapshot ("backup*" )).doesNotThrowAnyException ();
80- assertThatCode (() -> cfs .snapshot ("o'snap" )).doesNotThrowAnyException ();
81- assertThatCode (() -> cfs .snapshot ("snap(1)" )).doesNotThrowAnyException ();
82- assertThatCode (() -> cfs .snapshot ("!._-*'()" )).doesNotThrowAnyException ();
8376 // Dots embedded in a name are not traversal: with '/' excluded, "a..tag" is just a literal directory.
8477 assertThatCode (() -> cfs .snapshot ("a..tag" )).doesNotThrowAnyException ();
8578
@@ -89,12 +82,26 @@ public void testSnapshotNameValidation()
8982 .hasMessage ("Snapshot name must not be more than 255 characters long for " +
9083 "resolved snapshot name (got 256 characters for \" " + tooLong + "\" )" );
9184
92- // '/' is not in the S3-safe set; this is what kills traversal attempts like "../../mysnapshot".
85+ // '/' is not in the allowed set; this is what kills traversal attempts like "../../mysnapshot".
9386 assertThatThrownBy (() -> cfs .snapshot ("a" + sep + "tag" ))
9487 .isInstanceOf (IllegalArgumentException .class )
9588 .hasMessage ("Snapshot name cannot contain " + sep );
9689
97- // Other characters outside the S3-safe set must still be rejected.
90+ // The shell-significant S3 "safe" characters (! * ' ( )) are deliberately NOT allowed.
91+ assertThatThrownBy (() -> cfs .snapshot ("important!" ))
92+ .isInstanceOf (IllegalArgumentException .class )
93+ .hasMessage ("Snapshot name contains illegal characters: important!" );
94+ assertThatThrownBy (() -> cfs .snapshot ("backup*" ))
95+ .isInstanceOf (IllegalArgumentException .class )
96+ .hasMessage ("Snapshot name contains illegal characters: backup*" );
97+ assertThatThrownBy (() -> cfs .snapshot ("o'snap" ))
98+ .isInstanceOf (IllegalArgumentException .class )
99+ .hasMessage ("Snapshot name contains illegal characters: o'snap" );
100+ assertThatThrownBy (() -> cfs .snapshot ("snap(1)" ))
101+ .isInstanceOf (IllegalArgumentException .class )
102+ .hasMessage ("Snapshot name contains illegal characters: snap(1)" );
103+
104+ // Other characters outside the allowed set must still be rejected.
98105 assertThatThrownBy (() -> cfs .snapshot ("a tag" ))
99106 .isInstanceOf (IllegalArgumentException .class )
100107 .hasMessage ("Snapshot name contains illegal characters: a tag" );
@@ -114,9 +121,12 @@ public void testSnapshotNameValidation()
114121
115122 try (WithProperties p = new WithProperties ().set (CassandraRelevantProperties .SNAPSHOT_NAME_VALIDATION , false ))
116123 {
117- // Previously-rejected characters are now accepted: space, ':', and other non-S3-safe chars.
124+ // The character check is bypassed entirely: space, ':', and the now-disallowed
125+ // shell-significant characters (! * ' ( )) are all accepted.
118126 assertThatCode (() -> cfs .snapshot ("a tag" )).doesNotThrowAnyException ();
119127 assertThatCode (() -> cfs .snapshot ("a:tag" )).doesNotThrowAnyException ();
128+ assertThatCode (() -> cfs .snapshot ("important!" )).doesNotThrowAnyException ();
129+ assertThatCode (() -> cfs .snapshot ("snap(1)" )).doesNotThrowAnyException ();
120130
121131 // Path separator and "." / ".." rejections are unconditional — they guard against
122132 // traversal regardless of the toggle.
0 commit comments