Skip to content

fix: contain and bound skill supporting file reads - #11342

Merged
jbg merged 7 commits into
mainfrom
jbg/security-skill-file-size-bound
Aug 20, 2026
Merged

jbg merged 7 commits into
mainfrom
jbg/security-skill-file-size-bound

Conversation

@jbg

@jbg jbg commented Aug 19, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • open file-backed source content relative to validated roots without following link/reparse-point escapes
  • share the protected reader between legacy and state-machine skill paths and extend it to Recipe, Subrecipe, Agent, and Project source-loading paths
  • make Agent delegation consume the secured discovery snapshot rather than reopening the source path
  • enforce the character-based response budget while bounding encoded reads to four bytes per allowed character
  • cover nested legitimate files, search-only ancestors, symlinked sources/ancestors, root replacement, swap races, multibyte and wrapped-response limits, and streamed-read ceilings

Security invariant

File-backed trusted-source reads must stay bound to their discovered source root and must not allocate or return more than the configured response budget.

Verification

  • cargo fmt --all -- --check
  • cargo test -p goose skills::supporting_files::tests --lib — 10 passed
  • cargo test -p goose agents::platform_extensions::summon::tests --lib — 52 passed
  • cargo test -p goose recipe::read_recipe_file_content::tests --lib — 3 passed
  • cargo test -p goose sources:: --lib — 20 passed
  • cargo build -p goose
  • cargo clippy -p goose --all-targets -- -D warnings
  • git diff origin/main...HEAD --check

Windows-specific handle traversal was reviewed against Goose's existing NtCreateFile implementation; the available validation host was macOS/aarch64.

Addresses https://github.com/project-loupe/audit-goose/issues/860
Addresses https://github.com/project-loupe/audit-goose/issues/862

This finding was discovered by Project Loupe

jbg added 2 commits August 19, 2026 14:52
Signed-off-by: Jasper Hugo <jasper@spiral.xyz>
Signed-off-by: Jasper Hugo <jasper@spiral.xyz>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 50e3037812

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/goose/src/skills/supporting_files.rs Outdated
Signed-off-by: Jasper Hugo <jasper@spiral.xyz>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 76216b95a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/goose/src/skills/supporting_files.rs Outdated
Signed-off-by: Jasper Hugo <jasper@spiral.xyz>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7c412b8f18

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/goose/src/skills/supporting_files.rs
Signed-off-by: Jasper Hugo <jasper@spiral.xyz>
@jbg
jbg requested review from DOsinga, alexhancock and jamadeo August 19, 2026 13:54
@DOsinga

DOsinga commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

This currently protects only supporting files loaded through load_skill, with the call sites restricted to SourceType::Skill | SourceType::BuiltinSkill. We should expand this so the same secure, bounded file-reading behavior applies to all relevant source types, not just skills; otherwise recipes, agents, and other file-backed sources may retain the same class of path-escape, race, and unbounded-read problems. Please audit the other source-loading paths and either include them here or explain why they cannot hit the same issue.

@michaelneale michaelneale left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think makes sense. Small chance this may surprise some users of skills but the way to resolve that is to put things in sensible places! nice.

