Skip to content

Fix/codeql remediation pr455#458

Merged
ginccc merged 9 commits into
mainfrom
fix/codeql-remediation-pr455
Apr 27, 2026
Merged

Fix/codeql remediation pr455#458
ginccc merged 9 commits into
mainfrom
fix/codeql-remediation-pr455

Conversation

@ginccc

@ginccc ginccc commented Apr 26, 2026

Copy link
Copy Markdown
Member

fix(security): remediate CodeQL findings — log injection, array bounds, arithmetic overflow

Summary

Remediates 81 CodeQL findings flagged during the PR #455 scan (pre-existing in codebase, surfaced by large diff).

Severity Count Category Fix
High 4 Array index / arithmetic overflow Bounds checks, long arithmetic, pagination clamping
Medium 77 Log injection (CWE-117) Centralized LogSanitizer.sanitize() utility

Changes

New: LogSanitizer utility

  • ai.labs.eddi.utils.LogSanitizer — single static sanitize(String) method
  • Two-pass approach: replaces \r\n\t_ (preserving readability), then strips remaining control characters (U+0000–U+001F, U+007F)
  • Null-safe: returns "null" string for null input
  • Replaces 6 duplicated private sanitizeForLog() methods that were scattered across the codebase

High severity fixes

CronDescriber — Added v >= 0 lower-bound guard in formatSet() to prevent ArrayIndexOutOfBoundsException from negative values. Falls back to String.valueOf(v) gracefully.

RestConversationStore — Pagination parameter sanitization:

  • index: clamped to >= 0 (null/negative → 0)
  • limit: clamped to 1–100 (null → 20, >100 → 100)
  • Added Integer.MAX_VALUE overflow guard on the index increment loop

DescriptorStore — Promoted index * effectiveLimit skip calculation to long arithmetic with Math.min(skipLong, Integer.MAX_VALUE) clamp to prevent integer multiplication overflow.

Medium severity — log injection (19 files)

Applied sanitize() to all user-controlled values in log statements: tenantId, keyName, conversationId, agentId, userId, toolName, serverName, kbId, storeType, eventType, eventId, etc.

Consolidated files (removed duplicated sanitizeForLog()):

  • InMemoryConversationCoordinator, NatsConversationCoordinator
  • RestAgentEngineStreaming, RestCoordinatorAdmin
  • InMemoryTenantQuotaStore, RestAgentGroupStore

Tests

Test class New tests What's covered
LogSanitizerTest 23 Null, CR/LF/tab replacement, control char stripping, Unicode passthrough, attack patterns (fake log entries, CRLF injection, ANSI escape), two-pass ordering
CronDescriberExtendedTest 4 formatSet negative value (single + multi-element), boundary at labels.length, valid max index
RestConversationStoreTest 5 Null index/limit clamping, negative index, excessive limit >100, valid limit passthrough

74 tests pass, 0 failures.

Not in scope

  • e.getMessage() in log statements — JVM-generated, not user-controlled
  • Micrometer counter tag values — separate concern from log injection (CWE-117)

Summary by CodeRabbit

  • Bug Fixes
    • Sanitized log output across many components to prevent log injection; fixed pagination integer-overflow and array-indexing edge cases; normalized pagination inputs to sane defaults and bounds.
  • Documentation
    • Added changelog entry describing the remediation and verification.
  • Tests
    • Added comprehensive tests for sanitization, pagination handling, and edge-case formatting.

ginccc added 5 commits April 26, 2026 14:55
…verflow, log injection

High severity (4 findings):
- CronDescriber: add v >= 0 guard in formatSet() (2 findings)
- RestConversationStore: clamp pagination params, guard index++ overflow (1 finding)
- DescriptorStore: use long arithmetic for skip calculation (1 finding, 30 annotations)

Medium severity (77 findings):
- Create LogSanitizer utility (CWE-117 log injection prevention)
- Wrap user-controlled values with sanitize() across 13 files:
  RestSecretStore, RagIngestionService, ExpressionProvider,
  ToolRateLimiter, ToolCostTracker, ToolCacheService,
  McpToolProviderManager, LlmTask, EmbeddingStoreFactory,
  ConversationSummarizer, ChatModelRegistry, AgentOrchestrator,
  RestSlackWebhook
…gSanitizer.sanitize()

Replaced private sanitizeForLog() methods in 6 files with the centralized
LogSanitizer.sanitize() utility. The centralized version is more thorough:
handles tabs and all control characters (0x00-0x1F, 0x7F), not just CR/LF.

Files consolidated:
- InMemoryConversationCoordinator
- NatsConversationCoordinator
- RestAgentEngineStreaming
- RestCoordinatorAdmin
- InMemoryTenantQuotaStore
- RestAgentGroupStore

Also fixed Javadoc encoding issue in LogSanitizer (em-dash in hex range)
and added braces to if-statement for project style consistency.
@ginccc ginccc requested a review from rolandpickl as a code owner April 26, 2026 18:09
@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Centralizes log sanitization via a new LogSanitizer.sanitize utility, replaces many file-local sanitizers, tightens pagination offset math to avoid integer overflow, adds array-bounds checks in cron formatting, and introduces/updates unit tests and a changelog entry. (47 words)

Changes

