Skip to content

feat: align endpoint i18n and prompt protection preview#1546

Merged
looplj merged 1 commit into
unstablefrom
dev-tmp
Apr 30, 2026
Merged

feat: align endpoint i18n and prompt protection preview#1546
looplj merged 1 commit into
unstablefrom
dev-tmp

Conversation

@looplj

@looplj looplj commented Apr 30, 2026

Copy link
Copy Markdown
Owner

Closes #1527


Open in Devin Review

@greptile-apps

greptile-apps Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR moves the prompt-protection rule preview from a client-side JS regex eval to a server-side GraphQL mutation (previewPromptProtectionRule), and adds i18n keys for the channels endpoint count strings. The previous infinite-loop and toast-spam concerns are resolved: stable mutate/reset references are used in the useEffect deps, and handleError is intentionally omitted from the preview hook in favour of inline error display.

Confidence Score: 5/5

Safe 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

Filename Overview
internal/server/biz/prompt_protection_preview.go New Preview method on PromptProtectionRuleService; logic is correct but testText is unbounded in size
go.mod Upgrades regexp2 v1 → v2.0.0-beta.2 (pre-release); used for all prompt protection regex matching
frontend/src/features/prompt-protection-rules/components/rules-action-dialog.tsx Replaces client-side JS regex preview with server-side API call; stable function refs fix the prior infinite-loop concern
frontend/src/features/prompt-protection-rules/data/rules.ts Adds usePreviewPromptProtectionRule hook without handleError, keeping inline-only error display as intended
internal/server/gql/prompt_protection_rule.graphql Adds PromptProtectionRulePreviewInput / PromptProtectionRulePreviewResult types and previewPromptProtectionRule mutation; schema looks correct
internal/server/gql/prompt_protection_rule.resolvers.go PreviewPromptProtectionRule resolver correctly maps GQL input to biz layer and returns result
frontend/src/features/channels/components/channels-endpoints-dialog.tsx Replaces hardcoded "X resolved"/"X configured" strings with i18n translation keys; straightforward
internal/server/biz/prompt_protection_rule_test.go Adds three Preview tests covering mask, reject, and invalid-pattern cases; good coverage

Sequence Diagram

sequenceDiagram
    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)
Loading

Reviews (2): Last reviewed commit: "feat: align endpoint i18n and prompt pro..." | Re-trigger Greptile

greptile-apps[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +21 to +52
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
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
result, err = re.Replace(input.TestText, input.Settings.Replacement, -1, -1)
result, err = re.Replace(input.TestText, input.Settings.Replacement, 0, -1)

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +306 to 320
{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')}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@looplj looplj merged commit 3be2fa8 into unstable Apr 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature/功能]: 提示词保护功能支持配置忽略大小写

1 participant