Skip to content

Commit 394dc0d

Browse files
committed
further restrict safe set
1 parent 7f44da9 commit 394dc0d

2 files changed

Lines changed: 27 additions & 18 deletions

File tree

src/java/org/apache/cassandra/service/snapshot/SnapshotOptions.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public class SnapshotOptions
4949
// 0-9 a-z A-Z ! - _ . * ' ( )
5050
// See https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html#object-key-guidelines
5151
// Hyphen is placed last in the character class, so it stays literal and never becomes a range operator.
52-
private static final Pattern SAFE_SNAPSHOT_NAME = Pattern.compile("[a-zA-Z0-9!_.*'()-]+");
52+
private static final Pattern SAFE_SNAPSHOT_NAME = Pattern.compile("[a-zA-Z0-9_.-]+");
5353
public final SnapshotType type;
5454
public final String tag;
5555
public final DurationSpec.IntSecondsBound ttl;
@@ -243,10 +243,11 @@ private void validateTag(String tag)
243243
FILENAME_LENGTH, resolvedSnapshotname.length(), resolvedSnapshotname));
244244
}
245245

246-
// Allowed characters follow the AWS S3 "Safe characters" set documented at
247-
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html#object-key-guidelines :
248-
// 0-9 a-z A-Z ! - _ . * ' ( )
249-
// The path separator '/' is intentionally excluded,
246+
// Allowed characters are a conservative subset of the AWS S3 "Safe characters" set
247+
// (https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html#object-key-guidelines):
248+
// 0-9 a-z A-Z - _ .
249+
// The remaining S3-safe characters (! * ' ( )) are intentionally excluded as they are
250+
// shell-significant and error-prone in paths, and the path separator '/' is excluded too,
250251
// which is what blocks traversal attempts such as "../../mysnapshot"
251252
if (!SAFE_SNAPSHOT_NAME.matcher(resolvedSnapshotname).matches())
252253
{

test/unit/org/apache/cassandra/service/snapshot/SnapshotOptionsTest.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,14 @@ public void testSnapshotNameValidation()
4545

4646
try (WithProperties p = new WithProperties().set(CassandraRelevantProperties.SNAPSHOT_NAME_VALIDATION, true))
4747
{
48-
// Previously-allowed alphanumerics, '-' and '_' must still be accepted.
48+
// Only alphanumerics, '-', '_' and '.' are accepted.
4949
validate("atag", USER);
5050
validate("a-tag", USER);
5151
validate("a_tag", USER);
5252
validate("a_tag" + Instant.now().toEpochMilli(), USER);
5353
validate("a_tag_1and_something2-more", USER);
5454
validate("a".repeat(255), USER);
55-
56-
// AWS S3 "Safe characters" newly accepted by the relaxed allowlist:
57-
// ! . * ' ( )
58-
// See https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html#object-key-guidelines
5955
validate("snap.2026-05-20", USER);
60-
validate("important!", USER);
61-
validate("backup*", USER);
62-
validate("o'snap", USER);
63-
validate("snap(1)", USER);
64-
validate("!._-*'()", USER);
6556
// Dots embedded in a name are not traversal: with '/' excluded, "a..tag" is just a literal directory.
6657
validate("a..tag", USER);
6758

@@ -75,12 +66,26 @@ public void testSnapshotNameValidation()
7566
.isInstanceOf(IllegalArgumentException.class)
7667
.hasMessageContaining("Snapshot name must not be more than 255 characters long");
7768

78-
// '/' is not in the S3-safe set; this is what kills traversal attempts like "../../mysnapshot".
69+
// '/' is not in the allowed set; this is what kills traversal attempts like "../../mysnapshot".
7970
assertThatThrownBy(() -> validate('a' + sep + "tag", USER))
8071
.isInstanceOf(IllegalArgumentException.class)
8172
.hasMessage("Snapshot name cannot contain " + sep);
8273

83-
// Other characters outside the S3-safe set must still be rejected.
74+
// The shell-significant S3 "safe" characters (! * ' ( )) are deliberately NOT allowed.
75+
assertThatThrownBy(() -> validate("important!", USER))
76+
.isInstanceOf(IllegalArgumentException.class)
77+
.hasMessage("Snapshot name contains illegal characters: important!");
78+
assertThatThrownBy(() -> validate("backup*", USER))
79+
.isInstanceOf(IllegalArgumentException.class)
80+
.hasMessage("Snapshot name contains illegal characters: backup*");
81+
assertThatThrownBy(() -> validate("o'snap", USER))
82+
.isInstanceOf(IllegalArgumentException.class)
83+
.hasMessage("Snapshot name contains illegal characters: o'snap");
84+
assertThatThrownBy(() -> validate("snap(1)", USER))
85+
.isInstanceOf(IllegalArgumentException.class)
86+
.hasMessage("Snapshot name contains illegal characters: snap(1)");
87+
88+
// Other characters outside the allowed set must still be rejected.
8489
assertThatThrownBy(() -> validate("a tag", USER))
8590
.isInstanceOf(IllegalArgumentException.class)
8691
.hasMessage("Snapshot name contains illegal characters: a tag");
@@ -101,9 +106,12 @@ public void testSnapshotNameValidation()
101106

102107
try (WithProperties p = new WithProperties().set(CassandraRelevantProperties.SNAPSHOT_NAME_VALIDATION, false))
103108
{
104-
// Previously-rejected characters are now accepted: space, ':', and other non-S3-safe chars.
109+
// The character check is bypassed entirely: space, ':', and the now-disallowed
110+
// shell-significant characters (! * ' ( )) are all accepted.
105111
assertThatCode(() -> validate("a tag", USER)).doesNotThrowAnyException();
106112
assertThatCode(() -> validate("a:tag", USER)).doesNotThrowAnyException();
113+
assertThatCode(() -> validate("important!", USER)).doesNotThrowAnyException();
114+
assertThatCode(() -> validate("snap(1)", USER)).doesNotThrowAnyException();
107115

108116
assertThatCode(() -> validate("a".repeat(256), USER)).doesNotThrowAnyException();
109117
assertThatCode(() -> validate("a".repeat(250), SnapshotType.UPGRADE)).doesNotThrowAnyException();

0 commit comments

Comments
 (0)