VYPR
High severity7.5NVD Advisory· Published Mar 27, 2026· Updated Apr 16, 2026

CVE-2026-22744

CVE-2026-22744

Description

In RedisFilterExpressionConverter of spring-ai-redis-store, when a user-controlled string is passed as a filter value for a TAG field, stringValue() inserts the value directly into the @field:{VALUE} RediSearch TAG block without escaping characters.This issue affects Spring AI: from 1.0.0 before 1.0.5, from 1.1.0 before 1.1.4.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.springframework.ai:spring-ai-redis-storeMaven
>= 1.0.0-M5, < 1.0.51.0.5
org.springframework.ai:spring-ai-redis-storeMaven
>= 1.1.0-M1, < 1.1.41.1.4

Affected products

1
  • cpe:2.3:a:vmware:spring_ai:*:*:*:*:*:*:*:*
    Range: >=1.0.0,<1.0.5

Patches

1
707e990c9152

fix: RedisFilterExpressionConverter handling string values for TAG/TEXT filter values

https://github.com/spring-projects/spring-aiIlayaperumal GopinathanMar 20, 2026via ghsa
2 files changed · +79 8
  • vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisFilterExpressionConverter.java+36 5 modified
    @@ -22,6 +22,8 @@
     import java.util.function.Function;
     import java.util.stream.Collectors;
     
    +import redis.clients.jedis.search.RediSearchUtil;
    +
     import org.springframework.ai.vectorstore.filter.Filter.Expression;
     import org.springframework.ai.vectorstore.filter.Filter.ExpressionType;
     import org.springframework.ai.vectorstore.filter.Filter.Group;
    @@ -117,12 +119,12 @@ private void doField(Expression expression, StringBuilder context) {
     				break;
     			case TAG:
     				context.append("{");
    -				context.append(stringValue(expression, value));
    +				context.append(tagStringValue(expression, value));
     				context.append("}");
     				break;
     			case TEXT:
     				context.append("(");
    -				context.append(stringValue(expression, value));
    +				context.append(textStringValue(expression, value));
     				context.append(")");
     				break;
     			default:
    @@ -131,12 +133,41 @@ private void doField(Expression expression, StringBuilder context) {
     		}
     	}
     
    -	private Object stringValue(Expression expression, Value value) {
    +	private String tagStringValue(Expression expression, Value value) {
    +		String delimiter = tagValueDelimiter(expression);
    +		if (value.value() instanceof List<?> list) {
    +			return list.stream().map(String::valueOf).map(this::escapeTagValue).collect(Collectors.joining(delimiter));
    +		}
    +		return escapeTagValue(String.valueOf(value.value()));
    +	}
    +
    +	private String textStringValue(Expression expression, Value value) {
     		String delimiter = tagValueDelimiter(expression);
     		if (value.value() instanceof List<?> list) {
    -			return String.join(delimiter, list.stream().map(String::valueOf).toList());
    +			return list.stream()
    +				.map(String::valueOf)
    +				.map(RediSearchUtil::escapeQuery)
    +				.collect(Collectors.joining(delimiter));
    +		}
    +		return RediSearchUtil.escapeQuery(String.valueOf(value.value()));
    +	}
    +
    +	/**
    +	 * Escapes characters that have special meaning inside a RediSearch TAG query clause
    +	 * ({@code @field:\{value\}}). The following characters are escaped with a backslash:
    +	 * {@code $}, {@code \}, {@code |}, {@code {}, {@code }}, {@code (}, {@code )},
    +	 * {@code [}, {@code ]}, {@code -}, and {@code '}.
    +	 */
    +	private String escapeTagValue(String value) {
    +		StringBuilder sb = new StringBuilder(value.length());
    +		for (int i = 0; i < value.length(); i++) {
    +			char c = value.charAt(i);
    +			switch (c) {
    +				case '\\', '$', '|', '{', '}', '(', ')', '[', ']', '-', '\'' -> sb.append('\\').append(c);
    +				default -> sb.append(c);
    +			}
     		}
    -		return value.value();
    +		return sb.toString();
     	}
     
     	private String tagValueDelimiter(Expression expression) {
    
  • vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisFilterExpressionConverterTests.java+43 3 modified
    @@ -131,12 +131,52 @@ void testComplexIdentifiers() {
     
     	@Test
     	void testSpecialCharactersInValues() {
    -		// Test values with Redis special characters that need escaping
     		String vectorExpr = converter(RedisVectorStore.MetadataField.tag("description"))
     			.convertExpression(new Expression(EQ, new Key("description"), new Value("test@value{with}special|chars")));
     
    -		// Should properly escape special Redis characters
    -		assertThat(vectorExpr).isEqualTo("@description:{test@value{with}special|chars}");
    +		assertThat(vectorExpr).isEqualTo("@description:{test@value\\{with\\}special\\|chars}");
    +	}
    +
    +	@Test
    +	void testTagValueWithInjectionPayload() {
    +		String vectorExpr = converter(RedisVectorStore.MetadataField.tag("category")).convertExpression(
    +				new Expression(EQ, new Key("category"), new Value("science} | @access_level:{restricted")));
    +
    +		assertThat(vectorExpr).isEqualTo("@category:{science\\} \\| @access_level:\\{restricted}");
    +		assertThat(vectorExpr).doesNotContain("} | @");
    +	}
    +
    +	@Test
    +	void testTagValueInListWithSpecialChars() {
    +		String vectorExpr = converter(RedisVectorStore.MetadataField.tag("category")).convertExpression(new Expression(
    +				IN, new Key("category"), new Value(List.of("science} | @access_level:{restricted", "normal"))));
    +
    +		assertThat(vectorExpr).isEqualTo("@category:{science\\} \\| @access_level:\\{restricted | normal}");
    +		assertThat(vectorExpr).doesNotContain("} | @");
    +	}
    +
    +	@Test
    +	void testTagValueWithPipe() {
    +		String vectorExpr = converter(RedisVectorStore.MetadataField.tag("status"))
    +			.convertExpression(new Expression(EQ, new Key("status"), new Value("active|inactive")));
    +
    +		assertThat(vectorExpr).isEqualTo("@status:{active\\|inactive}");
    +	}
    +
    +	@Test
    +	void testTagValueWithHyphen() {
    +		String vectorExpr = converter(RedisVectorStore.MetadataField.tag("type"))
    +			.convertExpression(new Expression(EQ, new Key("type"), new Value("non-fiction")));
    +
    +		assertThat(vectorExpr).isEqualTo("@type:{non\\-fiction}");
    +	}
    +
    +	@Test
    +	void testTextValueWithSpecialChars() {
    +		String vectorExpr = converter(RedisVectorStore.MetadataField.text("description"))
    +			.convertExpression(new Expression(EQ, new Key("description"), new Value("hello@world.com")));
    +
    +		assertThat(vectorExpr).isEqualTo("@description:(hello\\@world\\.com)");
     	}
     
     	@Test
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.