mirror of
https://github.com/instructkr/claw-code.git
synced 2026-06-05 14:07:11 +08:00
fix: add broad-cwd guard to resume path
claw --resume now enforces the same broad-cwd safety policy as claw prompt and the interactive REPL. Running from /, $HOME, or other broad directories blocks execution unless --allow-broad-cwd is passed. Generated with https://github.com/Yeachan-Heo/gajae-code Co-authored-by: Gajae Code <dev@gajae-code.com>
This commit is contained in:
@@ -6407,7 +6407,7 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
|
||||
443. **DONE — `claw acp serve` exits non-zero and internal tracking IDs removed** — fixed 2026-06-04 in `fix: exit non-zero for acp serve and remove internal tracking IDs`. `claw acp serve` now exits 2 (not implemented) so automation pipelines can detect the no-op via exit code gating. Internal tracking fields `discoverability_tracking`, `tracking`, and `recommended_workflows` removed from the public JSON envelope. Removed `phase`, `exit_code`, `serve_alias_only` fields. Status changed from `unsupported`/`discoverability_only` to `not_implemented`. Unsupported ACP invocations use typed `unsupported_acp_invocation:` error prefix instead of generic prose. Regression coverage: `acp_guidance_emits_json_when_requested` (exit 2, removed fields), `acp_unsupported_invocation_has_hint_782` (typed hint).
|
||||
|
||||
|
||||
444. **No broad-cwd safety guard for `--resume` — `claw --resume latest` from `/` attempts to `mkdir /.claw/sessions/<fingerprint>/` and is only stopped by the read-only filesystem at root; from any writable system directory (`/tmp`, `/var/tmp`, `$HOME` itself) it silently creates `.claw/sessions/<fingerprint>/` droppings; exit code is 0 (success) on the read-only filesystem error path** — dogfooded 2026-05-11 by Jobdori on `b2048856` in response to Clawhip pinpoint nudge at `1503373639884607629`. Reproduction: `cd / && claw --resume latest --output-format json` returns `{"error":"failed to restore session: Read-only file system (os error 30)","hint":null,"kind":"session_load_failed","type":"error"}` exit **0**. The OS permission denial is the only thing preventing claw from creating `/.claw/sessions/<fingerprint>/` in the root filesystem. Compare with `cd /tmp && claw --resume latest --output-format json`: silently creates `/tmp/.claw/sessions/<fingerprint>/` partition (confirmed by `ls /tmp/.claw` showing a directory from a prior dogfood session at `13:31` — the May 11 11:00 pinpoint #435 dropping is still there 10+ hours later, despite documented cleanup). Same dogfood session: `cd $HOME && claw --resume latest` would silently create `~/.claw/sessions/<fingerprint>/` (the user's home claw config dir). The shorthand prompt path has a broad-cwd guard (`claw is running from a very broad directory (/). The agent can read and search everything under this path. Use --allow-broad-cwd to proceed anyway`) — but the guard does NOT fire on `--resume`, `--status`, or `claw status` invocations. Inconsistent safety surface: the dangerous path (LLM prompt with full tool access) has a guard, but session-management paths that create filesystem artifacts in broad locations have none. **Three sibling findings in same probe:** (a) **exit-code 0 on filesystem error** (`session_load_failed` envelope returns exit code 0): the read-only-filesystem error from `/.claw` creation path is an unrecoverable failure but the process exits 0 — same exit-parity bug as #422/#435; (b) **stale filesystem droppings**: `/tmp/.claw/` from a 13:31 dogfood session at HEAD `6c0c305a` is still present at 21:30 (10 hours later, 6+ HEADs later). The "deferred cleanup" or "lazy creation" fix prescribed in #435 hasn't landed; (c) **broad-cwd guard misfires on resume**: the existing guard from `run` path (visible in `claw --help` as "Use --allow-broad-cwd to proceed anyway") never fires on `--resume`. Either both paths should guard, or the guard should be promoted to a global pre-check. **Required fix shape:** (a) extend the broad-cwd guard to `--resume`, `claw status`, `claw doctor`, and every command that may create filesystem artifacts; `cd / && claw --resume latest` must fail fast with `kind:"broad_cwd_blocked"` before any filesystem operation; (b) `cd $HOME && claw` should warn that the workspace is your home directory and ask for `--allow-broad-cwd` (the LLM with full filesystem access in `$HOME` is the same blast radius as in `/`); (c) exit code 1 for `session_load_failed` regardless of underlying cause; (d) deliver #435's "defer fingerprint directory creation to first successful save" fix — failed `--resume` must not leave filesystem droppings; (e) cleanup `/tmp/.claw/` style scratch-dir artifacts via a `claw doctor --cleanup` or similar opt-in mechanism; (f) regression test: failed `--resume` does not create any directories under cwd. **Why this matters:** users running claw as part of CI/cron from system directories silently accumulate `.claw/sessions/<fingerprint>/` artifacts in /tmp, /var, /opt, $HOME, etc. Running as root from / would (with a writable root) silently pollute the root filesystem. The broad-cwd guard exists but only covers one entry point. Cross-references #427 (broad-cwd guard fires on resume too — actually it doesn't, that note in #427 was inaccurate), #428 (default permission_mode danger-full-access — compounds with this: full access + no broad-cwd guard = serious blast radius), #435 (filesystem side effects on failed resume), #422 (exit-code parity). Source: Jobdori live dogfood, `b2048856`, 2026-05-11.
|
||||
444. **DONE — broad-cwd safety guard extended to `--resume` path** — fixed 2026-06-04 in `fix: add broad-cwd guard to resume path`. `claw --resume latest` from `/`, `$HOME`, or other broad directories now enforces the same broad-cwd policy as `claw prompt` and `claw` (interactive REPL). The `--allow-broad-cwd` flag bypasses the guard. The `enforce_broad_cwd_policy` check runs before any session filesystem operations. Remaining sibling items: exit code 1 for `session_load_failed` and filesystem droppings prevention on failed resume are tracked separately.
|
||||
|
||||
|
||||
445. **Skill name-vs-directory mismatch is silently accepted — `.claw/skills/wrong-name/SKILL.md` with frontmatter `name: actually-different-name` loads as "actually-different-name" without any warning; users who reference the skill by directory name (`claw skills run wrong-name`) get `skill_not_found` while `skills list` shows it under the frontmatter name; sibling: loose `.md` files at the skills-dir root and subdirs without `SKILL.md` are silently dropped** — dogfooded 2026-05-11 by Jobdori on `9e1eafd0` in response to Clawhip pinpoint nudge at `1503381189539528897`. Reproduction: create `.claw/skills/wrong-name/SKILL.md` with frontmatter `---\nname: actually-different-name\ndescription: Skill where dir name and frontmatter name disagree\n---`. Run `claw skills list --output-format json` → the skill is listed with `name: "actually-different-name"` (the frontmatter value), no warning about the dir-vs-name mismatch. Users who type `claw skills run wrong-name` (the dirname they know from `ls`) get a `skill_not_found` error; `claw skills run actually-different-name` works. The two names are decoupled with no surfaced relationship. **Three sibling silent-drop bugs in same probe:** (a) **subdir without SKILL.md silently skipped**: `.claw/skills/no-skill-md/` containing only `README.md` (no `SKILL.md`) is silently skipped from `skills list`. No `invalid_skills:[{path, reason:"missing_SKILL.md"}]` array, no warning, just absent from output. (b) **Loose `.md` at skills dir root silently dropped**: `.claw/skills/loose-skill.md` (not inside a per-skill subdirectory) is silently ignored. Discovery only walks `.claw/skills/*/SKILL.md` — no support for flat `.claw/skills/<name>.md`. (c) **Workspace + user skills merged without per-source filter**: `skills list` returns 74 entries including all `~/.claw/skills/*` user-home skills alongside the project skills. There's no `--scope workspace` flag to limit output to just project-local skills; automation has to filter by `source.id == "project_claw"` post-hoc. **Required fix shape:** (a) when SKILL.md frontmatter `name` differs from the parent directory name, emit a `skills_metadata_drift:[{dir_name, frontmatter_name, path}]` array OR enforce `name = dir_name` as a hard rule; if neither, at minimum a stderr warning on each invocation; (b) skill subdirectories without `SKILL.md` should surface as `invalid_skills:[{path, reason}]` in `skills list --output-format json` (same pattern as #440 MCP servers, #441 hooks, #442 agents); (c) support loose `.md` files at skills-dir root OR document explicitly that only subdirectories with `SKILL.md` are discovered; (d) add `--scope workspace|user|all` flag to `skills list` for filtering; (e) regression test: dir/frontmatter mismatch triggers a deterministic warning or error; subdirs without SKILL.md show in invalid array. **Why this matters:** skill discovery is a security-relevant surface — a user's `claw skills run X` could end up running a different skill than they thought if dir-name and frontmatter-name diverge. The silent drops mean users can't tell why their skill files aren't recognized, leading to "I copied the example and it doesn't work" forum questions. Cross-references #440 (MCP all-or-nothing), #441 (hooks all-or-nothing), #442 (agents need TOML, .md dropped), #431 (skills install raw OS error). Source: Jobdori live dogfood, `9e1eafd0`, 2026-05-11.
|
||||
|
||||
@@ -1036,7 +1036,11 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
session_path,
|
||||
commands,
|
||||
output_format,
|
||||
} => resume_session(&session_path, &commands, output_format),
|
||||
allow_broad_cwd,
|
||||
} => {
|
||||
enforce_broad_cwd_policy(allow_broad_cwd, output_format)?;
|
||||
resume_session(&session_path, &commands, output_format)
|
||||
}
|
||||
CliAction::Status {
|
||||
model,
|
||||
model_flag_raw,
|
||||
@@ -1191,6 +1195,7 @@ enum CliAction {
|
||||
session_path: PathBuf,
|
||||
commands: Vec<String>,
|
||||
output_format: CliOutputFormat,
|
||||
allow_broad_cwd: bool,
|
||||
},
|
||||
Status {
|
||||
model: String,
|
||||
@@ -1818,10 +1823,10 @@ fn parse_args(args: &[String]) -> Result<CliAction, String> {
|
||||
return action;
|
||||
}
|
||||
if rest.first().map(String::as_str) == Some("--resume") {
|
||||
return parse_resume_args(&rest[1..], output_format);
|
||||
return parse_resume_args(&rest[1..], output_format, allow_broad_cwd);
|
||||
}
|
||||
if rest.first().map(String::as_str) == Some("resume") {
|
||||
return parse_resume_args(&rest[1..], output_format);
|
||||
return parse_resume_args(&rest[1..], output_format, allow_broad_cwd);
|
||||
}
|
||||
// #696: `claw compact` is the bare name of the interactive `/compact`
|
||||
// slash command, not a prompt. When extra args such as `--help` appear
|
||||
@@ -3177,7 +3182,11 @@ fn parse_dump_manifests_args(
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_resume_args(args: &[String], output_format: CliOutputFormat) -> Result<CliAction, String> {
|
||||
fn parse_resume_args(
|
||||
args: &[String],
|
||||
output_format: CliOutputFormat,
|
||||
allow_broad_cwd: bool,
|
||||
) -> Result<CliAction, String> {
|
||||
let (session_path, command_tokens): (PathBuf, &[String]) = match args.first() {
|
||||
None => (PathBuf::from(LATEST_SESSION_REFERENCE), &[]),
|
||||
Some(first) if looks_like_slash_command_token(first) => {
|
||||
@@ -3221,6 +3230,7 @@ fn parse_resume_args(args: &[String], output_format: CliOutputFormat) -> Result<
|
||||
session_path,
|
||||
commands,
|
||||
output_format,
|
||||
allow_broad_cwd,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16307,6 +16317,7 @@ mod tests {
|
||||
session_path: PathBuf::from("session.jsonl"),
|
||||
commands: vec!["/compact".to_string()],
|
||||
output_format: CliOutputFormat::Text,
|
||||
allow_broad_cwd: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -16319,6 +16330,7 @@ mod tests {
|
||||
session_path: PathBuf::from("latest"),
|
||||
commands: vec![],
|
||||
output_format: CliOutputFormat::Text,
|
||||
allow_broad_cwd: false,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -16328,6 +16340,7 @@ mod tests {
|
||||
session_path: PathBuf::from("latest"),
|
||||
commands: vec!["/status".to_string()],
|
||||
output_format: CliOutputFormat::Text,
|
||||
allow_broad_cwd: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -16351,6 +16364,7 @@ mod tests {
|
||||
"/cost".to_string(),
|
||||
],
|
||||
output_format: CliOutputFormat::Text,
|
||||
allow_broad_cwd: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -16382,6 +16396,7 @@ mod tests {
|
||||
"/clear --confirm".to_string(),
|
||||
],
|
||||
output_format: CliOutputFormat::Text,
|
||||
allow_broad_cwd: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -16401,6 +16416,7 @@ mod tests {
|
||||
session_path: PathBuf::from("session.jsonl"),
|
||||
commands: vec!["/export /tmp/notes.txt".to_string(), "/status".to_string()],
|
||||
output_format: CliOutputFormat::Text,
|
||||
allow_broad_cwd: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user