Conversation
Greptile SummaryThis PR moves the prompt-protection rule preview from a client-side JS regex eval to a server-side GraphQL mutation ( Confidence Score: 5/5Safe to merge — prior P1 concerns are addressed and remaining findings are P2 quality suggestions The two previously flagged issues (infinite loop from unstable mutation ref in useEffect deps, toast spam on preview errors) are both correctly resolved. Two new P2 observations remain: the regexp2/v2 beta dependency and the absence of a testText size cap in the preview endpoint. Neither blocks correctness of the feature today. go.mod and internal/server/biz/prompt_protection_preview.go for the beta dependency and missing input size guard Important Files Changed
Sequence DiagramsequenceDiagram
participant U as User (Dialog)
participant FE as rules-action-dialog.tsx
participant GQL as GraphQL Mutation
participant BIZ as PromptProtectionRuleService.Preview
participant RE as regexp2/v2
U->>FE: types testText / pattern
FE->>FE: debounce 250ms
FE->>GQL: previewPromptProtectionRule(input)
GQL->>BIZ: Preview(pattern, testText, settings)
BIZ->>BIZ: ValidateSettings()
BIZ->>RE: getOrCompilePromptProtectionPattern(pattern)
RE-->>BIZ: compiled regexp
BIZ->>RE: MatchString(testText)
RE-->>BIZ: hasMatch
alt action == mask && hasMatch
BIZ->>RE: Replace(testText, replacement)
RE-->>BIZ: masked result
else action == reject && hasMatch
BIZ->>BIZ: result = "reject"
end
BIZ-->>GQL: PromptProtectionPreviewResult
GQL-->>FE: { result, hasMatch }
FE-->>U: show inline preview (match / no-match / error)
Reviews (2): Last reviewed commit: "feat: align endpoint i18n and prompt pro..." | Re-trigger Greptile |
There was a problem hiding this comment.
Code Review
This pull request introduces a server-side preview feature for prompt protection rules, migrating the regex evaluation logic from the frontend to the backend. Key changes include the addition of a new GraphQL mutation, the implementation of the preview logic in the business layer using the regexp2/v2 library, and frontend updates to support debounced preview requests with loading states. Feedback highlights a potential ReDoS vulnerability due to the lack of regex execution timeouts and suggests a more idiomatic approach for string replacement parameters.
| func (svc *PromptProtectionRuleService) Preview(ctx context.Context, input PromptProtectionPreviewInput) (*PromptProtectionPreviewResult, error) { | ||
| if err := svc.ValidateSettings(input.Pattern, input.Settings); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| re, err := getOrCompilePromptProtectionPattern(input.Pattern) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("invalid regex pattern: %w", err) | ||
| } | ||
|
|
||
| hasMatch, err := re.MatchString(input.TestText) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| result := input.TestText | ||
| if hasMatch && input.Settings.Action == objects.PromptProtectionActionMask { | ||
| result, err = re.Replace(input.TestText, input.Settings.Replacement, -1, -1) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
|
|
||
| if hasMatch && input.Settings.Action == objects.PromptProtectionActionReject { | ||
| result = string(objects.PromptProtectionActionReject) | ||
| } | ||
|
|
||
| return &PromptProtectionPreviewResult{ | ||
| Result: result, | ||
| HasMatch: hasMatch, | ||
| }, nil | ||
| } |
There was a problem hiding this comment.
The Preview function executes a regular expression against user-provided text without a timeout. This can lead to Denial of Service (ReDoS) if a complex or malicious regex pattern is provided, especially since this is a preview feature where users are encouraged to experiment with patterns. Consider setting a MatchTimeout on the regexp2.Regexp object.
|
|
||
| result := input.TestText | ||
| if hasMatch && input.Settings.Action == objects.PromptProtectionActionMask { | ||
| result, err = re.Replace(input.TestText, input.Settings.Replacement, -1, -1) |
There was a problem hiding this comment.
Using -1 for the startAt parameter in re.Replace is non-standard and only supported in regexp2/v2. While the project has been upgraded to v2 in this PR, it is generally clearer and more idiomatic to use 0 to indicate starting from the beginning of the string.
| result, err = re.Replace(input.TestText, input.Settings.Replacement, -1, -1) | |
| result, err = re.Replace(input.TestText, input.Settings.Replacement, 0, -1) |
| {previewMutation.isError ? ( | ||
| <div className='flex items-start gap-2 text-destructive'> | ||
| <AlertCircle className='h-4 w-4 mt-0.5 flex-shrink-0' /> | ||
| <span>{previewResult.error}</span> | ||
| <span>{previewMutation.error instanceof Error ? previewMutation.error.message : t('promptProtectionRules.test.invalidPattern')}</span> | ||
| </div> | ||
| ) : previewResult?.hasMatch ? ( | ||
| ) : previewMutation.data?.hasMatch ? ( | ||
| <div className='flex items-start gap-2 text-green-600 dark:text-green-400'> | ||
| <CheckCircle2 className='h-4 w-4 mt-0.5 flex-shrink-0' /> | ||
| <pre className='whitespace-pre-wrap font-mono text-xs'>{previewResult.result}</pre> | ||
| <pre className='whitespace-pre-wrap font-mono text-xs'>{previewMutation.data.result}</pre> | ||
| </div> | ||
| ) : previewMutation.isPending ? ( | ||
| <div className='text-muted-foreground'>{t('common.loading')}</div> | ||
| ) : ( | ||
| <div className='text-muted-foreground'> | ||
| {t('promptProtectionRules.test.noMatch')} |
There was a problem hiding this comment.
🟡 Stale preview data shown instead of loading indicator due to incorrect condition ordering
In TanStack Query v5's useMutation, calling mutate() again sets isPending = true but does not clear data (it retains the previous successful result). The rendering chain checks previewMutation.data?.hasMatch (line 311) before previewMutation.isPending (line 316). When the user edits the pattern or test text while a previous successful match exists, the stale data.hasMatch is truthy and the old result is displayed instead of the loading indicator. The isPending branch is unreachable whenever the previous result had a match.
(Refers to lines 306-322)
Was this helpful? React with 👍 or 👎 to provide feedback.
Closes #1527