mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 01:48:20 -10:00
fix(tools): grep_files, rlm open and the working set honor the read deny-list
Three more in-process readers handed file content to the model without
resolve_guarded_read_path or enforce_read_denylist, the guard that
read, image_ocr, pandoc and (since 4fa8fdda02) validate_data, review
and verify go through:
- grep_files resolved its root with plain resolve_path. The walk skips
denied entries, but a root that is itself a file was searched directly,
so `grep_files {path: ".env", pattern: "."}` returned every line. The
tool is read-only and auto-approved; it is also reached as
`File search_content` and the MCP server's `search`.
- rlm open with file_path read the file and returned a 500-character
preview, the same way.
- With CODEWHALE_CACHE_MAXIMAL, append_resident_file_contents embedded
every active working-set path in model-bound turn metadata. Paths are
recorded from tool inputs even when the read was refused, so a refused
`read .env` put the whole file into every later request. It now calls
enforce_read_denylist and leaves a one-line skip note with the path the
model already named.
Found by the 2026-10-17 safety hunt, round 3. The verifiers rated each
real with high confidence; the working-set entry is low severity because
it needs the opt-in env var. Receipts:
CW/artifacts/failopen-hunt-20261017/round3.json and round3-verify.json.
Verification:
- scripts/dev-test.sh tui tools:: core:: session_manager working_set
legacy_checkpoint subagent: 4085 run, 4077 passed. Of the 8 failures,
5 are tests updated in the next commit and 3 are the environmental
test_runner tests (nested cargo EPERM in this harness).
- scripts/dev-test.sh tui tools::search tools::rlm tools::fim
tools::workflow workflow_plan_approval tui::approval: 349 passed, 0
failed.
- clippy -p codewhale-tui --all-targets --all-features --locked with the
CI flags: clean. cargo fmt --check: clean. Blocking-call and dead-code
budgets pass.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NBKbhx19BRbZtvWK8rVKjz
This commit is contained in:
1 parent
b33f0e5d43
commit
864d0db02a
5 files changed
+24
-2
No files matched your search
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- `grep_files` (also `File search_content`) aimed at a single file, `rlm`
|
||||
`open` with `file_path`, and the cache-maximal working set now honor the
|
||||
read deny-list. Each read files such as `.env` past the check and returned
|
||||
their lines to the model. A deny-listed working-set file is skipped with a
|
||||
note.
|
||||
- A `tool_call_before` hook that answers `ask` now always shows the approval
|
||||
prompt, as `docs/HOOKS.md` promises. Before, an earlier "approve for
|
||||
session" or a conversation grant for the same command answered it
|
||||
|
||||
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- `grep_files` (also `File search_content`) aimed at a single file, `rlm`
|
||||
`open` with `file_path`, and the cache-maximal working set now honor the
|
||||
read deny-list. Each read files such as `.env` past the check and returned
|
||||
their lines to the model. A deny-listed working-set file is skipped with a
|
||||
note.
|
||||
- A `tool_call_before` hook that answers `ask` now always shows the approval
|
||||
prompt, as `docs/HOOKS.md` promises. Before, an earlier "approve for
|
||||
session" or a conversation grant for the same command answered it
|
||||
|
||||
@@ -856,7 +856,7 @@ async fn load_source(
|
||||
context: &ToolContext,
|
||||
) -> Result<(String, String, Option<String>), ToolError> {
|
||||
if let Some(path) = rlm_open_source_field(input, "file_path").map(str::trim) {
|
||||
let resolved = context.resolve_path(path)?;
|
||||
let resolved = crate::tools::file::resolve_guarded_read_path(context, path, "rlm_open")?;
|
||||
let body = tokio::fs::read_to_string(&resolved).await.map_err(|e| {
|
||||
ToolError::execution_failed(format!("rlm_open: read {}: {e}", resolved.display()))
|
||||
})?;
|
||||
|
||||
@@ -181,7 +181,8 @@ impl ToolSpec for GrepFilesTool {
|
||||
.map_err(|e| ToolError::invalid_input(format!("Invalid regex pattern: {e}")))?;
|
||||
|
||||
// Resolve search path
|
||||
let search_path = context.resolve_path(path_str)?;
|
||||
let search_path =
|
||||
crate::tools::file::resolve_guarded_read_path(context, path_str, "grep_files")?;
|
||||
|
||||
let workspace = context.workspace.clone();
|
||||
let cancel_token = context.cancel_token.clone();
|
||||
|
||||
@@ -1165,6 +1165,17 @@ impl WorkingSet {
|
||||
}
|
||||
|
||||
let abs = workspace.join(&entry.path);
|
||||
if crate::tools::file::enforce_read_denylist(&abs, "working_set").is_err() {
|
||||
if !header_pushed {
|
||||
lines.push("### Active file contents (cache-resident)".to_string());
|
||||
header_pushed = true;
|
||||
}
|
||||
lines.push(format!(
|
||||
"<!-- file: {} (read deny-list, skipped) -->",
|
||||
entry.path
|
||||
));
|
||||
continue;
|
||||
}
|
||||
let body = match std::fs::read_to_string(&abs) {
|
||||
Ok(text) => text,
|
||||
Err(_) => {
|
||||
|
||||
Reference in new issue
Block a user