Cohort / File(s) Summary
Utility & Tests
src/main/java/ai/labs/eddi/utils/LogSanitizer.java, src/test/java/ai/labs/eddi/utils/LogSanitizerTest.java
Adds LogSanitizer.sanitize(String) and comprehensive unit tests validating null handling, newline/tab → underscore replacement, control-character stripping, and attack-pattern neutralization.
REST / Storage Sanitization
src/main/java/ai/labs/eddi/configs/groups/rest/RestAgentGroupStore.java, src/main/java/ai/labs/eddi/configs/rag/rest/RestRagIngestion.java, src/main/java/ai/labs/eddi/secrets/rest/RestSecretStore.java, src/main/java/ai/labs/eddi/integrations/slack/rest/RestSlackWebhook.java
Replaces inline sanitizers with LogSanitizer.sanitize(...) for logged identifiers (resourceId, ragConfigId/effectiveKbId/documentName, tenantId/keyName, Slack event fields).
Engine & Coordinator Sanitization
src/main/java/ai/labs/eddi/engine/internal/RestAgentEngineStreaming.java, src/main/java/ai/labs/eddi/engine/internal/RestCoordinatorAdmin.java, src/main/java/ai/labs/eddi/engine/runtime/internal/InMemoryConversationCoordinator.java, src/main/java/ai/labs/eddi/engine/runtime/internal/NatsConversationCoordinator.java, src/main/java/ai/labs/eddi/engine/tenancy/InMemoryTenantQuotaStore.java
Removes local sanitizeForLog helpers; uses shared sanitize(...) for conversation IDs, entry IDs, dead-letter subjects, tenant IDs in logs.
LLM Module & Tooling Sanitization
src/main/java/ai/labs/eddi/modules/llm/impl/AgentOrchestrator.java, src/main/java/ai/labs/eddi/modules/llm/impl/ChatModelRegistry.java, src/main/java/ai/labs/eddi/modules/llm/impl/ConversationSummarizer.java, src/main/java/ai/labs/eddi/modules/llm/impl/EmbeddingStoreFactory.java, src/main/java/ai/labs/eddi/modules/llm/impl/LlmTask.java, src/main/java/ai/labs/eddi/modules/llm/impl/McpToolProviderManager.java, src/main/java/ai/labs/eddi/modules/llm/tools/...
Sanitizes IDs, secret refs, conversation IDs, store/connection identifiers, tool names, server/client names; adds debug guards (LOGGER.isDebugEnabled()) and switches to debugf where applicable.
Tooling: Cache / Rate / Cost
src/main/java/ai/labs/eddi/modules/llm/tools/ToolCacheService.java, src/main/java/ai/labs/eddi/modules/llm/tools/ToolCostTracker.java, src/main/java/ai/labs/eddi/modules/llm/tools/ToolRateLimiter.java
Sanitizes toolName and conversationId in logs, guards debug logs, and converts string concatenation formatting to logger-formatted calls.
RAG & NLP Sanitization
src/main/java/ai/labs/eddi/modules/rag/RagIngestionService.java, src/main/java/ai/labs/eddi/modules/nlp/expressions/utilities/ExpressionProvider.java, src/main/java/ai/labs/eddi/configs/rag/rest/RestRagIngestion.java
Applies sanitize(...) to kbId, documentName, and expression in log messages.
Pagination / Array-Bounds Fixes
src/main/java/ai/labs/eddi/datastore/DescriptorStore.java, src/main/java/ai/labs/eddi/engine/runtime/internal/CronDescriber.java, src/main/java/ai/labs/eddi/engine/memory/rest/RestConversationStore.java
Prevents integer overflow in pagination by using long for index*limit and capping to Integer.MAX_VALUE; adds bounds checks before indexing cron label arrays; clamps/normalizes pagination inputs (index/limit) and prevents index overflow during paging loops.
Tests & Changelog
src/test/java/ai/labs/eddi/engine/internal/RestAgentEngineStreamingTest.java, src/test/java/ai/labs/eddi/engine/memory/rest/RestConversationStoreTest.java, src/test/java/ai/labs/eddi/engine/runtime/internal/CronDescriberExtendedTest.java, docs/changelog.md
Updates tests to use centralized sanitizer; adds pagination and cron bounds tests; documents changes in changelog entry for PR #455.
Small Rest & Coordinator tweaks
src/main/java/ai/labs/eddi/engine/internal/RestCoordinatorAdmin.java, src/main/java/ai/labs/eddi/configs/groups/rest/RestAgentGroupStore.java, src/main/java/ai/labs/eddi/engine/internal/RestAgentEngineStreaming.java
Removes now-dead private sanitizer methods and replaces usages with static import of LogSanitizer.sanitize.

Sequence Diagram(s)

