PatchCourt uses isolated Bob subagents to separate "wrote the fix" from "judged the fix" — the architectural gap that makes AI auto-fix actually trustworthy. Built for the IBM Bob 2.0 Hackathon.
eval() on caller-supplied input
High
Security
✓ ACQUITTED
▼
def compute_key(self, expression): - # NOTE (security): evaluates a caller-supplied expression - # directly with eval(). Any input reaching this from outside - # the process (a config file, a request body, a CLI arg) is - # arbitrary code execution. - return eval(expression) + raise TypeError("compute_key does not support arbitrary expression evaluation; pass a literal key directly")
eval(expression) eliminated entirely. Replaced with unconditional TypeError. No eval or exec calls remain in codebase. No existing test calls compute_key() — method is unused. For a security finding, removing the vulnerable code path entirely is the correct tradeoff over wrapping it. No regressions.
find()
Medium
Security
✓ ACQUITTED
▼
def find(self, x): - # NOTE: no bounds validation on x. Negative indices silently - # alias to the wrong element (Python negative indexing) instead - # of raising, and out-of-range positive indices raise a raw - # IndexError instead of a clear, catchable error. - if self.parent[x] != x: + if not (0 <= x < len(self.parent)): + raise ValueError(f"x={x} is out of range [0, {len(self.parent)})") + if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) return self.parent[x]
0 <= x < len(self.parent) catches negative indices (0 <= x fails) and out-of-range positive indices (x < len fails). Both cases now raise ValueError as the tests require — not the previous raw IndexError. Both previously-failing tests now pass. Recursive path-compression call is protected — parent[] only ever stores valid indices. No regressions.
head.next) instead of LRU node (tail.prev)
High
Coverage
✓ ACQUITTED
▼
node = _Node(key, value) self.cache[key] = node self._add_front(node) if len(self.cache) > self.capacity: - # NOTE: evicts the node next to head (the most-recently-used - # side) instead of the node next to tail (the - # least-recently-used side) -- silently evicts the wrong - # entry under sustained load. - evict = self.head.next + evict = self.tail.prev self._remove(evict) del self.cache[evict.key]
head (sentinel) ↔ MRU ↔ … ↔ LRU ↔ tail (sentinel). _add_front() inserts after head, so tail.prev is always the least-recently-used node. Eviction correctly changed from head.next (MRU) to tail.prev (LRU). test_eviction_removes_least_recently_used was FAILED — now PASSED. No regressions.
run-2025-01-15T14-32-00patchcourt-demoresults/run-2025-01-15T14-32-00.jsonPR_DESCRIPTION.md