Skip to content

fix: close $IFS permission bypass, litellm.callbacks overwrite, cross-agent memory reset#3150

Open
praisonai-triage-agent[bot] wants to merge 2 commits into
mainfrom
claude/issue-3149-20260718-0716
Open

fix: close $IFS permission bypass, litellm.callbacks overwrite, cross-agent memory reset#3150
praisonai-triage-agent[bot] wants to merge 2 commits into
mainfrom
claude/issue-3149-20260718-0716

Conversation

@praisonai-triage-agent

@praisonai-triage-agent praisonai-triage-agent Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Fixes #3149

Summary

Addresses three validated high-impact gaps in praisonaiagents core, each with a minimal, backward-compatible fix (no new public knobs, defaults unchanged).

Gap 1 — $IFS shell-permission bypass (Security)

permissions/command_parser.py + manager.py: the static tokenizer cannot see bash runtime parameter expansion, so rm${IFS}-rf${IFS}/ slipped past a bash:rm -rf * deny while a broad bash:* allow matched. Now _check_shell_command escalates any command containing unresolvable parameter expansion ($IFS / ${...}) to ASK (an explicit deny still wins). Command substitution ($(...)/backticks) is intentionally excluded — it is already decomposed and deny-checked per-op by #2202, and those tests still pass.

Gap 2 — litellm.callbacks global overwrite (Correctness, multi-agent)

llm/llm.py: _setup_event_tracking unconditionally did litellm.callbacks = events, wiping other agents' callbacks process-wide (even from an unrelated Agent built with the default events=[]). It now no-ops on empty events and merges into the global list, removing only the callbacks this instance previously registered (tracked on self._registered_callbacks).

Gap 3 — cross-agent Memory reset wipes shared DB (Data isolation)

memory/memory.py (+ adapters/factories.py): reset_long_term() called chroma_client.reset(), destroying every collection in the shared persistent store. It now scopes deletion to this instance's own collection via delete_collection(name=...). Collection name is also an opt-in collection_name config override; the default (memory_store) and the documented deterministic-persistence behaviour are unchanged.

Scope / critical review

Per AGENTS.md, kept lightweight: no new modules or Agent params. I deliberately did not implement the issue's suggested "namespace every collection by instance" default, since that would break the intended deterministic/persistent collection (knowledge.py:95) — only the safe collection-scoped reset + opt-in override.

Test plan

  • Live repro: ${IFS}/$IFS payloads → ASK; $(rm -rf x) → DENY; normal commands → ALLOW
  • Gap 2: two-instance simulation confirms no cross-agent callback wipe + own-callback replacement
  • Gap 3: fake client confirms only own collection deleted, other agent's preserved, full reset() never called
  • tests/unit/permissions/test_command_aware.py, test_permissions.py — 72 passed
  • tests/test_memory_delete.py — 18 passed

Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Memory storage can now use a configurable collection name, supporting better separation between memory instances.
    • Resetting long-term memory now removes only the selected collection instead of clearing all stored collections.
  • Bug Fixes

    • Improved event tracking so callbacks from other active components are preserved.
    • Shell commands with ambiguous parameter expansion now require confirmation unless explicitly denied, improving command safety.

…-agent memory reset (fixes #3149)

- permissions: escalate shell commands containing unresolvable parameter
  expansion ($IFS / ${...}) to ASK instead of letting a broad allow rule
  short-circuit a specific deny. Command substitution ($()/backticks) is
  left to the existing per-op decomposition.
- llm: _setup_event_tracking now no-ops on empty events and merges into the
  process-global litellm.callbacks (tracking per-instance ownership) instead
  of overwriting it, so unrelated agents' callbacks are preserved.
- memory: reset_long_term() deletes only this instance's own Chroma
  collection rather than resetting the entire shared client; collection name
  is now an opt-in config override (default unchanged).

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison

Copy link
Copy Markdown
Owner

@coderabbitai review

@MervinPraison

Copy link
Copy Markdown
Owner

/review

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@MervinPraison MervinPraison added pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:manual-review Blocked: requires manual review pipeline/blocked:no-final Blocked: no FINAL @claude trigger yet pipeline/final-claude-pending Reviews done; waiting for FINAL @claude labels Jul 18, 2026
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a511983-e790-4539-8bf1-9d7d8236c207

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The changes update LiteLLM callback merging, make Chroma collection names configurable and instance-scoped, limit long-term resets to one collection, and escalate shell commands containing unresolved parameter expansions.

Changes

LLM callback registration

Layer / File(s) Summary
Instance-scoped callback merging
src/praisonai-agents/praisonaiagents/llm/llm.py
Event tracking returns early for empty events, removes callbacks previously registered by the current instance, and preserves callbacks from other instances.

Memory collection scoping

Layer / File(s) Summary
Configurable Chroma collections
src/praisonai-agents/praisonaiagents/memory/adapters/factories.py, src/praisonai-agents/praisonaiagents/memory/memory.py
Chroma collection names flow through adapter configuration and initialization, while long-term reset deletes and recreates only the active collection.

Shell expansion permission handling

Layer / File(s) Summary
Unresolvable expansion detection
src/praisonai-agents/praisonaiagents/permissions/command_parser.py, src/praisonai-agents/praisonaiagents/permissions/manager.py
Selected unresolved shell expansions are detected before decomposition; explicit denies remain denied and other matching results escalate to ASK.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: mervinpraison

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the three main fixes in the PR and matches the actual changes.
Linked Issues check ✅ Passed The PR addresses the shell expansion ASK escalation, callback merging, and per-instance Chroma collection/reset behavior described in #3149.
Out of Scope Changes check ✅ Passed The changes stay within the reported permission, callback, and memory isolation fixes without unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-3149-20260718-0716

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.

@greptile-apps

greptile-apps Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR closes three gaps in shell permissions, callback registration, and shared memory resets. The main changes are:

  • Escalate unresolved shell parameter expansions for approval.
  • Merge LiteLLM callbacks without replacing callbacks from other agents.
  • Delete and recreate only the configured Chroma collection.
  • Propagate collection names through the memory adapter.

Confidence Score: 4/5

The shell permission gap should be fixed before merging.

  • Callback merging and collection-scoped resets address the reported failures.
  • Bash arithmetic expansion can still produce a denied argument after permission matching.
  • A broad Bash allow can therefore approve a command whose expanded form matches a specific deny.

src/praisonai-agents/praisonaiagents/permissions/command_parser.py

Security Review

Bash arithmetic expansion remains outside the unresolved-expansion check, allowing runtime-expanded arguments to evade value-specific deny rules when a broad Bash allow is configured.

Important Files Changed

Filename Overview
src/praisonai-agents/praisonaiagents/permissions/command_parser.py Adds unresolved parameter-expansion detection, but omits Bash arithmetic expansion.
src/praisonai-agents/praisonaiagents/permissions/manager.py Escalates recognized shell expansions to approval while preserving explicit denies.
src/praisonai-agents/praisonaiagents/llm/llm.py Merges instance callbacks into LiteLLM's global callback list.
src/praisonai-agents/praisonaiagents/memory/memory.py Scopes Chroma resets to one collection and propagates deletion failures.
src/praisonai-agents/praisonaiagents/memory/adapters/factories.py Stores the configured Chroma collection name on the adapter.

Reviews (2): Last reviewed commit: "fix: escalate bare $VAR expansion to ASK..." | Re-trigger Greptile

Comment thread src/praisonai-agents/praisonaiagents/permissions/command_parser.py Outdated
Comment thread src/praisonai-agents/praisonaiagents/memory/memory.py
@MervinPraison

Copy link
Copy Markdown
Owner

@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.

Phase 1: Review per AGENTS.md

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.
  6. Repo routing: agent-callable tools → PraisonAI-Tools; lifecycle plugins → PraisonAI-Plugins; optional sandbox backends → PraisonAI-Plugins (praisonai.sandbox entry point) — request changes if wrongly added to praisonaiagents/

Phase 2: FIX Valid Issues
7. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
8. Also independently identify and fix any gaps or issues you find in the changed code — do not rely only on prior reviewer feedback
9. Push all code fixes directly to THIS branch (do NOT create a new PR)
10. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
11. If all issues are resolved, approve the PR / close the Issue
12. If blocking issues remain, request changes / leave clear action items

@MervinPraison MervinPraison added pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI pipeline/blocked:cooldown Blocked: post-push or @claude cooldown and removed pipeline/final-claude-pending Reviews done; waiting for FINAL @claude pipeline/blocked:no-final Blocked: no FINAL @claude trigger yet labels Jul 18, 2026
@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #3150

  • Read AGENTS.md + changed files
  • Evaluate reviewer findings (unbraced vars, failed-reset-reports-success)
  • Implement valid fixes
  • Validate (regex behaviour + syntax)
  • Push + final verdict

Phase 1 — AGENTS.md compliance ✅

  • Lightweight & routing: All changes stay in praisonaiagents/ core and touch only permissions + memory internals. No new Agent params, no new modules/exports, no new deps. Correct repo (not tools/plugins). ✔
  • Backward compatible: Defaults unchanged (memory_store, deterministic persistence). $(...)/backtick decomposition path preserved; deny still wins. ✔
  • Performance: Only a compiled-regex search per shell check and an exception path in a rarely-called reset — no hot-path regression. ✔
  • SDK value: Genuine security/correctness/isolation hardening of existing behaviour, not feature creep. ✔

Phase 2 — Fixes implemented

Both Greptile P1 findings were valid and are now fixed:

1. command_parser.py — unbraced $VAR bypass (Security)
$IFS/${...} were escalated to ASK, but bare $HOME/$VAR (also runtime-expanded, invisible to the static tokenizer) slipped through and could evade a value-specific deny under a broad bash:* allow. Widened the detector:

_UNSAFE_EXPANSION_RE = re.compile(r"\$(?:\{|[A-Za-z_])")

$( (command substitution) and backticks are intentionally excluded — they remain on the deny-checked decomposition path. Verified: ${IFS}, $IFS, $HOME, $MYVAR → match (ASK); $(rm -rf x), backticks, $1, plain commands → no match.

2. memory.py — failed Chroma reset reported success (Data isolation)
delete_collection failure was logged then swallowed, after which _init_chroma() reopened the unchanged collection so callers believed the reset succeeded. Now raises after logging so a failed reset surfaces instead of silently leaving long-term memory intact.

Files modified this round

  • permissions/command_parser.py — broadened unsafe-expansion regex + doc comment
  • memory/memory.py — re-raise on delete_collection failure

Skipped (intentionally)

  • Namespacing every collection per-instance — correctly rejected by the author as it would break the deterministic/persistent collection (knowledge.py:95). The collection-scoped reset + opt-in collection_name is the right minimal fix.
  • litellm callback merge (Gap 2) — reviewed, no issue found; instance-tracked merge is correct.
  • Running full pytest suitepytest isn't installed in this runner; validated the regex matrix and AST syntax directly instead. Author already reports test_command_aware.py/test_permissions.py (72) and test_memory_delete.py (18) green; no existing test uses $VAR literals so the broadened regex adds no regression.

Phase 3 — Verdict

Approve. Both blocking P1 findings resolved; changes are minimal, backward-compatible, and correctly scoped. Recommend merge once CI passes. (I can't submit a formal GitHub approval for security reasons — this is my review sign-off.)
• branch claude/issue-3149-20260718-0716

@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

Caution

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

⚠️ Outside diff range comments (1)
src/praisonai-agents/praisonaiagents/llm/llm.py (1)

5259-5292: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Fix cross-instance clobbering, state leaks, and TOCTOU races.

The callback tracking logic has three severe flaws:

  1. Cross-instance clobbering: Removing callbacks from success_callback and _async_success_callback based on type(event) wipes out callbacks registered by other concurrent agents of the same type (e.g., if multiple agents use the string "langfuse", all string callbacks are wiped globally).
  2. TOCTOU Data Race: The if cb in list: list.remove(cb) pattern is not thread-safe. Concurrent modifications by multi-agent workflows can raise ValueError: list.remove(x): x not in list.
  3. State leak on clear: The early return if not events: return prevents the cleanup of previously registered callbacks if the instance is updated with an empty list.

As per coding guidelines, avoid shared mutable global state between agents; isolating agent context requires strictly instance-scoped management without clobbering other instances. Replace the type-based removal with exact-instance removal, eliminate the TOCTOU race with try...except, and ensure the early return correctly checks for existing registrations.

🔒️ Proposed fix to isolate callbacks safely
-        if not events:
+        if not events and not getattr(self, "_registered_callbacks", []):
             return
 
         try:
             import litellm
         except ImportError:
             raise ImportError(
                 "LiteLLM is required but not installed. "
                 "Please install it with: pip install 'praisonaiagents[llm]'"
             )
 
-        event_types = [type(event) for event in events]
-        
-        # Remove old events of same type
-        for event in litellm.success_callback[:]:
-            if type(event) in event_types:
-                litellm.success_callback.remove(event)
-                
-        for event in litellm._async_success_callback[:]:
-            if type(event) in event_types:
-                litellm._async_success_callback.remove(event)
-
-        # Merge into the global list rather than replacing it. Only remove the
-        # callbacks this instance registered on a prior call, then append the
-        # current ones, preserving other instances' callbacks.
-        if litellm.callbacks is None:
+        # Merge into the global lists rather than replacing them. Only remove the
+        # exact callbacks this instance registered on a prior call, avoiding
+        # cross-instance clobbering and TOCTOU races.
+        if getattr(litellm, "callbacks", None) is None:
             litellm.callbacks = []
+
         for cb in getattr(self, "_registered_callbacks", []):
-            if cb in litellm.callbacks:
-                litellm.callbacks.remove(cb)
+            try:
+                litellm.callbacks.remove(cb)
+            except ValueError:
+                pass
+            try:
+                litellm.success_callback.remove(cb)
+            except ValueError:
+                pass
+            try:
+                litellm._async_success_callback.remove(cb)
+            except ValueError:
+                pass
+
         for event in events:
             if event not in litellm.callbacks:
                 litellm.callbacks.append(event)
+                
         self._registered_callbacks = list(events)
🤖 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 `@src/praisonai-agents/praisonaiagents/llm/llm.py` around lines 5259 - 5292,
Update the callback registration method around the events handling to track and
remove only callbacks previously registered by the current instance, rather than
removing global callbacks by type; preserve other agents’ callbacks. Before
returning for an empty events list, clean up any callbacks stored in
_registered_callbacks, and make each removal resilient to concurrent changes by
catching a missing-entry ValueError. Apply the same instance-scoped cleanup to
success_callback, _async_success_callback, and callbacks, then refresh
_registered_callbacks.

Source: Coding guidelines

🧹 Nitpick comments (1)
src/praisonai-agents/praisonaiagents/permissions/manager.py (1)

358-364: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify comment regarding command substitution.

The comment implies that command substitutions like $(...) and backticks cannot be statically verified and will trigger the escalation to ASK. However, as documented in command_parser.py, has_unresolvable_expansion explicitly ignores them because parse_command successfully decomposes them for per-operation verification.

Consider updating the comment to focus solely on parameter expansion, avoiding confusion about the behavior of command substitution.

♻️ Proposed tweak
-        # Shell parameter/command expansion (``${IFS}``, ``$(...)``, backticks)
-        # is resolved by bash at runtime and is invisible to the static
+        # Shell parameter expansion (e.g., ``${IFS}``, ``${VAR}``)
+        # is resolved by bash at runtime and is invisible to the static
         # tokenizer, so a payload like ``rm${IFS}-rf${IFS}/`` could slip past a
         # specific deny rule while a broad ``bash:*`` allow matches. When such
         # expansion is present we cannot statically verify the command: an
         # explicit deny still wins, but otherwise we escalate to ASK rather than
         # letting the flat matcher optimistically ALLOW it.
🤖 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 `@src/praisonai-agents/praisonaiagents/permissions/manager.py` around lines 358
- 364, Update the comment near the expansion handling in the permission manager
to describe only unresolved shell parameter expansion, such as ${IFS}, as
bypassing static verification; remove references to command substitution ($(...)
and backticks), since command_parser.py handles those through parse_command for
per-operation verification.
🤖 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 `@src/praisonai-agents/praisonaiagents/memory/memory.py`:
- Around line 1156-1168: After the delete and _init_chroma() call, update the
active memory_adapter so its collection reference points to the newly
initialized self.chroma_col. Ensure subsequent store_short_term calls use the
recreated collection instead of the deleted one, without changing the existing
scoped deletion or error handling.

In `@src/praisonai-agents/praisonaiagents/permissions/manager.py`:
- Around line 353-356: In the import block within the permission-manager method,
replace the broad Exception handler around importing parse_command and
has_unresolvable_expansion with an ImportError handler only. Preserve the
existing return None fallback for genuine import failures while allowing
internal module errors such as SyntaxError or NameError to propagate.

---

Outside diff comments:
In `@src/praisonai-agents/praisonaiagents/llm/llm.py`:
- Around line 5259-5292: Update the callback registration method around the
events handling to track and remove only callbacks previously registered by the
current instance, rather than removing global callbacks by type; preserve other
agents’ callbacks. Before returning for an empty events list, clean up any
callbacks stored in _registered_callbacks, and make each removal resilient to
concurrent changes by catching a missing-entry ValueError. Apply the same
instance-scoped cleanup to success_callback, _async_success_callback, and
callbacks, then refresh _registered_callbacks.

---

Nitpick comments:
In `@src/praisonai-agents/praisonaiagents/permissions/manager.py`:
- Around line 358-364: Update the comment near the expansion handling in the
permission manager to describe only unresolved shell parameter expansion, such
as ${IFS}, as bypassing static verification; remove references to command
substitution ($(...) and backticks), since command_parser.py handles those
through parse_command for per-operation verification.
🪄 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: 99dee3ad-b112-43e0-a7de-4d192dab359c

📥 Commits

Reviewing files that changed from the base of the PR and between 74b5f0f and 87eb851.

📒 Files selected for processing (5)
  • src/praisonai-agents/praisonaiagents/llm/llm.py
  • src/praisonai-agents/praisonaiagents/memory/adapters/factories.py
  • src/praisonai-agents/praisonaiagents/memory/memory.py
  • src/praisonai-agents/praisonaiagents/permissions/command_parser.py
  • src/praisonai-agents/praisonaiagents/permissions/manager.py

Comment on lines +1156 to +1168
# Scope the reset to this instance's own collection. A full
# ``chroma_client.reset()`` wipes *every* collection in the shared
# persistent store, destroying other agents' long-term memory that
# happens to live in the same directory.
collection_name = getattr(self, "_collection_name", "memory_store")
try:
self.chroma_client.delete_collection(name=collection_name)
except Exception as e:
self._log_verbose(
f"Error deleting ChromaDB collection '{collection_name}': {e}",
logging.ERROR,
)
self._init_chroma() # recreate only this collection

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Sync reinitialized Chroma client and collection to the active adapter.

While _init_chroma() correctly recreates the collection and binds it to self.chroma_col, it leaves self.memory_adapter holding a stale reference to the deleted collection. Because store_short_term blindly delegates to the active adapter (e.g., self.memory_adapter.store_short_term), subsequent memory writes will encounter an exception when accessing the deleted collection and silently fall back to SQLite, which breaks RAG memory context for the remainder of the instance's lifecycle.

Update the adapter's references immediately after re-initialization to maintain state consistency.

🐛 Proposed fix
             except Exception as e:
                 self._log_verbose(
                     f"Error deleting ChromaDB collection '{collection_name}': {e}",
                     logging.ERROR,
                 )
             self._init_chroma()         # recreate only this collection
+            
+            # Sync the recreated client and collection to the adapter to prevent writes to the deleted collection
+            if getattr(self, "memory_adapter", None) and hasattr(self.memory_adapter, "collection"):
+                self.memory_adapter.client = self.chroma_client
+                self.memory_adapter.collection = self.chroma_col
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Scope the reset to this instance's own collection. A full
# ``chroma_client.reset()`` wipes *every* collection in the shared
# persistent store, destroying other agents' long-term memory that
# happens to live in the same directory.
collection_name = getattr(self, "_collection_name", "memory_store")
try:
self.chroma_client.delete_collection(name=collection_name)
except Exception as e:
self._log_verbose(
f"Error deleting ChromaDB collection '{collection_name}': {e}",
logging.ERROR,
)
self._init_chroma() # recreate only this collection
# Scope the reset to this instance's own collection. A full
# ``chroma_client.reset()`` wipes *every* collection in the shared
# persistent store, destroying other agents' long-term memory that
# happens to live in the same directory.
collection_name = getattr(self, "_collection_name", "memory_store")
try:
self.chroma_client.delete_collection(name=collection_name)
except Exception as e:
self._log_verbose(
f"Error deleting ChromaDB collection '{collection_name}': {e}",
logging.ERROR,
)
self._init_chroma() # recreate only this collection
# Sync the recreated client and collection to the adapter to prevent writes to the deleted collection
if getattr(self, "memory_adapter", None) and hasattr(self.memory_adapter, "collection"):
self.memory_adapter.client = self.chroma_client
self.memory_adapter.collection = self.chroma_col
🧰 Tools
🪛 Ruff (0.15.21)

[warning] 1163-1163: Do not catch blind exception: Exception

(BLE001)

🤖 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 `@src/praisonai-agents/praisonaiagents/memory/memory.py` around lines 1156 -
1168, After the delete and _init_chroma() call, update the active memory_adapter
so its collection reference points to the newly initialized self.chroma_col.
Ensure subsequent store_short_term calls use the recreated collection instead of
the deleted one, without changing the existing scoped deletion or error
handling.

Comment on lines 353 to +356
try:
from .command_parser import parse_command
from .command_parser import parse_command, has_unresolvable_expansion
except Exception:
return None

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Catch ImportError instead of a blind Exception to fail fast.

Catching a broad Exception on the import statement masks unexpected programming errors (such as a SyntaxError or NameError within command_parser.py). If the module fails to load due to an internal error, this broad catch will silently downgrade the permission manager to legacy flat matching, which reduces security by bypassing shell decomposition and workspace boundary checks.

As per coding guidelines, "Fail fast with clear exceptions". Catch ImportError instead so that internal codebase errors are explicitly surfaced.

♻️ Proposed fix
         try:
             from .command_parser import parse_command, has_unresolvable_expansion
-        except Exception:
+        except ImportError:
             return None
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
from .command_parser import parse_command
from .command_parser import parse_command, has_unresolvable_expansion
except Exception:
return None
try:
from .command_parser import parse_command, has_unresolvable_expansion
except ImportError:
return None
🧰 Tools
🪛 Ruff (0.15.21)

[warning] 355-355: Do not catch blind exception: Exception

(BLE001)

🤖 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 `@src/praisonai-agents/praisonaiagents/permissions/manager.py` around lines 353
- 356, In the import block within the permission-manager method, replace the
broad Exception handler around importing parse_command and
has_unresolvable_expansion with an ImportError handler only. Preserve the
existing return None fallback for genuine import failures while allowing
internal module errors such as SyntaxError or NameError to propagate.

Sources: Coding guidelines, Linters/SAST tools

Address two greptile P1 findings on PR #3150:
- command_parser: broaden unsafe-expansion detector to match bare $VAR
  (e.g. `rm -rf $HOME`) in addition to $IFS/${...}; $(...) and backticks
  remain excluded so command substitution stays on the deny-checked
  decomposition path.
- memory.reset_long_term: re-raise on Chroma delete_collection failure so
  a failed reset is not silently reported as success while long-term
  memories remain available.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison MervinPraison added pipeline/blocked:stale-final Blocked: FINAL stale after new commits and removed pipeline/blocked:stale-final Blocked: FINAL stale after new commits labels Jul 18, 2026
# pattern only accepts ``${`` or ``$`` followed by an identifier char, so
# ``$(`` (command substitution) never matches. Only parameter expansion,
# which cannot be statically resolved, is treated as unverifiable.
_UNSAFE_EXPANSION_RE = re.compile(r"\$(?:\{|[A-Za-z_])")

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 security Arithmetic expansion bypass remains

The pattern excludes $((...)) because the character after $ is (. Bash evaluates this expression at runtime, so a broad bash:* allow can still override a deny for the expanded command. For example, a deny for bash:rm -rf /tmp/1 does not match rm -rf /tmp/$((1)), which Bash later executes against the denied path. Detect arithmetic expansion while continuing to exclude $(...) command substitution.

Suggested change
_UNSAFE_EXPANSION_RE = re.compile(r"\$(?:\{|[A-Za-z_])")
_UNSAFE_EXPANSION_RE = re.compile(r"\$(?:\{|\(\(|[A-Za-z_])")

@praisonai-triage-agent praisonai-triage-agent Bot removed the pipeline/blocked:cooldown Blocked: post-push or @claude cooldown label Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:manual-review Blocked: requires manual review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shell-permission bypass via $IFS expansion, global litellm.callbacks overwrite, and cross-agent Memory/Knowledge store collisions in praisonaiagents

1 participant