(Skipped — changes are widespread logging and defensive fixes without a single new multi-component sequential flow.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • rolandpickl

Poem

"🐰 I hopped through logs both far and near,
I swapped out helpers to make them clear;
Newlines tamed, odd controls are gone,
Bounds checked tight from dusk till dawn.
A little rabbit seals the log—safe and dear."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix/codeql remediation pr455' directly references the main purpose of the PR: remediating 81 CodeQL findings from PR #455, which is the primary change in the changeset.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/codeql-remediation-pr455

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 and usage tips.

@ginccc ginccc requested review from Copilot and removed request for rolandpickl April 26, 2026 18:09
@github-actions

github-actions Bot commented Apr 26, 2026

Copy link
Copy Markdown

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Comment thread src/main/java/ai/labs/eddi/modules/rag/RagIngestionService.java Fixed

Copilot AI 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.

Pull request overview

Remediates CodeQL findings across the EDDI codebase by centralizing log value sanitization (CWE-117) and adding guards against array bounds and integer overflow issues in pagination/skip calculations.

Changes:

  • Introduces LogSanitizer.sanitize(String) and applies it to user-controlled values across multiple log statements.
  • Fixes bounds/overflow issues in CronDescriber, RestConversationStore pagination, and DescriptorStore skip calculation.
  • Adds/updates unit tests to cover the sanitizer behavior and the bounds/overflow fixes.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/main/java/ai/labs/eddi/utils/LogSanitizer.java New centralized log-sanitization utility.
src/test/java/ai/labs/eddi/utils/LogSanitizerTest.java New tests covering sanitizer behavior and attack patterns.
src/main/java/ai/labs/eddi/engine/runtime/internal/CronDescriber.java Adds v >= 0 guard to prevent negative index access.
src/test/java/ai/labs/eddi/engine/runtime/internal/CronDescriberExtendedTest.java Adds coverage for formatSet() negative/out-of-bounds cases (via reflection).
src/main/java/ai/labs/eddi/engine/memory/rest/RestConversationStore.java Clamps pagination params and guards index++ overflow.
src/test/java/ai/labs/eddi/engine/memory/rest/RestConversationStoreTest.java Adds tests for pagination parameter clamping behavior.
src/main/java/ai/labs/eddi/datastore/DescriptorStore.java Prevents index * limit overflow by using long and clamping.
src/main/java/ai/labs/eddi/secrets/rest/RestSecretStore.java Sanitizes tenant/key values in error logs.
src/main/java/ai/labs/eddi/modules/rag/RagIngestionService.java Sanitizes KB/document identifiers in logs.
src/main/java/ai/labs/eddi/modules/nlp/expressions/utilities/ExpressionProvider.java Sanitizes expression text in parse error logs.
src/main/java/ai/labs/eddi/modules/llm/tools/ToolRateLimiter.java Sanitizes tool name in rate-limit logs.
src/main/java/ai/labs/eddi/modules/llm/tools/ToolCostTracker.java Sanitizes tool/conversation identifiers in logs.
src/main/java/ai/labs/eddi/modules/llm/tools/ToolCacheService.java Sanitizes tool name in cache debug logs.
src/main/java/ai/labs/eddi/modules/llm/impl/McpToolProviderManager.java Sanitizes MCP server name/URL in discovery/connect logs.
src/main/java/ai/labs/eddi/modules/llm/impl/LlmTask.java Sanitizes conversation id in summary warning logs.
src/main/java/ai/labs/eddi/modules/llm/impl/EmbeddingStoreFactory.java Sanitizes store/config fields in embedding-store build logs.
src/main/java/ai/labs/eddi/modules/llm/impl/ConversationSummarizer.java Sanitizes conversation id in summary logs.
src/main/java/ai/labs/eddi/modules/llm/impl/ChatModelRegistry.java Sanitizes secret reference fields in eviction logs.
src/main/java/ai/labs/eddi/modules/llm/impl/AgentOrchestrator.java Sanitizes IDs/budget text in logs.
src/main/java/ai/labs/eddi/integrations/slack/rest/RestSlackWebhook.java Sanitizes Slack event type/id in debug logs.
src/main/java/ai/labs/eddi/engine/tenancy/InMemoryTenantQuotaStore.java Replaces local sanitization helper with centralized sanitizer.
src/main/java/ai/labs/eddi/engine/runtime/internal/NatsConversationCoordinator.java Replaces local sanitization helper with centralized sanitizer.
src/main/java/ai/labs/eddi/engine/runtime/internal/InMemoryConversationCoordinator.java Replaces local sanitization helper with centralized sanitizer.
src/main/java/ai/labs/eddi/engine/internal/RestCoordinatorAdmin.java Uses centralized sanitizer in errors/logging for dead-letter actions.
src/main/java/ai/labs/eddi/engine/internal/RestAgentEngineStreaming.java Uses centralized sanitizer for conversation id in logs.
src/test/java/ai/labs/eddi/engine/internal/RestAgentEngineStreamingTest.java Updates test to validate centralized sanitizer behavior.
src/main/java/ai/labs/eddi/configs/groups/rest/RestAgentGroupStore.java Removes custom sanitization logic in favor of centralized sanitizer.
docs/changelog.md Documents the remediation work and affected areas.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main/java/ai/labs/eddi/modules/llm/tools/ToolCostTracker.java Outdated
Comment thread src/main/java/ai/labs/eddi/utils/LogSanitizer.java Outdated
Comment thread src/main/java/ai/labs/eddi/modules/llm/tools/ToolCacheService.java Outdated
Comment thread src/main/java/ai/labs/eddi/modules/llm/tools/ToolCacheService.java Outdated
Comment thread src/main/java/ai/labs/eddi/modules/llm/tools/ToolCacheService.java Outdated
Comment thread src/main/java/ai/labs/eddi/modules/llm/tools/ToolCacheService.java
Comment thread src/main/java/ai/labs/eddi/modules/llm/tools/ToolCacheService.java
Comment thread src/main/java/ai/labs/eddi/engine/memory/rest/RestConversationStore.java Outdated

@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: 3

Caution

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

⚠️ Outside diff range comments (2)
src/main/java/ai/labs/eddi/integrations/slack/rest/RestSlackWebhook.java (1)

99-99: ⚠️ Potential issue | 🟡 Minor

Unsanitized header value in adjacent log site.

The timestamp parameter on line 99 originates from the attacker-controllable X-Slack-Request-Timestamp header (especially since signature verification just failed, so the request is by definition untrusted). This is the same CWE-117 class being remediated elsewhere in the PR; consider sanitizing it for consistency.

🛡️ Proposed fix
-            LOGGER.warnf("Slack signature verification failed (timestamp=%s)", timestamp);
+            LOGGER.warnf("Slack signature verification failed (timestamp=%s)", sanitize(timestamp));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ai/labs/eddi/integrations/slack/rest/RestSlackWebhook.java` at
line 99, The log call LOGGER.warnf("Slack signature verification failed
(timestamp=%s)", timestamp) in RestSlackWebhook uses the untrusted
X-Slack-Request-Timestamp header value; sanitize or validate the timestamp
before logging (e.g., canonicalize to a digits-only substring or strip
control/formatting characters and fall back to a safe placeholder) and then log
the sanitized value via LOGGER.warnf so the adjacent log site no longer emits
attacker-controlled characters.
src/main/java/ai/labs/eddi/engine/runtime/internal/NatsConversationCoordinator.java (1)

322-336: ⚠️ Potential issue | 🟡 Minor

Note: dead-letter JSON payload still embeds raw conversationId.

At line 326–327 the JSON payload is constructed with String.format(...) interpolating the raw conversationId. If conversationId contains a " or control character, the resulting payload will be malformed and consumers reading from the dead-letter stream may misparse it. Only failure.getMessage() has its quotes escaped. Consider escaping conversationId the same way (or building the payload with a proper JSON serializer) — this is independent of the log-injection scope of this PR but follows the same threat model.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/ai/labs/eddi/engine/runtime/internal/NatsConversationCoordinator.java`
around lines 322 - 336, The dead-letter payload in routeToDeadLetter currently
interpolates a raw conversationId into the JSON string (DEAD_LETTER_PREFIX +
sanitizeSubject(...)) which can break JSON if conversationId contains
quotes/control chars; update routeToDeadLetter to produce a safe JSON payload by
escaping conversationId the same way failure.getMessage() is escaped or, better,
by using a proper JSON serializer to build the payload before calling
jetStream.publish; ensure sanitizeSubject/sanitize are still used for subject
and logs, and keep totalDeadLettered.incrementAndGet() and getMetrics() logic
unchanged.
🧹 Nitpick comments (8)
docs/changelog.md (1)

20-20: Anchor “all findings remediated” to an authoritative artifact.

Since this is an absolute claim, add a direct reference (e.g., CodeQL run URL/SARIF artifact/check run ID) next to the verification line so the changelog remains auditable.

As per coding guidelines, "Verify Factual Claims Against Authoritative Sources ... always verify against canonical source".

Also applies to: 52-52

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/changelog.md` at line 20, Update the changelog entry that reads "**What
changed:** Remediated all CodeQL findings from PR `#455` scan — 4 High severity...
and 77 Medium severity..." to include an authoritative reference (e.g., CodeQL
run URL, SARIF artifact ID, or check run ID) immediately adjacent to the claim
so it is auditable; also apply the same change to the other occurrence
referenced (the entry at lines noted as 52-52) so both places point to the
canonical verification artifact.
src/main/java/ai/labs/eddi/modules/llm/tools/ToolRateLimiter.java (1)

137-167: Prefer JBoss parameterized loggers (warnf / infof) over String.format and concatenation.

Both log sites bypass the JBoss Logger's lazy formatting (String.format always allocates; concatenation always concatenates) and diverge from the parameterized style used across the rest of the codebase.

♻️ Proposed change
-            LOGGER.warn(String.format("Rate limit exceeded for tool '%s'. Reset in %d seconds.", sanitize(toolName), waitTimeMs / 1000));
+            LOGGER.warnf("Rate limit exceeded for tool '%s'. Reset in %d seconds.", sanitize(toolName), waitTimeMs / 1000);
-        LOGGER.info("Reset rate limit for tool: " + sanitize(toolName));
+        LOGGER.infof("Reset rate limit for tool: %s", sanitize(toolName));

As per coding guidelines, "Logging — Use JBoss Logger (Logger.getLogger(Class.class))".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ai/labs/eddi/modules/llm/tools/ToolRateLimiter.java` around
lines 137 - 167, The two logging sites in ToolRateLimiter (the warn in acquire
flow and the info in reset) use String.format and string concatenation which
bypass JBoss Logger's lazy formatting; change them to the parameterized JBoss
Logger methods (use LOGGER.warnf(...) for the rate-limit exceeded message and
LOGGER.infof(...) for the reset message), pass sanitized toolName and the
computed wait/reset values as format arguments (e.g., %s and %d/%s placeholders)
instead of preformatting strings, and keep the sanitize(toolName) call as the
argument so no string allocation happens before logging.
src/main/java/ai/labs/eddi/modules/llm/tools/ToolCostTracker.java (1)

186-186: Use LOGGER.warnf(...) instead of LOGGER.warn(String.format(...)).

String.format is evaluated even when WARN is disabled, and it diverges from the parameterized JBoss-Logger style used elsewhere in this file (e.g., the debugf you just added on line 143).

♻️ Proposed change
-            LOGGER.warn(String.format("Conversation %s exceeded budget: $%.4f > $%.4f", sanitize(conversationId), metrics.getTotalCost(), maxBudget));
+            LOGGER.warnf("Conversation %s exceeded budget: $%.4f > $%.4f", sanitize(conversationId), metrics.getTotalCost(), maxBudget);

As per coding guidelines, "Logging — Use JBoss Logger (Logger.getLogger(Class.class))" with appropriate parameterized levels.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ai/labs/eddi/modules/llm/tools/ToolCostTracker.java` at line
186, Replace the current String.format logging call in ToolCostTracker with the
JBoss-Logger parameterized variant: change
LOGGER.warn(String.format("Conversation %s exceeded budget: $%.4f > $%.4f",
sanitize(conversationId), metrics.getTotalCost(), maxBudget)); to use
LOGGER.warnf(...) and pass the format string and the arguments
(sanitize(conversationId), metrics.getTotalCost(), maxBudget) directly so the
formatting is skipped when WARN is disabled and matches the debugf style used
elsewhere.
src/main/java/ai/labs/eddi/engine/runtime/internal/InMemoryConversationCoordinator.java (1)

152-176: Pass the sanitized id through executeWithRetry to avoid re-sanitizing on every retry.

submitInOrder already computes safeConversationId on line 103, but executeWithRetry receives the raw conversationId and calls sanitize(conversationId) again on every failure (lines 164 and 168). Each retry/dead-letter path re-walks the string — and since retries can happen up to MAX_RETRIES times in tight succession, you also lose the guarantee that all log lines display the same sanitized form (LogSanitizer is deterministic today, but the duplication is fragile and noisy).

Either thread the sanitized id through the helper, or sanitize once locally inside executeWithRetry.

♻️ Proposed refactor
     private void executeWithRetry(String conversationId, BlockingQueue<Callable<Void>> queue, Callable<Void> callable, int attempt) {
+        final String safeConversationId = sanitize(conversationId);
         runtime.submitCallable(callable, new IRuntime.IFinishedExecution<>() {
             `@Override`
             public void onComplete(Void result) {
                 totalProcessed.incrementAndGet();
                 submitNext(conversationId, queue);
             }

             `@Override`
             public void onFailure(Throwable t) {
                 int nextAttempt = attempt + 1;
                 if (nextAttempt < MAX_RETRIES) {
-                    log.warnf(t, "In-memory task failed (conversationId=%s, attempt=%d/%d), retrying...", sanitize(conversationId), nextAttempt,
+                    log.warnf(t, "In-memory task failed (conversationId=%s, attempt=%d/%d), retrying...", safeConversationId, nextAttempt,
                             MAX_RETRIES);
                     executeWithRetry(conversationId, queue, callable, nextAttempt);
                 } else {
-                    log.errorf(t, "In-memory task exhausted retries (conversationId=%s, attempts=%d), dead-lettering", sanitize(conversationId),
+                    log.errorf(t, "In-memory task exhausted retries (conversationId=%s, attempts=%d), dead-lettering", safeConversationId,
                             nextAttempt);
                     routeToDeadLetter(conversationId, t);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/ai/labs/eddi/engine/runtime/internal/InMemoryConversationCoordinator.java`
around lines 152 - 176, submit the sanitized conversation id into
executeWithRetry (or sanitize once at the top of that method) so we don't re-run
sanitize(...) on every retry; update the executeWithRetry signature/locals to
accept a safeConversationId (use the already computed safeConversationId from
submitInOrder) and replace sanitize(conversationId) calls in the
log.warnf/log.errorf invocations (and any places that log the id) with that
safeConversationId, while continuing to pass the original conversationId where
the raw id is required (e.g., to routeToDeadLetter or submitNext if they need
the raw id).
src/main/java/ai/labs/eddi/modules/llm/impl/AgentOrchestrator.java (1)

274-275: Minor: sanitize the untrusted source, not the composed message.

budgetError is "Budget exceeded for conversation " + conversationId. Sanitizing the whole string works, but it's clearer to sanitize the untrusted input once and reuse it (the same pattern used in NatsConversationCoordinator.submitInOrder with safeConversationId). It also avoids feeding the static literal portion through the sanitizer on every call.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ai/labs/eddi/modules/llm/impl/AgentOrchestrator.java` around
lines 274 - 275, In AgentOrchestrator (around the budget handling), sanitize the
untrusted conversationId once into a local safeConversationId (like the pattern
used in NatsConversationCoordinator.submitInOrder) and then build the log
message from the static literal plus safeConversationId; replace the current
sanitizing of the entire budgetError string so LOGGER.warn(sanitize(...))
becomes LOGGER.warn("Budget exceeded for conversation " + safeConversationId)
(or equivalent), ensuring sanitize is called only on the untrusted
conversationId.
src/test/java/ai/labs/eddi/engine/internal/RestAgentEngineStreamingTest.java (1)

39-51: Optional: nested test now covers LogSanitizer, not RestAgentEngineStreaming.

After the refactor, SanitizeForLog exercises the centralized utility directly rather than any behavior of RestAgentEngineStreaming. Per the PR description, LogSanitizerTest already provides 23 dedicated tests for this. This nested test is now redundant with that suite — consider removing it (or repurposing it to assert that a public RestAgentEngineStreaming log path actually goes through LogSanitizer.sanitize).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/ai/labs/eddi/engine/internal/RestAgentEngineStreamingTest.java`
around lines 39 - 51, The nested test SanitizeForLog in
RestAgentEngineStreamingTest is redundant because it directly tests
ai.labs.eddi.utils.LogSanitizer.sanitize; either remove the SanitizeForLog class
entirely, or replace it with a targeted test that verifies
RestAgentEngineStreaming uses LogSanitizer.sanitize (e.g., exercise the public
logging path on RestAgentEngineStreaming and assert the emitted log/message is
sanitized via LogSanitizer.sanitize). Locate the nested class SanitizeForLog and
either delete it or rewrite its test method to call the RestAgentEngineStreaming
public method that produces logs and assert the output matches
LogSanitizer.sanitize(originalInput).
src/main/java/ai/labs/eddi/engine/runtime/internal/NatsConversationCoordinator.java (1)

260-308: Optional: cache the sanitized conversationId once per call.

publishAndExecute invokes sanitize(conversationId) up to four times across the success/failure paths (lines 276, 295, 301, 335 in routeToDeadLetter). Computing it once at the top of each method (mirroring the safeConversationId pattern in submitInOrder at line 215) would be cheaper and easier to read. Not a correctness issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/ai/labs/eddi/engine/runtime/internal/NatsConversationCoordinator.java`
around lines 260 - 308, Compute a single local sanitizedConversationId =
sanitize(conversationId) at the top of publishAndExecute and use that variable
wherever sanitize(conversationId) is currently called (logs and
routeToDeadLetter) to avoid repeated sanitization; keep using
sanitizeSubject(conversationId) for the NATS subject as before. Update
references inside the runtime.submitCallable callbacks (onComplete/onFailure)
and the error log in the publish catch to use sanitizedConversationId, and pass
sanitizedConversationId (or the original conversationId if routeToDeadLetter
expects raw id) consistently to routeToDeadLetter and log calls to mirror the
safeConversationId pattern used in submitInOrder.
src/test/java/ai/labs/eddi/engine/memory/rest/RestConversationStoreTest.java (1)

244-311: Consider adding tests for remaining pagination edge cases.

The new suite covers null/negative index, null limit, excessive limit, and a valid limit, but leaves a few branches from the PR's stated normalization rules (index ≥ 0, limit 1–100 with null default 20, plus the Integer.MAX_VALUE overflow guard on index increment) untested:

  • limit = 0 and limit < 0 (per the PR description, the lower bound is 1 — worth pinning down whether these clamp to 1 or to 20).
  • The Integer.MAX_VALUE overflow guard on the page-advancement increment in RestConversationStore — this is one of the high-severity findings the PR claims to fix, but there's no regression test for it here.

Adding two more cases would make the regression suite complete for the documented behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/ai/labs/eddi/engine/memory/rest/RestConversationStoreTest.java`
around lines 244 - 311, Add two unit tests to cover the missing pagination edge
cases: (1) test that limit=0 and limit<0 are clamped to the lower bound (per PR
rules, assert readDescriptors is called with limit=1 or the documented lower
bound) by calling RestConversationStore.readConversationDescriptors(0, 0, ...)
and with a negative limit and verifying
conversationDescriptorStore.readDescriptors(anyString(), any(), eq(0), eq(1),
anyBoolean()); and (2) test the Integer.MAX_VALUE overflow guard by invoking
RestConversationStore.readConversationDescriptors(Integer.MAX_VALUE, 1, ...) and
exercising the page-advance path (e.g., simulate a next-page increment or call
the method path that would increment index) and then verify that readDescriptors
was called with a clamped index (not overflowing) — reference
RestConversationStore.readConversationDescriptors and
conversationDescriptorStore.readDescriptors in your assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/changelog.md`:
- Line 30: Update the changelog wording to accurately reflect
LogSanitizer.java's behavior: state that LogSanitizer.sanitize() first replaces
occurrences of \r, \n, and \t with '_' and then removes/strips other control
characters, rather than saying those characters are simply "stripped"; adjust
the sentence to use "replaces \r\n\t with '_' and strips other control
characters" (or equivalent precise phrasing) to avoid the misleading security
summary.

In `@src/main/java/ai/labs/eddi/modules/llm/impl/AgentOrchestrator.java`:
- Around line 420-421: The log call that prints groupIds is a potential
log-injection vector because groupIds (built from Property.getValueString()) is
not sanitized; update the LOGGER.infof call so each element of groupIds is
passed through sanitize (or sanitize the joined string) before formatting —
i.e., map groupIds via sanitize(...) (reference: LOGGER.infof invocation,
variable groupIds, and sanitize(...) method; related getters memory.getAgentId()
and memory.getUserId() remain sanitized as-is).

In `@src/test/java/ai/labs/eddi/utils/LogSanitizerTest.java`:
- Around line 94-109: The `@DisplayName` on the test
stripsAllNonWhitespaceControlChars is inaccurate because the loop also includes
0x0B (VT) and 0x0C (FF); update the annotation on the test method
stripsAllNonWhitespaceControlChars to correctly describe the range — for example
use "should strip all control chars 0x01-0x1F except \\r, \\n, \\t" or
explicitly "0x01-0x08, 0x0B-0x0C, 0x0E-0x1F"; modify only the `@DisplayName`
string and keep the test logic and the sanitize(...) call unchanged.

---

Outside diff comments:
In
`@src/main/java/ai/labs/eddi/engine/runtime/internal/NatsConversationCoordinator.java`:
- Around line 322-336: The dead-letter payload in routeToDeadLetter currently
interpolates a raw conversationId into the JSON string (DEAD_LETTER_PREFIX +
sanitizeSubject(...)) which can break JSON if conversationId contains
quotes/control chars; update routeToDeadLetter to produce a safe JSON payload by
escaping conversationId the same way failure.getMessage() is escaped or, better,
by using a proper JSON serializer to build the payload before calling
jetStream.publish; ensure sanitizeSubject/sanitize are still used for subject
and logs, and keep totalDeadLettered.incrementAndGet() and getMetrics() logic
unchanged.

In `@src/main/java/ai/labs/eddi/integrations/slack/rest/RestSlackWebhook.java`:
- Line 99: The log call LOGGER.warnf("Slack signature verification failed
(timestamp=%s)", timestamp) in RestSlackWebhook uses the untrusted
X-Slack-Request-Timestamp header value; sanitize or validate the timestamp
before logging (e.g., canonicalize to a digits-only substring or strip
control/formatting characters and fall back to a safe placeholder) and then log
the sanitized value via LOGGER.warnf so the adjacent log site no longer emits
attacker-controlled characters.

---

Nitpick comments:
In `@docs/changelog.md`:
- Line 20: Update the changelog entry that reads "**What changed:** Remediated
all CodeQL findings from PR `#455` scan — 4 High severity... and 77 Medium
severity..." to include an authoritative reference (e.g., CodeQL run URL, SARIF
artifact ID, or check run ID) immediately adjacent to the claim so it is
auditable; also apply the same change to the other occurrence referenced (the
entry at lines noted as 52-52) so both places point to the canonical
verification artifact.

In
`@src/main/java/ai/labs/eddi/engine/runtime/internal/InMemoryConversationCoordinator.java`:
- Around line 152-176: submit the sanitized conversation id into
executeWithRetry (or sanitize once at the top of that method) so we don't re-run
sanitize(...) on every retry; update the executeWithRetry signature/locals to
accept a safeConversationId (use the already computed safeConversationId from
submitInOrder) and replace sanitize(conversationId) calls in the
log.warnf/log.errorf invocations (and any places that log the id) with that
safeConversationId, while continuing to pass the original conversationId where
the raw id is required (e.g., to routeToDeadLetter or submitNext if they need
the raw id).

In
`@src/main/java/ai/labs/eddi/engine/runtime/internal/NatsConversationCoordinator.java`:
- Around line 260-308: Compute a single local sanitizedConversationId =
sanitize(conversationId) at the top of publishAndExecute and use that variable
wherever sanitize(conversationId) is currently called (logs and
routeToDeadLetter) to avoid repeated sanitization; keep using
sanitizeSubject(conversationId) for the NATS subject as before. Update
references inside the runtime.submitCallable callbacks (onComplete/onFailure)
and the error log in the publish catch to use sanitizedConversationId, and pass
sanitizedConversationId (or the original conversationId if routeToDeadLetter
expects raw id) consistently to routeToDeadLetter and log calls to mirror the
safeConversationId pattern used in submitInOrder.

In `@src/main/java/ai/labs/eddi/modules/llm/impl/AgentOrchestrator.java`:
- Around line 274-275: In AgentOrchestrator (around the budget handling),
sanitize the untrusted conversationId once into a local safeConversationId (like
the pattern used in NatsConversationCoordinator.submitInOrder) and then build
the log message from the static literal plus safeConversationId; replace the
current sanitizing of the entire budgetError string so
LOGGER.warn(sanitize(...)) becomes LOGGER.warn("Budget exceeded for conversation
" + safeConversationId) (or equivalent), ensuring sanitize is called only on the
untrusted conversationId.

In `@src/main/java/ai/labs/eddi/modules/llm/tools/ToolCostTracker.java`:
- Line 186: Replace the current String.format logging call in ToolCostTracker
with the JBoss-Logger parameterized variant: change
LOGGER.warn(String.format("Conversation %s exceeded budget: $%.4f > $%.4f",
sanitize(conversationId), metrics.getTotalCost(), maxBudget)); to use
LOGGER.warnf(...) and pass the format string and the arguments
(sanitize(conversationId), metrics.getTotalCost(), maxBudget) directly so the
formatting is skipped when WARN is disabled and matches the debugf style used
elsewhere.

In `@src/main/java/ai/labs/eddi/modules/llm/tools/ToolRateLimiter.java`:
- Around line 137-167: The two logging sites in ToolRateLimiter (the warn in
acquire flow and the info in reset) use String.format and string concatenation
which bypass JBoss Logger's lazy formatting; change them to the parameterized
JBoss Logger methods (use LOGGER.warnf(...) for the rate-limit exceeded message
and LOGGER.infof(...) for the reset message), pass sanitized toolName and the
computed wait/reset values as format arguments (e.g., %s and %d/%s placeholders)
instead of preformatting strings, and keep the sanitize(toolName) call as the
argument so no string allocation happens before logging.

In
`@src/test/java/ai/labs/eddi/engine/internal/RestAgentEngineStreamingTest.java`:
- Around line 39-51: The nested test SanitizeForLog in
RestAgentEngineStreamingTest is redundant because it directly tests
ai.labs.eddi.utils.LogSanitizer.sanitize; either remove the SanitizeForLog class
entirely, or replace it with a targeted test that verifies
RestAgentEngineStreaming uses LogSanitizer.sanitize (e.g., exercise the public
logging path on RestAgentEngineStreaming and assert the emitted log/message is
sanitized via LogSanitizer.sanitize). Locate the nested class SanitizeForLog and
either delete it or rewrite its test method to call the RestAgentEngineStreaming
public method that produces logs and assert the output matches
LogSanitizer.sanitize(originalInput).

In
`@src/test/java/ai/labs/eddi/engine/memory/rest/RestConversationStoreTest.java`:
- Around line 244-311: Add two unit tests to cover the missing pagination edge
cases: (1) test that limit=0 and limit<0 are clamped to the lower bound (per PR
rules, assert readDescriptors is called with limit=1 or the documented lower
bound) by calling RestConversationStore.readConversationDescriptors(0, 0, ...)
and with a negative limit and verifying
conversationDescriptorStore.readDescriptors(anyString(), any(), eq(0), eq(1),
anyBoolean()); and (2) test the Integer.MAX_VALUE overflow guard by invoking
RestConversationStore.readConversationDescriptors(Integer.MAX_VALUE, 1, ...) and
exercising the page-advance path (e.g., simulate a next-page increment or call
the method path that would increment index) and then verify that readDescriptors
was called with a clamped index (not overflowing) — reference
RestConversationStore.readConversationDescriptors and
conversationDescriptorStore.readDescriptors in your assertions.
🪄 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: 7c179641-f610-4266-a9a2-82b4cd113bb0

📥 Commits

Reviewing files that changed from the base of the PR and between ac42a77 and bafb415.

📒 Files selected for processing (29)
  • docs/changelog.md
  • src/main/java/ai/labs/eddi/configs/groups/rest/RestAgentGroupStore.java
  • src/main/java/ai/labs/eddi/configs/rag/rest/RestRagIngestion.java
  • src/main/java/ai/labs/eddi/datastore/DescriptorStore.java
  • src/main/java/ai/labs/eddi/engine/internal/RestAgentEngineStreaming.java
  • src/main/java/ai/labs/eddi/engine/internal/RestCoordinatorAdmin.java
  • src/main/java/ai/labs/eddi/engine/memory/rest/RestConversationStore.java
  • src/main/java/ai/labs/eddi/engine/runtime/internal/CronDescriber.java
  • src/main/java/ai/labs/eddi/engine/runtime/internal/InMemoryConversationCoordinator.java
  • src/main/java/ai/labs/eddi/engine/runtime/internal/NatsConversationCoordinator.java
  • src/main/java/ai/labs/eddi/engine/tenancy/InMemoryTenantQuotaStore.java
  • src/main/java/ai/labs/eddi/integrations/slack/rest/RestSlackWebhook.java
  • src/main/java/ai/labs/eddi/modules/llm/impl/AgentOrchestrator.java
  • src/main/java/ai/labs/eddi/modules/llm/impl/ChatModelRegistry.java
  • src/main/java/ai/labs/eddi/modules/llm/impl/ConversationSummarizer.java
  • src/main/java/ai/labs/eddi/modules/llm/impl/EmbeddingStoreFactory.java
  • src/main/java/ai/labs/eddi/modules/llm/impl/LlmTask.java
  • src/main/java/ai/labs/eddi/modules/llm/impl/McpToolProviderManager.java
  • src/main/java/ai/labs/eddi/modules/llm/tools/ToolCacheService.java
  • src/main/java/ai/labs/eddi/modules/llm/tools/ToolCostTracker.java
  • src/main/java/ai/labs/eddi/modules/llm/tools/ToolRateLimiter.java
  • src/main/java/ai/labs/eddi/modules/nlp/expressions/utilities/ExpressionProvider.java
  • src/main/java/ai/labs/eddi/modules/rag/RagIngestionService.java
  • src/main/java/ai/labs/eddi/secrets/rest/RestSecretStore.java
  • src/main/java/ai/labs/eddi/utils/LogSanitizer.java
  • src/test/java/ai/labs/eddi/engine/internal/RestAgentEngineStreamingTest.java
  • src/test/java/ai/labs/eddi/engine/memory/rest/RestConversationStoreTest.java
  • src/test/java/ai/labs/eddi/engine/runtime/internal/CronDescriberExtendedTest.java
  • src/test/java/ai/labs/eddi/utils/LogSanitizerTest.java

Comment thread docs/changelog.md Outdated
Comment thread src/main/java/ai/labs/eddi/modules/llm/impl/AgentOrchestrator.java Outdated
Comment thread src/test/java/ai/labs/eddi/utils/LogSanitizerTest.java
- changelog: corrected LogSanitizer behavior description (replace vs strip)
- AgentOrchestrator: sanitize groupIds entries before logging (CWE-117)
- LogSanitizerTest: fixed @DisplayName range to include 0x0B-0x0C

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

🧹 Nitpick comments (1)
src/main/java/ai/labs/eddi/modules/llm/impl/AgentOrchestrator.java (1)

420-421: groupIds sanitization addressed — minor nit: use a method reference.

The previously flagged log-injection vector on groupIds is now closed by mapping each element through sanitize(...) before formatting. As a small readability improvement, the lambda g -> sanitize(g) can be replaced with a method reference.

♻️ Proposed nit
-        LOGGER.infof("[MEMORY] UserMemoryTool enabled for agent='%s', user='%s', groups=%s", sanitize(memory.getAgentId()),
-                sanitize(memory.getUserId()), groupIds.stream().map(g -> sanitize(g)).toList());
+        LOGGER.infof("[MEMORY] UserMemoryTool enabled for agent='%s', user='%s', groups=%s",
+                sanitize(memory.getAgentId()), sanitize(memory.getUserId()),
+                groupIds.stream().map(ai.labs.eddi.utils.LogSanitizer::sanitize).toList());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/ai/labs/eddi/modules/llm/impl/AgentOrchestrator.java` around
lines 420 - 421, Replace the lambda mapping in the log line so it uses a method
reference to sanitize each group id instead of "g -> sanitize(g)"; update the
call in the LOGGER.infof statement that references groupIds (the line that
currently does groupIds.stream().map(g -> sanitize(g)).toList()) to use a method
reference to the existing sanitize(...) method (e.g.,
groupIds.stream().map(YourClassName::sanitize).toList() or this::sanitize as
appropriate) so readability is improved while preserving the sanitization
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/java/ai/labs/eddi/modules/llm/impl/AgentOrchestrator.java`:
- Around line 420-421: Replace the lambda mapping in the log line so it uses a
method reference to sanitize each group id instead of "g -> sanitize(g)"; update
the call in the LOGGER.infof statement that references groupIds (the line that
currently does groupIds.stream().map(g -> sanitize(g)).toList()) to use a method
reference to the existing sanitize(...) method (e.g.,
groupIds.stream().map(YourClassName::sanitize).toList() or this::sanitize as
appropriate) so readability is improved while preserving the sanitization
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3c05903e-fc69-4056-b220-2cd2ac647130

📥 Commits

Reviewing files that changed from the base of the PR and between bafb415 and 33cc751.

📒 Files selected for processing (3)
  • docs/changelog.md
  • src/main/java/ai/labs/eddi/modules/llm/impl/AgentOrchestrator.java
  • src/test/java/ai/labs/eddi/utils/LogSanitizerTest.java
✅ Files skipped from review due to trivial changes (1)
  • docs/changelog.md

@ginccc ginccc requested a review from rolandpickl April 27, 2026 09:11
@ginccc ginccc merged commit 7b740e0 into main Apr 27, 2026
21 of 23 checks passed
@ginccc ginccc deleted the fix/codeql-remediation-pr455 branch April 27, 2026 10:55
@coderabbitai coderabbitai Bot mentioned this pull request Jun 9, 2026
13 tasks
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.

4 participants