⚡ Sangsakala Proxy 🏠 Home
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: scan schema.table without a backtracking regex
CodeQL flagged QUALIFIED_TABLE as polynomial ReDoS on Agent Q&A
(long runs of 'A'). Walk the string once instead.

Co-authored-by: Venkat SF <venkatesh.sakamuri@stayflexi.com>
  • Loading branch information
cursoragent and venkateshsakamuri-lab committed Aug 22, 2026
commit ae10b41624d45482dbb4a48b12e772aab715eac2
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@
@Service
public class BrainNoteIntentService {

private static final Pattern QUALIFIED_TABLE = Pattern.compile(
"(?<![\\w.])([a-zA-Z_][\\w]*)\\.([a-zA-Z_][\\w]*)(?![\\w.])"
);
private static final Pattern BACKTICK_IDENT = Pattern.compile("`([^`]+)`");
private static final Pattern DEFINITION_CUE = Pattern.compile(
"\\b(metric|definition|means|pinned|use this|correct|from|count|always|filter|join)\\b",
Expand Down Expand Up @@ -242,16 +239,76 @@ private Optional<String[]> resolveTarget(String text) {
if (table != null) {
return Optional.of(new String[] { table, column });
}
Matcher tables = QUALIFIED_TABLE.matcher(text);
while (tables.find()) {
String schema = tables.group(1);
String name = tables.group(2);
String[] prose = findFirstQualifiedTable(text);
if (prose != null) {
return Optional.of(new String[] { prose[0] + "." + prose[1], column });
}
return Optional.empty();
}

/**
* Linear scan for {@code schema.table} (not {@code a.b.c}). Avoids the
* backtracking {@code [\w]*\.[\w]*} pattern CodeQL flags as ReDoS on
* attacker-controlled Agent answers.
*/
static String[] findFirstQualifiedTable(String text) {
if (text == null || text.isEmpty()) {
return null;
}
int n = text.length();
int i = 0;
while (i < n) {
char c = text.charAt(i);
if (!isIdentStart(c)) {
i++;
continue;
}
if (i > 0) {
char prev = text.charAt(i - 1);
if (isIdentPart(prev) || prev == '.') {
i++;
continue;
}
}
int schemaStart = i;
i++;
while (i < n && isIdentPart(text.charAt(i))) {
i++;
}
if (i >= n || text.charAt(i) != '.') {
continue;
}
int schemaEnd = i;
i++;
if (i >= n || !isIdentStart(text.charAt(i))) {
continue;
}
int nameStart = i;
i++;
while (i < n && isIdentPart(text.charAt(i))) {
i++;
}
if (i < n) {
char next = text.charAt(i);
if (isIdentPart(next) || next == '.') {
continue;
}
}
String schema = text.substring(schemaStart, schemaEnd);
if (STOP_TABLES.contains(schema.toLowerCase(Locale.ROOT))) {
continue;
}
return Optional.of(new String[] { schema + "." + name, column });
return new String[] { schema, text.substring(nameStart, i) };
}
return Optional.empty();
return null;
}

private static boolean isIdentStart(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
}

private static boolean isIdentPart(char c) {
return isIdentStart(c) || (c >= '0' && c <= '9');
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ void proposeFromTurn_ignoresNonDefinitionAnswers() {
assertThat(proposal).isEmpty();
}

@Test
void findFirstQualifiedTable_skipsTripleQualifiedAndCatalogSchemas() {
assertThat(BrainNoteIntentService.findFirstQualifiedTable("see marts.dim_person.col then crm.accounts"))
.containsExactly("crm", "accounts");
assertThat(BrainNoteIntentService.findFirstQualifiedTable("pg_catalog.pg_class")).isNull();
assertThat(BrainNoteIntentService.findFirstQualifiedTable("A".repeat(20_000) + " marts.dim_person"))
.containsExactly("marts", "dim_person");
}

@Test
void mergeTexts_doesNotDuplicateTheSameSentence() {
String merged = service.mergeTexts(
Expand Down
Loading