Skip to content

Commit c2d000e

Browse files
authored
fix: disallow prompts with leading or trailing spaces (usebruno#6201)
1 parent 6aaccab commit c2d000e

File tree

4 files changed

+48
-9
lines changed

4 files changed

+48
-9
lines changed

packages/bruno-app/src/utils/common/codemirror.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import get from 'lodash/get';
22
import { mockDataFunctions } from '@usebruno/common';
3+
import { PROMPT_VARIABLE_TEXT_PATTERN } from '@usebruno/common/utils';
34

45
const CodeMirror = require('codemirror');
56

@@ -31,8 +32,8 @@ export const defineCodeMirrorBrunoVariablesMode = (_variables, mode, highlightPa
3132
if (ch === '}' && stream.peek() === '}') {
3233
stream.eat('}');
3334

34-
// Prompt variable: starts with '?'
35-
if (word.startsWith('?')) {
35+
// Prompt variable: starts with '?', no leading/trailing spaces, no braces
36+
if (PROMPT_VARIABLE_TEXT_PATTERN.test(word)) {
3637
return `variable-prompt`;
3738
}
3839

packages/bruno-common/src/utils/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export {
1313
} from './template-hasher';
1414

1515
export {
16+
PROMPT_VARIABLE_TEXT_PATTERN,
17+
PROMPT_VARIABLE_TEMPLATE_PATTERN,
1618
extractPromptVariables,
1719
extractPromptVariablesFromString
1820
} from './prompt-variables';

packages/bruno-common/src/utils/prompt-variables.spec.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,13 @@ describe('prompt variable utils', () => {
4545
expect(extractPromptVariables([{ text: 'Multiple {{?prompts}} in {{?one}} string', noPrompt: 'No prompt here' }, ['Another {{?test}} string', { prompt: '{{?nested}}', no: 'prompt' }]])).toEqual(['prompts', 'one', 'test', 'nested']);
4646
});
4747

48-
it('should deduplicate prompt variables', () => {
49-
// Strings
50-
expect(extractPromptVariables(['{{?world}} prompt here', 'Hello {{?world}}'])).toEqual(['world']);
51-
expect(extractPromptVariables(['Multiple {{?prompts}} in {{?one}} string', 'Another {{?one}} string'])).toEqual(['prompts', 'one']);
48+
it('should not extract prompt variables from invalid template patterns', () => {
49+
expect(extractPromptVariables('Prompt with valid {{?inner space}}')).toEqual(['inner space']);
50+
expect(extractPromptVariables('Prompt with invalid {{? leading space}}')).toEqual([]);
51+
expect(extractPromptVariables('Prompt with invalid {{?trailing space }}')).toEqual([]);
52+
expect(extractPromptVariables('Prompt with invalid {{?{curly brace}}')).toEqual([]);
53+
expect(extractPromptVariables('Prompt with invalid {{?}curly brace}}')).toEqual([]);
54+
expect(extractPromptVariables('Prompt with invalid {{?{curly brace}}}')).toEqual([]);
5255
});
5356
});
5457
});

packages/bruno-common/src/utils/prompt-variables.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,47 @@
1+
/**
2+
* Inner regex pattern for prompt variable names (without braces or `?` prefix)
3+
*
4+
* Pattern: /[^{}\s](?:[^{}]*[^{}\s])?/
5+
*
6+
* Breakdown:
7+
* | Part | Meaning |
8+
* | -------------- | ---------------------------------------------------------- |
9+
* | `[^\s{}]` | First character: not whitespace, `{`, or `}` |
10+
* | `(?:...)?` | Optional non-capturing group (allows single-char names) |
11+
* | `[^{}]*` | Middle characters: any except `{` or `}` (spaces allowed) |
12+
* | `[^\s{}]` | Last character: not whitespace, `{`, or `}` |
13+
*
14+
* This inner pattern is reused in:
15+
* - PROMPT_VARIABLE_TEXT_PATTERN: Matches "?Name" format (with anchors)
16+
* - PROMPT_VARIABLE_PATTERN: Matches "{{?Name}}" format (in templates)
17+
*
18+
* Valid examples: "Name", "Prompt Var", "x"
19+
* Invalid examples: " Name", "Name ", "{Name}", "Na{me}"
20+
*/
21+
const PROMPT_VARIABLE_PATTERN = /[^{}\s](?:[^{}]*[^{}\s])?/;
22+
23+
/**
24+
* Valid examples: "?Name", "?Prompt Var", "?x"
25+
* Invalid examples: "? Name", "?Name ", "?{{Name}}", "?{Name}"
26+
*/
27+
export const PROMPT_VARIABLE_TEXT_PATTERN = new RegExp(`^\\?(${PROMPT_VARIABLE_PATTERN.source})$`);
28+
29+
/**
30+
* Valid matches: "{{?Name}}", "{{?Prompt Var}}", "{{?x}}"
31+
* Invalid: "{{? Name}}", "{{?Name }}", "{{?{Name}}}"
32+
*/
33+
export const PROMPT_VARIABLE_TEMPLATE_PATTERN = new RegExp(`{{\\?(${PROMPT_VARIABLE_PATTERN.source})}}`, 'g');
34+
135
/**
236
* Extract prompt variables matching {{?<Prompt Text>}} from a string.
337
* @param {string} str - The input string.
438
* @returns {string[]} - An array of extracted prompt variables.
539
*/
640
export const extractPromptVariablesFromString = (str: string): string[] => {
7-
const regex = /{{\?([^}]+)}}/g;
841
const prompts = new Set<string>();
942
let match;
10-
while ((match = regex.exec(str)) !== null) {
11-
prompts.add(match[1].trim());
43+
while ((match = PROMPT_VARIABLE_TEMPLATE_PATTERN.exec(str)) !== null) {
44+
prompts.add(match[1]);
1245
}
1346
return Array.from(prompts);
1447
};

0 commit comments

Comments
 (0)