Skip to content

feat(gateway): add centralized AI prompt validation#247

Open
karuna-1 wants to merge 5 commits into
AnkanMisra:mainfrom
karuna-1:feat/issue-244-ai-prompt-validation
Open

feat(gateway): add centralized AI prompt validation#247
karuna-1 wants to merge 5 commits into
AnkanMisra:mainfrom
karuna-1:feat/issue-244-ai-prompt-validation

Conversation

@karuna-1

@karuna-1 karuna-1 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Related Issue

Fixes #244

Summary

This PR improves input validation for AI summarize requests by introducing a centralized validation helper that's used before requests are sent to the AI provider.

What Changed

  • Added a shared validatePrompt() helper to avoid duplicated validation logic.

  • Rejects empty and whitespace-only prompts.

  • Rejects prompts longer than 4000 characters.

  • Detects and blocks common prompt injection phrases.

  • Logs rejected prompts for easier monitoring and debugging.

  • Updated both:

    • handleSummarize()
    • CacheMiddleware()
      to use the shared validation helper.
  • Added unit tests covering:

    • Valid prompts
    • Empty prompts
    • Whitespace-only prompts
    • Oversized prompts
    • Common prompt injection attempts

Testing

  • Ran go test ./...
  • All tests passed successfully.

Notes

While working on this issue, I noticed the current codebase uses a provider abstraction (Generate(...)) instead of the callLLM(...) example shown in the issue description. To keep the implementation consistent with the existing architecture, I applied the validation at the request layer without changing the provider interface.

Summary by CodeRabbit

  • Bug Fixes
    • Added unified prompt validation for summarization and cached requests. Empty input, overly long text, or disallowed content now returns 400 Invalid request with a clear validation message.
  • UI/Schema Updates
    • Enforced a 4,000-character maximum for summarize input in the web form and in the API specification.
  • Tests
    • Added test coverage for valid, empty/whitespace, length-exceeding, and disallowed-content prompts.

@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

@karuna-1 is attempting to deploy a commit to the ankanmisra's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

Hi @karuna-1, thanks for opening this PR.

Every contribution helps MicroAI-Paygate grow. If you find the project useful, consider starring the repository — it helps others discover it.

Star MicroAI-Paygate on GitHub

Looking forward to reviewing this PR.

@github-actions github-actions Bot added go Pull requests that update go code type:testing Tests, coverage, fixtures, or validation-only work. labels Jul 4, 2026
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3d3e5da5-3976-4fad-905d-fac8febe46fc

📥 Commits

Reviewing files that changed from the base of the PR and between e4d15d5 and 8f75ac7.

📒 Files selected for processing (4)
  • gateway/main.go
  • gateway/openapi.yaml
  • gateway/prompt_validation.go
  • web/src/components/summarize-form.tsx
✅ Files skipped from review due to trivial changes (1)
  • web/src/components/summarize-form.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • gateway/prompt_validation.go
  • gateway/main.go

📝 Walkthrough

Walkthrough

Adds shared prompt validation with a 4000-character limit and injection-pattern checks, wires it into gateway request handling, and updates the request schema and form input to match. Adds unit coverage for the validator.

Changes

Prompt Validation

Layer / File(s) Summary
Validation rules
gateway/prompt_validation.go
Adds MaxPromptLength, the disallowed-pattern list, and validatePrompt for empty, over-length, and injection-like prompts.
Gateway and form wiring
gateway/cache.go, gateway/main.go, gateway/openapi.yaml, web/src/components/summarize-form.tsx
Replaces empty-string checks with validatePrompt, returns structured 400 responses, and adds 4000-character limits to the API schema and textarea input.
Validation tests
gateway/prompt_validation_test.go
Adds table-driven tests covering valid, empty, whitespace-only, over-length, and injection-like prompts.

Estimated code review effort: 2 (Simple) | ~12 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant CacheMiddleware
  participant handleSummarize
  participant validatePrompt

  Client->>CacheMiddleware: submit summarize request
  CacheMiddleware->>validatePrompt: validatePrompt(req.Text)
  validatePrompt-->>CacheMiddleware: error or nil
  CacheMiddleware->>handleSummarize: forward validated request
  handleSummarize->>validatePrompt: validatePrompt(req.Text)
  validatePrompt-->>handleSummarize: error or nil
  handleSummarize-->>Client: 400 or summarize response
Loading

Possibly related issues

  • #256 — updates /api/ai/summarize validation and the gateway/openapi.yaml text-length constraint in the same area.
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is useful, but it does not follow the required template and omits several required sections like type, affected areas, checklist, and verification details. Rewrite the PR description using the repository template and fill in all required sections, especially type, affected areas, checklist, and exact verification commands.
Linked Issues check ⚠️ Warning [#244] The PR covers prompt length checks, injection blocking, and logging, but it does not enforce a max token budget or add the requested README documentation. Enforce a hard token budget on every LLM call and document the bounded per-request cost in README.md to fully satisfy #244.
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: centralized AI prompt validation in the gateway.
Out of Scope Changes check ✅ Passed The OpenAPI maxLength and web textarea limit are aligned with the prompt validation work and do not appear unrelated.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
gateway/prompt_validation_test.go (1)

10-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a boundary case and coverage for other injection patterns.

No test asserts that a prompt of exactly MaxPromptLength characters passes (only MaxPromptLength+1 is tested), and only 1 of the 5 injectionPatterns entries is exercised.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gateway/prompt_validation_test.go` around lines 10 - 41, Update
TestValidatePrompt to cover the missing boundary and broader injection coverage:
add a case in TestValidatePrompt that uses exactly MaxPromptLength characters
and expects success, and expand the prompt injection coverage so ValidatePrompt
exercises the other injectionPatterns entries in
gateway/prompt_validation_test.go. Use the existing MaxPromptLength constant and
the prompt validation test table to keep the cases aligned with the validation
logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@gateway/prompt_validation.go`:
- Around line 11-17: The broad "you are now" entry in injectionPatterns is
causing false positives in prompt validation. Tighten the matching in
prompt_validation.go by replacing the plain substring check with a more specific
rule in injectionPatterns/prompt validation logic, such as word-boundary or
regex-based matching, or require nearby role-assignment context before flagging
it. Keep the existing blacklist approach, but make this pattern less aggressive
so ordinary text is not rejected by the prompt validation path.
- Around line 25-32: The prompt length check in prompt validation is using byte
length instead of character count, so adjust the logic in the prompt validation
function to count Unicode characters rather than raw bytes. Update the
`len(prompt)` usage in the rejection check and the error formatting to use a
rune-based count so the `MaxPromptLength` limit matches the stated “characters”
semantics. Keep the existing logging and error path in `prompt_validation.go`,
but ensure the reported “received” value reflects character count consistently.

---

Nitpick comments:
In `@gateway/prompt_validation_test.go`:
- Around line 10-41: Update TestValidatePrompt to cover the missing boundary and
broader injection coverage: add a case in TestValidatePrompt that uses exactly
MaxPromptLength characters and expects success, and expand the prompt injection
coverage so ValidatePrompt exercises the other injectionPatterns entries in
gateway/prompt_validation_test.go. Use the existing MaxPromptLength constant and
the prompt validation test table to keep the cases aligned with the validation
logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d77e72ef-10c3-46bf-898e-cf507b23c312

📥 Commits

Reviewing files that changed from the base of the PR and between 33e7fc3 and e4d15d5.

📒 Files selected for processing (4)
  • gateway/cache.go
  • gateway/main.go
  • gateway/prompt_validation.go
  • gateway/prompt_validation_test.go

Comment thread gateway/prompt_validation.go
Comment thread gateway/prompt_validation.go Outdated
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
microai-paygate Ready Ready Preview, Comment Jul 6, 2026 1:57pm

@AnkanMisra AnkanMisra added bug Something isn't working level:intermediate Moderate scope requiring project familiarity or cross-file changes. triage Needs maintainer triage. type:bug A defect or regression in existing behavior. type:security Security, abuse resistance, secret safety, or payment integrity work. labels Jul 5, 2026 — with ChatGPT Codex Connector
@AnkanMisra

Copy link
Copy Markdown
Owner

@codex review the pr

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7fe5b9809

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread gateway/prompt_validation.go
Comment thread gateway/prompt_validation.go Outdated
Comment thread gateway/main.go Outdated
Comment thread gateway/main.go Outdated
@github-actions github-actions Bot added the TypeScript TypeScript code label Jul 6, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation type:docs Documentation, API docs, examples, or contributor docs. labels Jul 6, 2026
@karuna-1

karuna-1 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Hi! Could you please add the GSSoC 2026 label to this PR if it's eligible? Thank you!

@AnkanMisra AnkanMisra added the gssoc:approved Approved for GSSoC contribution label Jul 6, 2026
@AnkanMisra

Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a3d27cad5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread gateway/main.go Outdated
Comment thread web/src/components/summarize-form.tsx
Comment thread gateway/cache.go
Comment thread gateway/prompt_validation.go
@AnkanMisra

Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a3d27cad5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


lower := strings.ToLower(prompt)
for _, pattern := range injectionPatterns {
if strings.Contains(lower, pattern) {

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.

P1 Badge Avoid hard-blocking quoted prompt-injection text

Because this endpoint summarizes arbitrary user-provided documents, this substring check rejects any otherwise valid text that merely quotes or discusses one of these phrases, such as an article about prompt injection containing ignore all previous instructions. In the browser flow that user is asked to sign the x402 challenge and then receives a 400 instead of a summary, so consider treating the input as quoted content in the AI prompt or using a narrower validation mechanism rather than blocking raw substrings.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the feedback. I agree that simple substring matching can reject some benign quoted content. For this PR, I kept the validation intentionally simple and centralized the existing behavior without changing the overall security policy. A more context-aware validation approach would be a good follow-up improvement.

onChange={(e) => setInput(e.target.value)}
placeholder={`Paste any text. The summary returns after a ${DISPLAY_CHAIN_NAME} signature.`}
rows={9}
maxLength={4000}

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.

P2 Badge Align textarea limit with backend character counting

For prompts containing non-BMP characters such as emoji, this browser limit is enforced in UTF-16 code units while validatePrompt counts Unicode runes with utf8.RuneCountInString, so the web app blocks or truncates inputs that the gateway contract would accept, e.g. a 3,000-emoji prompt is under the 4,000-character server limit but exceeds this textarea limit. Count/enforce the same units in the client or avoid relying on the native maxLength for the server's character limit.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the feedback. I used the native maxLength={4000} to enforce the documented API limit with minimal client-side logic. I agree that matching the backend's rune-based counting exactly would handle Unicode edge cases more accurately, but I kept this PR focused on the existing validation contract. A rune-aware client-side counter could be considered as a follow-up enhancement.

Comment thread gateway/main.go Outdated
// Validate text is not empty (also validated in cache middleware, but needed here for non-cached requests)
if req.Text == "" {
c.JSON(400, gin.H{"error": "Invalid request", "message": "text field cannot be empty"})
if err := validatePrompt(req.Text); err != 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.

P2 Badge Validate prompts before consuming the nonce

On cache misses or when CACHE_ENABLED=false, the signed retry reaches verifyPayment before this new prompt validation runs; the verifier claims the nonce once the signature is valid, so an over-limit or newly disallowed prompt is rejected with 400 after its payment context has already been burned. API clients that retry the same signed headers then get nonce_already_used without ever receiving a summary, so parse and validate the body before calling the verifier on this path, matching the cache middleware ordering.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation go Pull requests that update go code gssoc:approved Approved for GSSoC contribution level:intermediate Moderate scope requiring project familiarity or cross-file changes. triage Needs maintainer triage. type:bug A defect or regression in existing behavior. type:docs Documentation, API docs, examples, or contributor docs. type:security Security, abuse resistance, secret safety, or payment integrity work. type:testing Tests, coverage, fixtures, or validation-only work. TypeScript TypeScript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] No input validation on AI prompt endpoint - prompt injection and excessive token consumption possible

2 participants