@@ -38,13 +38,26 @@ public class SnapshotOptionsTest
3838 @ Test
3939 public void testSnapshotNameValidation ()
4040 {
41+ // Previously-allowed alphanumerics, '-' and '_' must still be accepted.
4142 validate ("atag" , USER );
4243 validate ("a-tag" , USER );
4344 validate ("a_tag" , USER );
4445 validate ("a_tag" + Instant .now ().toEpochMilli (), USER );
4546 validate ("a_tag_1and_something2-more" , USER );
4647 validate ("a" .repeat (255 ), USER );
4748
49+ // AWS S3 "Safe characters" newly accepted by the relaxed allowlist:
50+ // ! . * ' ( )
51+ // See https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html#object-key-guidelines
52+ validate ("snap.2026-05-20" , USER );
53+ validate ("important!" , USER );
54+ validate ("backup*" , USER );
55+ validate ("o'snap" , USER );
56+ validate ("snap(1)" , USER );
57+ validate ("!._-*'()" , USER );
58+ // Dots embedded in a name are not traversal: with '/' excluded, "a..tag" is just a literal directory.
59+ validate ("a..tag" , USER );
60+
4861 assertThatThrownBy (() -> validate ("a" .repeat (256 ), USER ))
4962 .isInstanceOf (IllegalArgumentException .class )
5063 .hasMessage ("Snapshot name must not be more than 255 characters long for " +
@@ -55,13 +68,28 @@ public void testSnapshotNameValidation()
5568 .isInstanceOf (IllegalArgumentException .class )
5669 .hasMessageContaining ("Snapshot name must not be more than 255 characters long" );
5770
71+ // '/' is not in the S3-safe set; this is what kills traversal attempts like "../../mysnapshot".
5872 assertThatThrownBy (() -> validate ('a' + File .pathSeparator () + "tag" , USER ))
5973 .isInstanceOf (IllegalArgumentException .class )
6074 .hasMessage ("Snapshot name contains illegal characters: a/tag" );
6175
62- assertThatThrownBy (() -> validate ("a..tag" , USER ))
76+ // Other characters outside the S3-safe set must still be rejected.
77+ assertThatThrownBy (() -> validate ("a tag" , USER ))
78+ .isInstanceOf (IllegalArgumentException .class )
79+ .hasMessage ("Snapshot name contains illegal characters: a tag" );
80+ assertThatThrownBy (() -> validate ("a:tag" , USER ))
81+ .isInstanceOf (IllegalArgumentException .class )
82+ .hasMessage ("Snapshot name contains illegal characters: a:tag" );
83+
84+ // "." and ".." pass the charset check but resolve to the snapshots/ dir itself
85+ // and its parent (the live table dir) respectively, so they must be rejected as reserved.
86+ assertThatThrownBy (() -> validate ("." , USER ))
87+ .isInstanceOf (IllegalArgumentException .class )
88+ .hasMessage ("Snapshot name '.' is reserved" );
89+
90+ assertThatThrownBy (() -> validate (".." , USER ))
6391 .isInstanceOf (IllegalArgumentException .class )
64- .hasMessage ("Snapshot name contains illegal characters: a..tag " );
92+ .hasMessage ("Snapshot name '..' is reserved " );
6593 }
6694
6795 private void validate (String tag , SnapshotType type )
0 commit comments