jbg added 2 commits August 20, 2026 12:28
* origin/main: (59 commits)
  chore(deps): bump the ui-minor-and-patch group across 1 directory with 53 updates (#11386)
  fix(security): bound call graph traversal (#11193)
  fix: pin arrayref to known-good commit (#11389)
  feat(providers): add SayGM as declarative OpenAI-compatible provider (#11267)
  feat(providers): custom provider cost fields drive cost tracking (config-declared pricing fallback) (#11220)
  fix(deps): repair dangling syn reference in Cargo.lock (#11385)
  fix(flake): add cudaforge hash for git dependency (#10910)
  feat: auto-focus chat input when user starts typing (#11184)
  fix(security): fail closed on invalid Codex ACP mode (#11362)
  fix(mcp): keep stdio extensions alive across worker exits (#10364)
  feat(ui): collapse scheduled job sessions into accordion in chat history (#11265)
  fix: bound retry command diagnostics (#11365)
  Disable thinking for tool call labels (#11207)
  fix(review): contain REVIEW.md discovery (#11367)
  fix(acp): preserve tool result audience metadata (#11375)
  test(providers): isolate environment-proxy test in its own binary (#11262)
  fix(security): bind Foundry API keys to request origin (#11347)
  fix: canonicalize mangled tool names before permission inspection (follow-up to #10230) (#10285)
  feat(providers): add Lynkr as a declarative OpenAI-compatible provider (#11372)
  fix: sanitize Pi imported output (#10990)
  ...
@jbg

jbg commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed in 62f3743115.

I audited each file-backed SourceType and extended the same descriptor-confined, bounded reader beyond skill supporting files:

  • Recipe and Subrecipe loading now use it through the shared recipe-file path.
  • Agent and Recipe discovery use it and reject symlinked source files; Agent delegation consumes the already-secured discovery snapshot instead of reopening the path.
  • Skill, Project, and Agent source listing, property reads, and export paths now use the same primitive.
  • BuiltinSkill already shares the Skill supporting-file path. Recipe/Subrecipe source CRUD is unsupported, but their executable loading path is covered above.

Added symlink regressions for Recipe and Agent discovery. Verification includes the focused supporting-file, summon, recipe, and source suites (20 source tests), cargo build -p goose, formatting, and cargo clippy -p goose --all-targets -- -D warnings.

— AI-generated by Codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62f3743115

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +29 to +32
let content = crate::skills::read_source_file_with_limit(
&parent_dir,
Path::new(file_name),
crate::agents::max_tool_response_size(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Decouple recipe reads from the tool-response threshold

When GOOSE_MAX_TOOL_RESPONSE_SIZE is lowered to control when tool output is offloaded, this now also rejects any recipe whose source exceeds that character count, so an otherwise valid recipe can no longer be discovered or run before any tool response exists. The setting is specifically a tool-response threshold, and this path previously read recipes without that cap; use a separate source-file safety limit rather than coupling recipe loading to this configuration.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid finding. I missed this unresolved thread before #11342 entered the merge queue.

Corrected in 7bac7a9a73 and follow-up PR #11391: trusted source reads now use a fixed 1 MiB byte limit grounded in the existing scheduled-recipe ceiling, while skill supporting-file responses retain the independent character limit from GOOSE_MAX_TOOL_RESPONSE_SIZE. Added regressions for a recipe above the default tool-response threshold and an over-limit source file; focused tests and clippy pass.

I am leaving this thread unresolved until the follow-up merges.

— AI-generated by Codex

@jbg
jbg added this pull request to the merge queue Aug 20, 2026
Merged via the queue into main with commit df89965 Aug 20, 2026
26 checks passed
@jbg
jbg deleted the jbg/security-skill-file-size-bound branch August 20, 2026 10:57
alexhancock added a commit that referenced this pull request Aug 20, 2026
…bined

* origin/main: (85 commits)
  feat(desktop): sort configured providers to the top of the provider list (#11409)
  fix(cli): refuse symlink diagnostics outputs (#11398)
  test(plugins): isolate GOOSE_PATH_ROOT in discovery tests (#11407)
  fix(config): serialize secret mutations (#11388)
  fix: decouple source file and tool response limits (#11391)
  chore(deps): bump pctx_code_mode from 0.4.1 to 0.5.0 (#11245)
  fix(security): suppress sensitive OTLP traces (#11381)
  feat(openrouter): forward session_id and add app category header (#10868)
  feat(acp): derive and forward thinking effort from the ACP harness (#10949)
  fix(aws_bedrock): replace flat model list with routing table, add Gemma 4 Mantle support (#10297)
  Add GPT-5.6 follow-up support for Codex and Responses API (#10460)
  fix(update): fetch attestation bundles from bundle_url (#10557)
  fix(security): fail closed on invalid default GCP credentials (#11363)
  fix(codex): reject socket-backed MCP extensions (#11304)
  fix: pass complete response to stop hooks (#11366)
  fix: contain and bound skill supporting file reads (#11342)
  chore(deps): bump the ui-minor-and-patch group across 1 directory with 53 updates (#11386)
  fix(security): bound call graph traversal (#11193)
  fix: pin arrayref to known-good commit (#11389)
  feat(providers): add SayGM as declarative OpenAI-compatible provider (#11267)
  ...

# Conflicts:
#	crates/goose/src/agents/agent.rs
leanzero-srl pushed a commit to leanzero-srl/goose-local-edition that referenced this pull request Sep 6, 2026
…rn's context; skill file reads are bounded

A local 27B rarely decides to call search_memories or load_skill. The new `recall` platform
extension (default on, no tools) takes the user's own words on each request turn — never during a
tool loop — drops function words, searches the memory store (when the memory extension is on) and
the skill catalogue (when skills is on), and contributes a turn-context part: `<recalled-memories>`
with the matching entries in full (an entry must share two terms with the request or carry one in
its name; at most three) and `<relevant-skills>` naming skills to load (name match, or two
description terms; at most three). No match, no block; an unreadable store is logged, never faked.
Its one system-prompt instruction explains the two blocks; moim's template is untouched, so every
other agent's prompt is byte-identical — swarm workers get only `developer` plus the swarm config's
extensions, so they never carry it.

Skills: a supporting file is cut at GOOSE_MAX_TOOL_RESPONSE_SIZE with the cut announced in the
result (upstream aaif-goose#11342's intent, ported by hand around our rmcp 1.4 client); tracing::info on
every skill and file load.

Tests: 5 for recall's pure functions (request-turn detection, stopwords, hit selection, skill
ranking, rendering); prompt_manager snapshot carries the new section.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvuBB2cVVZ8Herdq3zyuMQ
leanzero-srl pushed a commit to leanzero-srl/goose-local-edition that referenced this pull request Sep 6, 2026
…rn's context; skill file reads are bounded

A local 27B rarely decides to call search_memories or load_skill. The new `recall` platform
extension (default on, no tools) takes the user's own words on each request turn — never during a
tool loop — drops function words, searches the memory store (when the memory extension is on) and
the skill catalogue (when skills is on), and contributes a turn-context part: `<recalled-memories>`
with the matching entries in full (an entry must share two terms with the request or carry one in
its name; at most three) and `<relevant-skills>` naming skills to load (name match, or two
description terms; at most three). No match, no block; an unreadable store is logged, never faked.
Its one system-prompt instruction explains the two blocks; moim's template is untouched, so every
other agent's prompt is byte-identical — swarm workers get only `developer` plus the swarm config's
extensions, so they never carry it.

Skills: a supporting file is cut at GOOSE_MAX_TOOL_RESPONSE_SIZE with the cut announced in the
result (upstream aaif-goose#11342's intent, ported by hand around our rmcp 1.4 client); tracing::info on
every skill and file load.

Tests: 5 for recall's pure functions (request-turn detection, stopwords, hit selection, skill
ranking, rendering); prompt_manager snapshot carries the new section.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvuBB2cVVZ8Herdq3zyuMQ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants