Skip to content

OLS-3864,OLS-3865: sanitize LLM analysis options before CRD write - #442

Open
tremes wants to merge 1 commit into
openshift:mainfrom
tremes:ols-3864-5
Open

OLS-3864,OLS-3865: sanitize LLM analysis options before CRD write#442
tremes wants to merge 1 commit into
openshift:mainfrom
tremes:ols-3864-5

Conversation

@tremes

@tremes tremes commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Add two-layer defense against LLM output that violates CRD constraints:

  1. Schema enforcement: add maxLength to all string fields in AnalysisOutputSchema and MinimalAnalysisOutputSchema so the LLM is told the limits upfront.

  2. Go-side sanitization: add sanitizeAnalysisOptions() called from Analyze() that zeros per-option Diagnosis structs with empty required fields (prevents MinLength rejection) and truncates strings exceeding CRD MaxLength limits (prevents Too Long rejection).

Both the JSON schemas and the sanitization function share the same maxLen* constants to keep limits in sync.

Add TestSchemasCoverCRDMaxLength drift test to catch future divergence between CRD and LLM schema maxLength values.

Assisted-by: Claude Code:claude-opus-4-6

Add two-layer defense against LLM output that violates CRD constraints:

1. Schema enforcement: add maxLength to all string fields in
   AnalysisOutputSchema and MinimalAnalysisOutputSchema so the LLM is
   told the limits upfront.

2. Go-side sanitization: add sanitizeAnalysisOptions() called from
   Analyze() that zeros per-option Diagnosis structs with empty required
   fields (prevents MinLength rejection) and truncates strings exceeding
   CRD MaxLength limits (prevents Too Long rejection).

Both the JSON schemas and the sanitization function share the same
maxLen* constants to keep limits in sync.

Add TestSchemasCoverCRDMaxLength drift test to catch future divergence
between CRD and LLM schema maxLength values.

Signed-off-by: Tomáš Remeš <tremes@redhat.com>
Assisted-by: Claude Code:claude-opus-4-6
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Aug 12, 2026
@openshift-ci-robot

openshift-ci-robot commented Aug 12, 2026

Copy link
Copy Markdown

@tremes: This pull request references OLS-3864 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "5.0.0" version, but no target version was set.

This pull request references OLS-3865 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Add two-layer defense against LLM output that violates CRD constraints:

  1. Schema enforcement: add maxLength to all string fields in AnalysisOutputSchema and MinimalAnalysisOutputSchema so the LLM is told the limits upfront.

  2. Go-side sanitization: add sanitizeAnalysisOptions() called from Analyze() that zeros per-option Diagnosis structs with empty required fields (prevents MinLength rejection) and truncates strings exceeding CRD MaxLength limits (prevents Too Long rejection).

Both the JSON schemas and the sanitization function share the same maxLen* constants to keep limits in sync.

Add TestSchemasCoverCRDMaxLength drift test to catch future divergence between CRD and LLM schema maxLength values.

Assisted-by: Claude Code:claude-opus-4-6

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of incomplete remediation diagnoses by removing invalid per-option content.
    • Limited oversized remediation details, including diagnoses, plans, rollback steps, verification data, and RBAC justifications.
    • Ensured analysis outputs consistently follow configured length limits.
  • Tests

    • Added coverage for incomplete diagnoses and truncation of oversized fields.
    • Added validation to keep analysis schemas aligned with enforced length constraints.

Walkthrough

Changes

Analysis output limits

Layer / File(s) Summary
Schema length constraints
controller/agenticrun/schemas.go
Formatted default and minimal schemas now enforce maximum lengths for analysis output fields.
Runtime option sanitization
controller/agenticrun/sandbox_agent.go
Analyze clears incomplete per-option diagnoses and truncates oversized remediation option fields before returning output.
Sanitization and schema validation
controller/agenticrun/sandbox_agent_test.go, controller/agenticrun/schema_crd_drift_test.go
Tests cover diagnosis clearing, field truncation, and recursive CRD-to-LLM schema length coverage.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: sanitizing LLM analysis options before writing them to the CRD.
Description check ✅ Passed The description accurately explains schema limits, Go-side sanitization, shared constants, and drift testing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

@openshift-ci
openshift-ci Bot requested review from harche and xrajesh August 12, 2026 12:29
@openshift-ci

openshift-ci Bot commented Aug 12, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign joshuawilson for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
controller/agenticrun/sandbox_agent.go (1)

119-124: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Sanitize the top-level diagnosis before returning it.

Line 119 sanitizes only resp.Options. If actionRequired is false, an oversized resp.Diagnosis.Summary or resp.Diagnosis.RootCause reaches AnalysisOutput unchanged. The CRD limits can then reject the status patch.

Proposed fix
 sanitizeAnalysisOptions(log, run.Name, resp.Options)

-if resp.Diagnosis != nil && (resp.Diagnosis.Summary == "" || resp.Diagnosis.RootCause == "") {
-	log.Info("ignoring empty top-level diagnosis (per-option diagnoses used instead)", "run", run.Name)
-	resp.Diagnosis = nil
+if resp.Diagnosis != nil {
+	if resp.Diagnosis.Summary == "" || resp.Diagnosis.RootCause == "" {
+		log.Info("ignoring empty top-level diagnosis (per-option diagnoses used instead)", "run", run.Name)
+		resp.Diagnosis = nil
+	} else {
+		resp.Diagnosis.Summary = truncate(resp.Diagnosis.Summary, maxLenDiagnosisSummary)
+		resp.Diagnosis.RootCause = truncate(resp.Diagnosis.RootCause, maxLenDiagnosisRootCause)
+	}
 }
🤖 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 `@controller/agenticrun/sandbox_agent.go` around lines 119 - 124, Sanitize the
top-level diagnosis before returning the analysis output, not only resp.Options.
Update the flow around sanitizeAnalysisOptions and the resp.Diagnosis
empty-check to apply the existing diagnosis sanitization and enforce CRD length
limits on Summary and RootCause, including when actionRequired is false, while
preserving the current nil handling.
🤖 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 `@controller/agenticrun/sandbox_agent.go`:
- Around line 338-345: Update truncate to limit Unicode characters using
rune-aware length and slicing, preserving unchanged strings within the limit;
add coverage for non-ASCII input. Before storing the top-level resp.Diagnosis in
AnalysisResult.Status, pass it through the same truncation limit used for
diagnosis text.

---

Outside diff comments:
In `@controller/agenticrun/sandbox_agent.go`:
- Around line 119-124: Sanitize the top-level diagnosis before returning the
analysis output, not only resp.Options. Update the flow around
sanitizeAnalysisOptions and the resp.Diagnosis empty-check to apply the existing
diagnosis sanitization and enforce CRD length limits on Summary and RootCause,
including when actionRequired is false, while preserving the current nil
handling.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b2f045d0-73ec-491d-b9f1-f42302785d47

📥 Commits

Reviewing files that changed from the base of the PR and between ab7f51a and 8c6b6de.

📒 Files selected for processing (4)
  • controller/agenticrun/sandbox_agent.go
  • controller/agenticrun/sandbox_agent_test.go
  • controller/agenticrun/schema_crd_drift_test.go
  • controller/agenticrun/schemas.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift/lightspeed-agentic-sandbox (manual)

Comment on lines +338 to +345
// truncate returns s unchanged if its byte length is within max,
// otherwise it returns the first max bytes.
func truncate(s string, max int) string {
if len(s) <= max {
return s
}
return s[:max]
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- truncate definition and usages ---'
rg -n -C 6 '\btruncate\(|func truncate\b' controller/agenticrun
printf '%s\n' '--- relevant sanitizer and response construction ---'
rg -n -C 12 'sanitize|AnalysisOutput|Diagnosis|Options|maxLength|RootCause|rootCause' controller/agenticrun api
printf '%s\n' '--- tests ---'
rg -n -C 8 'truncate|Unicode|UTF|rune|analysis' controller/agenticrun/*_test.go api

Repository: openshift/lightspeed-agentic-operator

Length of output: 50394


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- implementation ---'
rg -n -C 18 'func truncate|truncate\(' controller/agenticrun/sandbox_agent.go
printf '%s\n' '--- analysis flow ---'
rg -n -C 20 'func .*Analyze|AnalysisOutput|analysisResponse|sanitize|Options' controller/agenticrun/sandbox_agent.go
printf '%s\n' '--- CRD length annotations ---'
rg -n -C 4 'MaxLength|maxLength|RootCause|Summary|Description' api/v1alpha1
printf '%s\n' '--- truncation test ---'
sed -n '455,515p' controller/agenticrun/sandbox_agent_test.go

Repository: openshift/lightspeed-agentic-operator

Length of output: 50393


🏁 Script executed (no clone):

#!/bin/bash
set -e
python3 - <<'PY'
samples = ["é" * 10, "🙂" * 10, "a🙂b"]
for s in samples:
    for max_len in (1, 2, 5, 10):
        raw = s.encode("utf-8")
        prefix = raw[:max_len]
        try:
            decoded = prefix.decode("utf-8")
            valid = True
            chars = len(decoded)
        except UnicodeDecodeError:
            decoded = None
            valid = False
            chars = None
        print({
            "input_chars": len(s),
            "input_bytes": len(raw),
            "max": max_len,
            "byte_prefix_valid_utf8": valid,
            "byte_prefix_chars": chars,
        })
PY

Length of output: 1408


🏁 Script executed (no clone):

#!/bin/bash
set -e
python3 - <<'PY'
samples = ["é" * 10, "🙂" * 10, "a🙂b"]
for s in samples:
    for max_len in (1, 2, 5, 10):
        raw = s.encode("utf-8")
        prefix = raw[:max_len]
        try:
            decoded = prefix.decode("utf-8")
            valid = True
            chars = len(decoded)
        except UnicodeDecodeError:
            valid = False
            chars = None
        print({
            "input_chars": len(s),
            "input_bytes": len(raw),
            "max": max_len,
            "byte_prefix_valid_utf8": valid,
            "byte_prefix_chars": chars,
        })
PY

Length of output: 1408


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- analysis status types ---'
sed -n '1,260p' api/v1alpha1/agenticrun_status_types.go
printf '%s\n' '--- analysis result types ---'
sed -n '1,180p' api/v1alpha1/analysisresult_types.go
printf '%s\n' '--- generated CRD analysis schema references ---'
rg -n -C 3 '"(title|summary|rootCause|description|command|justification)":|maxLength:' config/crd/bases config/crd 2>/dev/null | head -n 240
printf '%s\n' '--- existing truncation assertions ---'
sed -n '465,510p' controller/agenticrun/sandbox_agent_test.go
printf '%s\n' '--- top-level diagnosis consumers ---'
rg -n -C 10 'output\.Diagnosis|Analysis\.Diagnosis|Diagnosis:' controller api

Repository: openshift/lightspeed-agentic-operator

Length of output: 50393


🏁 Script executed (no clone):

#!/bin/bash
set -e
cat > /tmp/json_utf8_probe.go <<'EOF'
package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	s := "🙂🙂"
	b := []byte(s)[:5]
	out, err := json.Marshal(struct {
		Value string `json:"value"`
	}{string(b)})
	fmt.Printf("raw_valid=%t json=%s err=%v\n", json.Valid(out), out, err)
}
EOF
go run /tmp/json_utf8_probe.go
rm -f /tmp/json_utf8_probe.go

Length of output: 176


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- exact analysis model definitions ---'
rg -n 'type (DiagnosisResult|RemediationOption|RemediationPlan|ProposedAction|VerificationPlan|RBAC)|MaxLength' api/v1alpha1/analysisresult_types.go api/v1alpha1/*.go
printf '%s\n' '--- definitions context ---'
for pat in 'type DiagnosisResult' 'type RemediationOption' 'type RemediationPlan' 'type ProposedAction'; do
  line=$(rg -n "$pat" api/v1alpha1 | head -n 1 | cut -d: -f2)
  file=$(rg -l "$pat" api/v1alpha1 | head -n 1)
  if [ -n "$file" ] && [ -n "$line" ]; then
    start=$((line-8)); [ "$start" -lt 1 ] && start=1
    end=$((line+100))
    echo "--- $pat in $file ---"
    sed -n "${start},${end}p" "$file"
  fi
done
printf '%s\n' '--- analysis result creation ---'
rg -n -C 25 'AnalysisResultStatus|createAnalysis|CreateAnalysis|result :=.*Analysis|Diagnosis' controller/agenticrun --glob '*.go' | head -n 260

Repository: openshift/lightspeed-agentic-operator

Length of output: 42948


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- non-test analysis result writes ---'
rg -n -C 18 'AnalysisResult\{|Status:.*AnalysisResultStatus|Diagnosis:.*output|Diagnosis:.*analysis|output\.Diagnosis|analysis\.Diagnosis' controller/agenticrun --glob '*.go' --glob '!**/*_test.go'
printf '%s\n' '--- all AnalysisOutput field assignments ---'
rg -n -C 10 'AnalysisOutput\{|\.Diagnosis|Diagnosis:' controller/agenticrun --glob '*.go' --glob '!**/*_test.go'

Repository: openshift/lightspeed-agentic-operator

Length of output: 23603


Truncate strings by Unicode characters, and sanitize the top-level diagnosis.

len and s[:max] operate on bytes. Non-ASCII output can be split and serialized as , while retaining fewer characters than the CRD limit. Use rune-aware truncation and add a Unicode test. Apply the same limits to the top-level resp.Diagnosis before storing it in AnalysisResult.Status.

🤖 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 `@controller/agenticrun/sandbox_agent.go` around lines 338 - 345, Update
truncate to limit Unicode characters using rune-aware length and slicing,
preserving unchanged strings within the limit; add coverage for non-ASCII input.
Before storing the top-level resp.Diagnosis in AnalysisResult.Status, pass it
through the same truncation limit used for diagnosis text.

@openshift-ci

openshift-ci Bot commented Aug 12, 2026

Copy link
Copy Markdown

@tremes: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

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

Labels

jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants