mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-24 00:55:35 +00:00
fix(validation): avoid regex stack overflow
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
parent
824a992afc
commit
7c62aa218a
2 changed files with 81 additions and 10 deletions
|
|
@ -20,9 +20,6 @@ public class BasicPrePublishValidator implements PrePublishValidator {
|
|||
private static final Pattern ASSIGNMENT_WITH_SENSITIVE_KEY = Pattern.compile(
|
||||
"(?i)(api[_-]?key|access[_-]?key|secret|password|token)\\s*[:=]\\s*(.+)$"
|
||||
);
|
||||
private static final Pattern QUOTED_LITERAL = Pattern.compile(
|
||||
"^(['\"])(.*)\\1(?:\\s*[,;)}\\]])*\\s*(?:(?://|#).*)?$"
|
||||
);
|
||||
private static final Pattern IDENTIFIER = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
|
||||
private static final Pattern BARE_LITERAL = Pattern.compile("[A-Za-z0-9_\\-]{12,}");
|
||||
private static final Set<String> PLACEHOLDER_MARKERS = Set.of(
|
||||
|
|
@ -112,9 +109,9 @@ public class BasicPrePublishValidator implements PrePublishValidator {
|
|||
return null;
|
||||
}
|
||||
|
||||
Matcher quotedLiteralMatcher = QUOTED_LITERAL.matcher(rawValue);
|
||||
if (quotedLiteralMatcher.matches()) {
|
||||
return quotedLiteralMatcher.group(2);
|
||||
String quotedLiteral = extractQuotedLiteral(rawValue);
|
||||
if (quotedLiteral != null) {
|
||||
return quotedLiteral;
|
||||
}
|
||||
|
||||
rawValue = stripInlineComment(rawValue);
|
||||
|
|
@ -122,9 +119,9 @@ public class BasicPrePublishValidator implements PrePublishValidator {
|
|||
return null;
|
||||
}
|
||||
|
||||
quotedLiteralMatcher = QUOTED_LITERAL.matcher(rawValue);
|
||||
if (quotedLiteralMatcher.matches()) {
|
||||
return quotedLiteralMatcher.group(2);
|
||||
quotedLiteral = extractQuotedLiteral(rawValue);
|
||||
if (quotedLiteral != null) {
|
||||
return quotedLiteral;
|
||||
}
|
||||
|
||||
if (looksLikeExpression(rawValue) || IDENTIFIER.matcher(rawValue).matches()) {
|
||||
|
|
@ -134,6 +131,52 @@ public class BasicPrePublishValidator implements PrePublishValidator {
|
|||
return BARE_LITERAL.matcher(rawValue).matches() ? rawValue : null;
|
||||
}
|
||||
|
||||
private String extractQuotedLiteral(String rawValue) {
|
||||
if (rawValue.length() < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
char quote = rawValue.charAt(0);
|
||||
if (quote != '\'' && quote != '"') {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean escaped = false;
|
||||
for (int i = 1; i < rawValue.length(); i++) {
|
||||
char current = rawValue.charAt(i);
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (current == '\\') {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
if (current == quote) {
|
||||
return hasOnlyTrailingSyntax(rawValue, i + 1) ? rawValue.substring(1, i) : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean hasOnlyTrailingSyntax(String rawValue, int startIndex) {
|
||||
for (int i = startIndex; i < rawValue.length(); i++) {
|
||||
char current = rawValue.charAt(i);
|
||||
if (Character.isWhitespace(current) || isTrailingDelimiter(current)) {
|
||||
continue;
|
||||
}
|
||||
if (current == '#') {
|
||||
return true;
|
||||
}
|
||||
return current == '/' && i + 1 < rawValue.length() && rawValue.charAt(i + 1) == '/';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isTrailingDelimiter(char value) {
|
||||
return value == ',' || value == ';' || value == ')' || value == '}' || value == ']';
|
||||
}
|
||||
|
||||
private String stripInlineComment(String rawValue) {
|
||||
int hashIndex = rawValue.indexOf('#');
|
||||
if (hashIndex >= 0) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
|
@ -109,8 +110,9 @@ class BasicPrePublishValidatorTest {
|
|||
self._client_secret = credentials.client_secret
|
||||
access_token = ensure_valid_access_token(session)
|
||||
headers = build_headers(access_token=access_token)
|
||||
client_secret = "prefix-" + configured_secret
|
||||
""".getBytes(StandardCharsets.UTF_8),
|
||||
229,
|
||||
275,
|
||||
"text/x-python"
|
||||
);
|
||||
|
||||
|
|
@ -152,4 +154,30 @@ class BasicPrePublishValidatorTest {
|
|||
assertTrue(result.warnings().stream().anyMatch(warning -> warning.contains("line 3")));
|
||||
assertTrue(result.warnings().stream().anyMatch(warning -> warning.contains("line 4")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldScanDeeplyNestedSingleLineObjectWithoutOverflowingRegexStack() {
|
||||
String content = "const config = "
|
||||
+ "{ nested: ".repeat(5_000)
|
||||
+ "{ token: \"literalcredential123\""
|
||||
+ " }".repeat(5_001)
|
||||
+ ";";
|
||||
PackageEntry script = new PackageEntry(
|
||||
"scripts/deeply-nested.js",
|
||||
content.getBytes(StandardCharsets.UTF_8),
|
||||
content.length(),
|
||||
"text/javascript"
|
||||
);
|
||||
PrePublishValidator.SkillPackageContext context = new PrePublishValidator.SkillPackageContext(
|
||||
List.of(script),
|
||||
new SkillMetadata("Deeply Nested Skill", "desc", "1.0.0", "body", Map.of()),
|
||||
"user-1",
|
||||
1L
|
||||
);
|
||||
|
||||
ValidationResult result = assertDoesNotThrow(() -> validator.validate(context));
|
||||
|
||||
assertTrue(result.passed());
|
||||
assertTrue(result.warnings().stream().anyMatch(warning -> warning.contains("line 1")));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue