Commit c3e99ac
fix(security): isToolCallable fail-closed on non-not-found storage errors
The new isToolCallable check returned true (callable) for any non-nil
GetToolApproval error other than the new ErrToolApprovalNotFound
sentinel. A transient BBolt mmap-remap during compaction, a decode
failure, or any other read error would silently re-enable a tool the
operator had previously persisted as Disabled=true — the inverse of
the Spec 032 rug-pull bypass that setToolEnabledNoEmit already guards
against on the write side.
Mirror that pattern on the read side:
switch {
case err == nil:
if approval != nil && approval.Disabled { return false }
case errors.Is(err, storage.ErrToolApprovalNotFound):
// no record → default callable (matches prior fallthrough)
default:
// real storage error → fail closed and log
return false
}
The "no record → callable" branch is preserved so the synthesis path
for never-toggled tools still works (writes happen lazily on first
toggle; reads before any toggle should still expose the tool by
default). A WARN log fires on the fail-closed path so operators can
correlate storage health with sudden tool unavailability.
Regression test: TestIsToolCallable_FailsClosedOnStorageError writes
corrupt non-JSON bytes directly into the tool_approvals bucket via the
storage manager's GetDB hook, then asserts isToolCallable returns
false. Exercises the real json.Unmarshal error path, not a mocked
storage layer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent f548de1 commit c3e99ac
2 files changed
Lines changed: 55 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4575 | 4575 | | |
4576 | 4576 | | |
4577 | 4577 | | |
4578 | | - | |
| 4578 | + | |
| 4579 | + | |
| 4580 | + | |
| 4581 | + | |
| 4582 | + | |
| 4583 | + | |
| 4584 | + | |
| 4585 | + | |
| 4586 | + | |
| 4587 | + | |
| 4588 | + | |
| 4589 | + | |
| 4590 | + | |
| 4591 | + | |
| 4592 | + | |
4579 | 4593 | | |
4580 | 4594 | | |
4581 | 4595 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
148 | 149 | | |
149 | 150 | | |
150 | 151 | | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
0 commit comments