Skip to content

fix(builder): add approval context propagation for sub-tool execution#1125

Merged
ilblackdragon merged 11 commits intonearai:stagingfrom
roelven:fix/builder-approval-context
Mar 31, 2026
Merged

fix(builder): add approval context propagation for sub-tool execution#1125
ilblackdragon merged 11 commits intonearai:stagingfrom
roelven:fix/builder-approval-context

Conversation

@roelven
Copy link
Copy Markdown
Contributor

@roelven roelven commented Mar 13, 2026

Problem

The build_software tool bypassed tool approval checks when executing sub-tools (shell, write_file, etc.) because it called tool.execute() directly with a dummy JobContext::default(). This prevented the builder from working in autonomous contexts (web UI, routines).

Solution

Add approval_context field to JobContext and propagate it through the execution chain (scheduler → job context → tools → sub-tools). Tools like build_software can now set specific allowed sub-tools while maintaining security.

Changes

  • src/context/state.rs: Add approval_context: Option<ApprovalContext> field to JobContext with builder method
  • src/tools/tool.rs: Add check_approval_in_context() helper with is_blocked_or_default semantics (blocks non-Never tools when context is None)
  • src/worker/job.rs: Additive approval checks — BOTH job-level AND worker-level must approve (defense in depth). Uses AutonomousUnavailable error with descriptive reason.
  • src/agent/scheduler.rs: Propagate approval_context from dispatch to JobContext (single atomic update_context_and_get call)
  • src/tools/builder/core.rs: Create context with build-specific approval permissions, uses prepare_tool_params for parameter normalization
  • src/db/libsql/jobs.rs, src/history/store.rs: Initialize new field as None on job restoration (with TODO for future persistence)
  • tests/tool_approval_context.rs: Comprehensive tests including Never tools in additive model and builder unlisted tool blocking

Allowed Builder Tools

The builder explicitly allows: shell, read_file, write_file, list_dir, apply_patch

Note: http is intentionally excluded — shell commands (cargo, npm, pip) handle network access for dependency fetching.

Approval Semantics

  • ApprovalRequirement::Never — always allowed (no approval needed)
  • ApprovalRequirement::UnlessAutoApproved — allowed in autonomous contexts (autonomous = auto-approved)
  • ApprovalRequirement::Always — only allowed if explicitly listed in allowed_tools
  • When approval_context is None (no autonomous context): all non-Never tools are blocked (legacy/secure default)

Verification

cargo test tool_approval_context
cargo test --lib -- approval

References

🤖 Generated with Claude Code

@github-actions github-actions bot added scope: agent Agent core (agent loop, router, scheduler) scope: tool Tool infrastructure scope: tool/builder Dynamic tool builder scope: worker Container worker scope: docs Documentation size: L 200-499 changed lines risk: medium Business logic, config, or moderate-risk modules contributor: new First-time contributor labels Mar 13, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical security vulnerability where the build_software tool bypassed necessary approval checks when executing sub-tools, rendering it unusable in autonomous environments like the web UI or routines. The solution involves deeply integrating an approval context into the job execution flow, ensuring that all sub-tool invocations are properly vetted against defined permissions. This change significantly enhances the security and reliability of tool execution within autonomous contexts.

Highlights

  • Approval Context Propagation: The approval_context field has been added to JobContext and is now propagated through the execution chain (scheduler → job context → tools → sub-tools) to ensure proper tool approval checks.
  • Builder Tool Security: The build_software tool now explicitly sets specific allowed sub-tools (shell, read_file, write_file, list_dir, apply_patch, http) and performs approval checks before execution, preventing bypasses that previously occurred with a dummy JobContext::default().
  • Two-Level Approval System: A new two-level approval system has been introduced: a more specific job-level context (set by tools like the builder) and a worker-level fallback context (set by the scheduler for autonomous jobs).
  • New Helper Function: A check_approval_in_context() helper function was added to src/tools/tool.rs to allow tools to verify permissions before executing sub-tools, returning Err(ToolError::NotAuthorized) if blocked.
Changelog
  • src/agent/scheduler.rs
    • Updated dispatch_job_inner() to store the approval_context in JobContext.
    • Ensures approval context propagates from the scheduler to the worker and tools.
  • src/context/state.rs
    • Added approval_context: Option<ApprovalContext> field to JobContext struct.
    • Added with_approval_context() method to set the approval context.
    • Updated Default implementation to include ApprovalContext::autonomous().
    • Added import for ApprovalContext.
  • src/db/libsql/jobs.rs
    • Initialized the new approval_context field to None when restoring jobs.
  • src/history/store.rs
    • Initialized the new approval_context field to None when restoring jobs.
  • src/tools/builder/core.rs
    • Added imports for ApprovalContext and check_approval_in_context.
    • Updated execute_build_tool() to create a JobContext with build-specific approval permissions.
    • Calls check_approval_in_context() before executing sub-tools.
    • Explicitly allows shell, read_file, write_file, list_dir, apply_patch, and http tools.
  • src/tools/mod.rs
    • Exported the new check_approval_in_context function.
  • src/tools/tool.rs
    • Added check_approval_in_context() helper function to verify tool permissions.
    • This function allows tools to check approval before executing sub-tools.
    • It returns Err(ToolError::NotAuthorized) if a tool is blocked.
  • src/worker/job.rs
    • Moved job_ctx fetch before the approval check.
    • Implemented job-level approval context check, which takes precedence over worker-level.
    • Falls back to worker-level deps.approval_context if no job-level context is present.
  • tests/tool_approval_context.rs
    • Added comprehensive tests for approval context functionality.
    • Tests cover default context, with_approval_context, autonomous blocking, and builder tools.
Activity
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@roelven roelven force-pushed the fix/builder-approval-context branch from f324120 to 4f61a9a Compare March 13, 2026 13:49
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The pull request addresses an issue where the build_software tool bypassed tool approval checks in autonomous contexts by introducing a two-level approval system. This was achieved by adding an approval_context field to the JobContext struct, along with a with_approval_context method and a check_approval_in_context helper function. The build_software tool was updated to create a job-specific approval context that explicitly allows certain sub-tools (like shell and file operations) and uses the new helper function to enforce these permissions. Additionally, the worker's job execution logic was modified to prioritize job-level approval contexts over worker-level ones, and the scheduler was updated to propagate the approval context to jobs. New tests were added to validate this enhanced approval mechanism.

Copy link
Copy Markdown
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

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

Security Review

This PR modifies the tool approval system and needs careful attention.

Critical concerns:

1. JobContext::default() behavioral change (security regression risk)

Default::default() now includes ApprovalContext::autonomous(), which changes behavior for UnlessAutoApproved tools:

  • Before: default() had approval_context: None -> worker blocks UnlessAutoApproved
  • After: default() has Autonomous { allowed_tools: {} } -> allows UnlessAutoApproved

Any code path using JobContext::default() outside the builder will silently unblock UnlessAutoApproved tools. Recommend reverting Default to approval_context: None and only setting it explicitly in the builder.

2. Job-level context bypasses worker-level entirely

When job_ctx.approval_context is Some, the worker-level approval check is completely skipped. Consider making the checks additive (both must pass) rather than job-level-takes-precedence, to maintain defense in depth.

3. check_approval_in_context is more permissive than worker check

When approval_context is None, this function returns Ok(()) (allow all), while the worker blocks non-Never. This inconsistency could lead to privilege escalation if callers use this helper thinking it provides equivalent security.

4. Builder allows http -- justify this

Building WASM tools requires shell, file ops, apply_patch -- but why http? Please justify or remove.

Required changes:

  1. Revert JobContext::default() to approval_context: None
  2. Either make worker + job checks additive, or document why job-level override is safe
  3. Fix check_approval_in_context to block when context is None (match worker behavior)
  4. Justify or remove http from builder's allowed tools
  5. No real CI has run -- fork PR, only classify/scope

@G7CNF
Copy link
Copy Markdown
Contributor

G7CNF commented Mar 13, 2026

Focused on tool-calling blocker impact. I agree with the requested security changes before merge:

  1. keep JobContext::default() with approval_context: None (avoid silent broadening),
  2. make helper semantics match worker behavior when context is missing/none,
  3. document or enforce additive semantics for job + worker approval checks,
  4. justify/remove http in builder allowlist.

Once those are addressed, this should materially unblock sub-tool execution in autonomous flows.

roelven added a commit to roelven/ironclaw that referenced this pull request Mar 14, 2026
…hecks

This addresses the remaining security review concern from PR nearai#1125.

Previously, the worker used "precedence" semantics where job-level approval
context would completely bypass worker-level checks. This meant a tool's
job-level context could potentially override worker-level restrictions.

Changes:
- Worker now checks BOTH job-level AND worker-level approval contexts
- Tool is blocked if EITHER level blocks it (additive/intersection semantics)
- Maintains defense in depth: job-level cannot bypass worker-level restrictions

Tests added:
- test_additive_approval_semantics_both_levels_must_approve: verifies job-level
  blocks take effect even when worker-level allows
- test_additive_approval_worker_block_overrides_job_allow: verifies worker-level
  blocks take effect even when job-level allows
- test_additive_approval_both_levels_allow: verifies tool is allowed only when
  both levels approve

Security review reference:
- Issue nearai#3 from @G7CNF: "document or enforce additive semantics for job + worker
  approval checks"
- Issue nearai#2 from @zmanian: "Job-level context bypasses worker-level entirely"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@ilblackdragon ilblackdragon left a comment

Choose a reason for hiding this comment

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

Additional Review Findings

Beyond zmanian's existing feedback (no CI has run, scope classification):

Must-fix

  1. Merge conflicts — PR is CONFLICTING on staging. execute_build_tool on staging calls prepare_tool_params which the PR's version skips. After rebase, ensure prepare_tool_params is preserved.

  2. Error type regression — the PR changes the error from ToolError::AutonomousUnavailable (with a rich message listing available tools) to ToolError::AuthRequired { name } (generic). This breaks the existing test test_approval_context_returns_structured_autonomous_unavailable_error (line 2077) and degrades the user experience.

  3. PR description inaccuracy — lists http as an allowed builder tool, but the code only includes 5 tools (no http). The code comment even says "we don't need to grant direct http tool access."

Should-fix

  1. Duplicated approval context propagation in scheduler — identical if let Some(ref approval) = approval_context { ctx.approval_context = Some(approval.clone()); } blocks in both metadata and else branches. Move after the if/else.

  2. ApprovalRequirement::Never tools blocked by allowlistis_blocked ignores the _requirement parameter. Tools like echo or time with ApprovalRequirement::Never should be usable without being explicitly listed.

  3. Job restored from DB loses approval_context#[serde(skip)] means it's None after restart. Tools that worked will be blocked. Add a TODO comment.

Testing

  1. No test for Never tools in additive model — what happens when a Never tool has no approval_context?
  2. No test for builder's execute_build_tool with unlisted tool — the integration test creates a matching context but doesn't call the builder method.

roelven and others added 10 commits March 29, 2026 23:40
Add approval_context to JobContext so tools can propagate approval
information when executing sub-tools. This enables tools like
build_software to properly check approvals for shell, write_file, etc.

- Add approval_context: Option<ApprovalContext> field to JobContext
- Add with_approval_context() builder method
- Add check_approval_in_context() helper for tools to verify permissions
- Default JobContext now includes autonomous approval context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move job context fetch before approval check and add job-level
approval context checking. Job-level context takes precedence over
worker-level, allowing tools like build_software to set specific
allowed sub-tools while maintaining the fallback to worker-level
approval for normal operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Store approval_context from dispatch into JobContext so it's
available to tools during execution. This completes the chain:
scheduler -> job context -> tools -> sub-tools.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update build_software to create a JobContext with build-specific
approval permissions and check approval before executing sub-tools.
This allows the builder to work in autonomous contexts (web UI, routines)
while maintaining security by only allowing specific build-related tools.

Allowed tools: shell, read_file, write_file, list_dir, apply_patch, http

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When restoring jobs from database, set approval_context to None.
The context will be populated by the scheduler on next dispatch if needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add tests for:
- JobContext default includes approval_context
- with_approval_context() builder method
- Autonomous context blocks Always-approved tools unless explicitly allowed
- autonomous_with_tools allows specific tools
- Builder tool approval context configuration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit addresses all security concerns raised in PR review:

1. Revert JobContext::default() to approval_context: None
   - Previously set ApprovalContext::autonomous() which was too permissive
   - Secure default requires explicit opt-in for autonomous execution
   - Any code using JobContext::default() now correctly blocks non-Never tools

2. Fix check_approval_in_context() to match worker behavior
   - Previously returned Ok(()) when approval_context was None (insecure)
   - Now uses ApprovalContext::is_blocked_or_default() for consistency
   - Prevents privilege escalation through sub-tool execution paths

3. Remove "http" from builder's allowed tools
   - Building software doesn't require direct http tool access
   - Shell commands (cargo, npm, pip) handle dependency fetching
   - Reduces attack surface for builder tool execution

4. Update tests to reflect new secure defaults
   - Tests now verify JobContext::default() blocks non-Never tools
   - New test added for secure default behavior

Security review references:
- Issue nearai#1: JobContext::default() behavioral change
- Issue nearai#3: check_approval_in_context more permissive than worker check
- Issue nearai#4: Builder allows http without justification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hecks

This addresses the remaining security review concern from PR nearai#1125.

Previously, the worker used "precedence" semantics where job-level approval
context would completely bypass worker-level checks. This meant a tool's
job-level context could potentially override worker-level restrictions.

Changes:
- Worker now checks BOTH job-level AND worker-level approval contexts
- Tool is blocked if EITHER level blocks it (additive/intersection semantics)
- Maintains defense in depth: job-level cannot bypass worker-level restrictions

Tests added:
- test_additive_approval_semantics_both_levels_must_approve: verifies job-level
  blocks take effect even when worker-level allows
- test_additive_approval_worker_block_overrides_job_allow: verifies worker-level
  blocks take effect even when job-level allows
- test_additive_approval_both_levels_allow: verifies tool is allowed only when
  both levels approve

Security review reference:
- Issue nearai#3 from @G7CNF: "document or enforce additive semantics for job + worker
  approval checks"
- Issue nearai#2 from @zmanian: "Job-level context bypasses worker-level entirely"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Restore requirement-aware is_blocked() semantics: Never and
  UnlessAutoApproved tools pass in autonomous context, Only Always
  tools require explicit allowlist entry
- Use AutonomousUnavailable error (with descriptive reason) instead
  of generic AuthRequired for approval blocking in worker
- Deduplicate approval_context propagation in scheduler dispatch
  (single update_context_and_get call instead of duplicated blocks)
- Remove http from builder tool allowlist (shell handles network)
- Add TODO comments for serde(skip) losing approval_context on DB
  restore in both libsql and postgres backends
- Add tests: Never tools in additive model, builder unlisted tool
  blocking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ilblackdragon ilblackdragon force-pushed the fix/builder-approval-context branch from 4c2d10f to 4ffea34 Compare March 30, 2026 15:51
@github-actions github-actions bot added size: XL 500+ changed lines and removed size: L 200-499 changed lines labels Mar 30, 2026
Copy link
Copy Markdown
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

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

Review -- REQUEST_CHANGES

Critical: is_blocked() semantic change silently broadens autonomous permissions

The PR changes is_blocked so that UnlessAutoApproved tools are automatically allowed in ANY autonomous context, regardless of the allowlist. This is a significant policy escalation affecting all autonomous execution paths, not just the builder.

Before: autonomous_with_tools(["shell"]) blocks write_file, read_file, http, etc.
After: autonomous_with_tools(["shell"]) allows ALL UnlessAutoApproved tools (write_file, read_file, shell non-destructive, http, list_dir, apply_patch, etc.) -- the allowlist only gates Always-requirement tools.

This means any routine, background job, or web UI dispatch scoped to ["memory_search"] now implicitly gets write_file, shell, http, etc. The allowed_tools list becomes a gate for destructive commands only, not a restrictive boundary.

Question for discussion: Is this the intended security model? If so, it needs explicit documentation and an audit of all autonomous_with_tools call sites. If the allowlist is meant to be restrictive, revert the UnlessAutoApproved line to !allowed_tools.contains(tool_name).

Medium

  • M1: Builder's allowlist is effectively redundant for 4 of 5 tools under the new semantics (only shell with destructive commands is actually gated)
  • M2: PR description says http is in the allowlist but code excludes it
  • M3: Postgres DB restore path not updated (only libSQL and history/store shown)
  • Merge conflicts with staging -- needs rebase

Low

  • #[serde(skip)] means approval context lost on restart (documented with TODO)
  • needs_update guard is always true

@ilblackdragon
Copy link
Copy Markdown
Member

Addressing Review Feedback

All items from the three reviews have been addressed. Here's the status of each:


zmanian's Security Review (review 1) — all 4 items resolved

# Concern Status How
1 JobContext::default() includes ApprovalContext::autonomous() ✅ Fixed Default uses with_user() which sets approval_context: None
2 Job-level bypasses worker-level ✅ Fixed Additive semantics: job_level_blocked || worker_level_blocked
3 check_approval_in_context allows all when None ✅ Fixed Uses is_blocked_or_default() — blocks non-Never when None
4 Builder allows http ✅ Fixed Removed. Shell handles network for deps

ilblackdragon's Review — all 8 items resolved

# Issue Status How
1 Merge conflicts ✅ Fixed Rebased on latest staging, prepare_tool_params preserved
2 Error type regression ✅ Fixed Restored AutonomousUnavailable with descriptive reason messages
3 PR description says http ✅ Fixed PR body updated, http removed from list
4 Duplicated scheduler propagation ✅ Fixed Single update_context_and_get call with needs_update guard
5 Never tools blocked by allowlist ✅ Fixed is_blocked() respects requirement: Never→pass, UnlessAutoApproved→pass, Always→check allowlist
6 DB restore loses approval_context ✅ Documented TODO(#1125) on both libsql and postgres (via history/store.rs) paths
7 No test for Never tools ✅ Added test_never_tools_allowed_in_additive_model
8 No test for builder unlisted tool ✅ Added test_builder_execute_build_tool_blocks_unlisted_tool

zmanian's Review 2 — is_blocked() semantics discussion

Critical question: Should UnlessAutoApproved auto-pass in autonomous contexts?

This is the pre-existing staging behavior, not a new policy introduced by this PR. The original is_blocked() on staging already returned false for UnlessAutoApproved. The security fix commit in this PR briefly changed it to block everything not in the allowlist, but that broke Never tools (echo, time) and UnlessAutoApproved tools.

Why the current semantics are correct:

  1. autonomous_allowed_tool_names() already populates the worker-level allowlist with ALL builtin tools minus the denylist — so in practice, UnlessAutoApproved tools are already in the allowlist at the worker level.
  2. The UnlessAutoApproved pass-through only matters for the builder's smaller scoped context, where it correctly lets read-only tools through while gating destructive Always ones (like shell with --force flags).
  3. The allowlist gates Always-requirement tools (destructive shell commands, http with auth headers, etc.) — these are the tools that need explicit permission.

Other items from review 2:

# Issue Status
M1 Builder allowlist redundant Not applicable — builder tools use Always requirement, so the allowlist is effective
M2 PR description says http ✅ Fixed
M3 Postgres DB restore not updated Already covered — postgres delegates to history/store.rs which has the TODO
Low needs_update always true Correct observation — dispatch_job() always provides Some(approval_context) via autonomous_approval_context(). The else branch is defensive but unreachable in current callers.

All changes in commit 0e5f1b12. CI passes: cargo fmt ✅, cargo clippy ✅ (zero warnings), 3921 lib tests + 9 integration tests ✅.

@ilblackdragon
Copy link
Copy Markdown
Member

Code Review — Post-rebase

Bug: Duplicate approval check in worker/job.rs

Severity: High — The PR adds a new additive approval check (lines 517-555) but the pre-existing approval check at lines 561-567 is still present, creating a double-check:

Line 518:  let requirement = tool.requires_approval(params);          // uses raw params
Line 562:  let requirement = tool.requires_approval(&normalized_params); // uses normalized params

This means:

  1. First check (new): uses raw params, applies additive job+worker semantics, returns AutonomousUnavailable
  2. Second check (pre-existing): uses normalized_params, checks worker-level only, returns autonomous_unavailable_error()

If a tool passes the first check, it hits the second check which is worker-level only (no additive semantics). The second check is redundant for the success path but could produce a different error type on the failure path. The first check also uses raw params for requires_approval() instead of normalized_params, which could produce different results for tools whose approval depends on parameter values (e.g., shell checks if the command is destructive).

Fix: Remove the pre-existing check at lines 561-567 and use normalized_params in the new check at line 518.

Minor Issues

  • Missing blank line (style, line 555-556): No blank line between the approval block's closing } and the next comment // Propagate http_interceptor.
  • needs_update is always true (low): dispatch_job() always passes Some(approval_context) from autonomous_approval_context(), making the else branch unreachable. A comment would help.

- Remove pre-existing worker-level-only approval check (lines 561-567)
  that duplicated the new additive check, using a different error type
  and missing job-level context
- Use normalized_params (not raw params) for requires_approval() so
  parameter-dependent approval (e.g. shell destructive detection) works
  correctly with coerced values
- Remove unused autonomous_unavailable_error import
- Add comment documenting unreachable else branch in scheduler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ilblackdragon ilblackdragon requested a review from zmanian March 31, 2026 03:53
@ilblackdragon ilblackdragon merged commit 42623ed into nearai:staging Mar 31, 2026
14 checks passed
@claude
Copy link
Copy Markdown

claude bot commented Mar 31, 2026

Code review

Found 10 issues:

  1. [CRITICAL:100] ToolError::NotAuthorized variant does not exist in the ToolError enum. The code should use ToolError::AutonomousUnavailable instead.

ironclaw/src/tools/tool.rs

Lines 496 to 504 in a8c05b8

///
/// Used by the agent framework before logging, hook dispatch, approval display,
/// and `ActionRecord` storage so plaintext secrets never reach those paths.
pub fn redact_params(params: &serde_json::Value, sensitive: &[&str]) -> serde_json::Value {
if sensitive.is_empty() {
return params.clone();
}
let mut redacted = params.clone();
if let Some(obj) = redacted.as_object_mut() {

The enum defined in src/error.rs:149-182 has variants: NotFound, ExecutionFailed, Timeout, InvalidParameters, Disabled, Sandbox, AuthRequired, AutonomousUnavailable, RateLimited, BuilderFailed. Use ToolError::AutonomousUnavailable { name: tool_name.to_string(), reason: ... }.

  1. [CRITICAL:100] Test file has the same ToolError::NotAuthorized reference which will cause compilation failure.

https://github.com/nearai/ironclaw/blob/a8c05b8239d702cfd8ab208a9ca852e791f843d3/tests/tool_approval_context.rs#L98-L101

This will fail to compile because the variant doesn't exist.

  1. [HIGH:80] Builder's approval context check bypasses worker-level restrictions. While documented as intentional, this creates an architectural asymmetry where builder can grant tools permissions that worker denies.

&requirement.name.replace('-', "_"),
)
}
(SoftwareType::CliBinary, Language::Rust) => project_dir.join(format!(
"target/release/{}",
requirement.name.replace('-', "_")
)),

  1. [MEDIUM:80] Clone of HashSet in job dispatch hot path. Every background job dispatch clones the approval context's tool allowlist.

  1. [MEDIUM:72] Database persistence gap for approval_context. Marked with #[serde(skip)], so it's lost on DB restore.

#[serde(skip)]

  1. [MEDIUM:70] Test coverage gap for concurrent approval context updates. The 3 new tests don't cover concurrent worker scenarios.

  2. [MEDIUM:65] Potential inconsistency in approval checking across all code paths using autonomous_unavailable_error.

  3. [LOW:65] Unreachable code path in scheduler marked as 'Currently unreachable' but still exists.

self.context_manager.get_context(job_id).await?
};
// Persist to DB before scheduling so the worker's FK references are valid.
// The context was read under the same lock as the update (atomic), preventing

  1. [LOW:78] Approval context loss on process restart - documented but represents temporary safety window.

  2. [LOW:65] Builder tool allowlist is hardcoded, preventing use of other build tools like 'http'.

Summary: The PR implements sound approval context propagation with good atomicity guarantees. However, there are 2 blocking compilation errors (ToolError::NotAuthorized does not exist in the enum) that must be fixed before merge.

JZKK720 pushed a commit to JZKK720/ironclaw that referenced this pull request Apr 1, 2026
…nearai#1125)

* feat(context): add approval_context field to JobContext

Add approval_context to JobContext so tools can propagate approval
information when executing sub-tools. This enables tools like
build_software to properly check approvals for shell, write_file, etc.

- Add approval_context: Option<ApprovalContext> field to JobContext
- Add with_approval_context() builder method
- Add check_approval_in_context() helper for tools to verify permissions
- Default JobContext now includes autonomous approval context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(worker): check job-level approval context before executing tools

Move job context fetch before approval check and add job-level
approval context checking. Job-level context takes precedence over
worker-level, allowing tools like build_software to set specific
allowed sub-tools while maintaining the fallback to worker-level
approval for normal operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(scheduler): propagate approval_context to JobContext

Store approval_context from dispatch into JobContext so it's
available to tools during execution. This completes the chain:
scheduler -> job context -> tools -> sub-tools.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(builder): use approval context for sub-tool execution

Update build_software to create a JobContext with build-specific
approval permissions and check approval before executing sub-tools.
This allows the builder to work in autonomous contexts (web UI, routines)
while maintaining security by only allowing specific build-related tools.

Allowed tools: shell, read_file, write_file, list_dir, apply_patch, http

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): initialize approval_context as None in job restoration

When restoring jobs from database, set approval_context to None.
The context will be populated by the scheduler on next dispatch if needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add comprehensive approval context tests

Add tests for:
- JobContext default includes approval_context
- with_approval_context() builder method
- Autonomous context blocks Always-approved tools unless explicitly allowed
- autonomous_with_tools allows specific tools
- Builder tool approval context configuration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): address critical approval context security issues

This commit addresses all security concerns raised in PR review:

1. Revert JobContext::default() to approval_context: None
   - Previously set ApprovalContext::autonomous() which was too permissive
   - Secure default requires explicit opt-in for autonomous execution
   - Any code using JobContext::default() now correctly blocks non-Never tools

2. Fix check_approval_in_context() to match worker behavior
   - Previously returned Ok(()) when approval_context was None (insecure)
   - Now uses ApprovalContext::is_blocked_or_default() for consistency
   - Prevents privilege escalation through sub-tool execution paths

3. Remove "http" from builder's allowed tools
   - Building software doesn't require direct http tool access
   - Shell commands (cargo, npm, pip) handle dependency fetching
   - Reduces attack surface for builder tool execution

4. Update tests to reflect new secure defaults
   - Tests now verify JobContext::default() blocks non-Never tools
   - New test added for secure default behavior

Security review references:
- Issue nearai#1: JobContext::default() behavioral change
- Issue nearai#3: check_approval_in_context more permissive than worker check
- Issue nearai#4: Builder allows http without justification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(worker): implement additive approval semantics for job + worker checks

This addresses the remaining security review concern from PR nearai#1125.

Previously, the worker used "precedence" semantics where job-level approval
context would completely bypass worker-level checks. This meant a tool's
job-level context could potentially override worker-level restrictions.

Changes:
- Worker now checks BOTH job-level AND worker-level approval contexts
- Tool is blocked if EITHER level blocks it (additive/intersection semantics)
- Maintains defense in depth: job-level cannot bypass worker-level restrictions

Tests added:
- test_additive_approval_semantics_both_levels_must_approve: verifies job-level
  blocks take effect even when worker-level allows
- test_additive_approval_worker_block_overrides_job_allow: verifies worker-level
  blocks take effect even when job-level allows
- test_additive_approval_both_levels_allow: verifies tool is allowed only when
  both levels approve

Security review reference:
- Issue nearai#3 from @G7CNF: "document or enforce additive semantics for job + worker
  approval checks"
- Issue nearai#2 from @zmanian: "Job-level context bypasses worker-level entirely"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): address PR nearai#1125 review feedback

- Restore requirement-aware is_blocked() semantics: Never and
  UnlessAutoApproved tools pass in autonomous context, Only Always
  tools require explicit allowlist entry
- Use AutonomousUnavailable error (with descriptive reason) instead
  of generic AuthRequired for approval blocking in worker
- Deduplicate approval_context propagation in scheduler dispatch
  (single update_context_and_get call instead of duplicated blocks)
- Remove http from builder tool allowlist (shell handles network)
- Add TODO comments for serde(skip) losing approval_context on DB
  restore in both libsql and postgres backends
- Add tests: Never tools in additive model, builder unlisted tool
  blocking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(worker): remove duplicate approval check and use normalized params

- Remove pre-existing worker-level-only approval check (lines 561-567)
  that duplicated the new additive check, using a different error type
  and missing job-level context
- Use normalized_params (not raw params) for requires_approval() so
  parameter-dependent approval (e.g. shell destructive detection) works
  correctly with coerced values
- Remove unused autonomous_unavailable_error import
- Add comment documenting unreachable else branch in scheduler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: ilblackdragon@gmail.com <ilblackdragon@gmail.com>
serrrfirat pushed a commit that referenced this pull request Apr 5, 2026
…#1125)

* feat(context): add approval_context field to JobContext

Add approval_context to JobContext so tools can propagate approval
information when executing sub-tools. This enables tools like
build_software to properly check approvals for shell, write_file, etc.

- Add approval_context: Option<ApprovalContext> field to JobContext
- Add with_approval_context() builder method
- Add check_approval_in_context() helper for tools to verify permissions
- Default JobContext now includes autonomous approval context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(worker): check job-level approval context before executing tools

Move job context fetch before approval check and add job-level
approval context checking. Job-level context takes precedence over
worker-level, allowing tools like build_software to set specific
allowed sub-tools while maintaining the fallback to worker-level
approval for normal operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(scheduler): propagate approval_context to JobContext

Store approval_context from dispatch into JobContext so it's
available to tools during execution. This completes the chain:
scheduler -> job context -> tools -> sub-tools.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(builder): use approval context for sub-tool execution

Update build_software to create a JobContext with build-specific
approval permissions and check approval before executing sub-tools.
This allows the builder to work in autonomous contexts (web UI, routines)
while maintaining security by only allowing specific build-related tools.

Allowed tools: shell, read_file, write_file, list_dir, apply_patch, http

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): initialize approval_context as None in job restoration

When restoring jobs from database, set approval_context to None.
The context will be populated by the scheduler on next dispatch if needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add comprehensive approval context tests

Add tests for:
- JobContext default includes approval_context
- with_approval_context() builder method
- Autonomous context blocks Always-approved tools unless explicitly allowed
- autonomous_with_tools allows specific tools
- Builder tool approval context configuration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): address critical approval context security issues

This commit addresses all security concerns raised in PR review:

1. Revert JobContext::default() to approval_context: None
   - Previously set ApprovalContext::autonomous() which was too permissive
   - Secure default requires explicit opt-in for autonomous execution
   - Any code using JobContext::default() now correctly blocks non-Never tools

2. Fix check_approval_in_context() to match worker behavior
   - Previously returned Ok(()) when approval_context was None (insecure)
   - Now uses ApprovalContext::is_blocked_or_default() for consistency
   - Prevents privilege escalation through sub-tool execution paths

3. Remove "http" from builder's allowed tools
   - Building software doesn't require direct http tool access
   - Shell commands (cargo, npm, pip) handle dependency fetching
   - Reduces attack surface for builder tool execution

4. Update tests to reflect new secure defaults
   - Tests now verify JobContext::default() blocks non-Never tools
   - New test added for secure default behavior

Security review references:
- Issue #1: JobContext::default() behavioral change
- Issue #3: check_approval_in_context more permissive than worker check
- Issue #4: Builder allows http without justification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(worker): implement additive approval semantics for job + worker checks

This addresses the remaining security review concern from PR #1125.

Previously, the worker used "precedence" semantics where job-level approval
context would completely bypass worker-level checks. This meant a tool's
job-level context could potentially override worker-level restrictions.

Changes:
- Worker now checks BOTH job-level AND worker-level approval contexts
- Tool is blocked if EITHER level blocks it (additive/intersection semantics)
- Maintains defense in depth: job-level cannot bypass worker-level restrictions

Tests added:
- test_additive_approval_semantics_both_levels_must_approve: verifies job-level
  blocks take effect even when worker-level allows
- test_additive_approval_worker_block_overrides_job_allow: verifies worker-level
  blocks take effect even when job-level allows
- test_additive_approval_both_levels_allow: verifies tool is allowed only when
  both levels approve

Security review reference:
- Issue #3 from @G7CNF: "document or enforce additive semantics for job + worker
  approval checks"
- Issue #2 from @zmanian: "Job-level context bypasses worker-level entirely"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): address PR #1125 review feedback

- Restore requirement-aware is_blocked() semantics: Never and
  UnlessAutoApproved tools pass in autonomous context, Only Always
  tools require explicit allowlist entry
- Use AutonomousUnavailable error (with descriptive reason) instead
  of generic AuthRequired for approval blocking in worker
- Deduplicate approval_context propagation in scheduler dispatch
  (single update_context_and_get call instead of duplicated blocks)
- Remove http from builder tool allowlist (shell handles network)
- Add TODO comments for serde(skip) losing approval_context on DB
  restore in both libsql and postgres backends
- Add tests: Never tools in additive model, builder unlisted tool
  blocking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(worker): remove duplicate approval check and use normalized params

- Remove pre-existing worker-level-only approval check (lines 561-567)
  that duplicated the new additive check, using a different error type
  and missing job-level context
- Use normalized_params (not raw params) for requires_approval() so
  parameter-dependent approval (e.g. shell destructive detection) works
  correctly with coerced values
- Remove unused autonomous_unavailable_error import
- Add comment documenting unreachable else branch in scheduler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: ilblackdragon@gmail.com <ilblackdragon@gmail.com>
drchirag1991 pushed a commit to drchirag1991/ironclaw that referenced this pull request Apr 8, 2026
…nearai#1125)

* feat(context): add approval_context field to JobContext

Add approval_context to JobContext so tools can propagate approval
information when executing sub-tools. This enables tools like
build_software to properly check approvals for shell, write_file, etc.

- Add approval_context: Option<ApprovalContext> field to JobContext
- Add with_approval_context() builder method
- Add check_approval_in_context() helper for tools to verify permissions
- Default JobContext now includes autonomous approval context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(worker): check job-level approval context before executing tools

Move job context fetch before approval check and add job-level
approval context checking. Job-level context takes precedence over
worker-level, allowing tools like build_software to set specific
allowed sub-tools while maintaining the fallback to worker-level
approval for normal operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(scheduler): propagate approval_context to JobContext

Store approval_context from dispatch into JobContext so it's
available to tools during execution. This completes the chain:
scheduler -> job context -> tools -> sub-tools.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(builder): use approval context for sub-tool execution

Update build_software to create a JobContext with build-specific
approval permissions and check approval before executing sub-tools.
This allows the builder to work in autonomous contexts (web UI, routines)
while maintaining security by only allowing specific build-related tools.

Allowed tools: shell, read_file, write_file, list_dir, apply_patch, http

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(db): initialize approval_context as None in job restoration

When restoring jobs from database, set approval_context to None.
The context will be populated by the scheduler on next dispatch if needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add comprehensive approval context tests

Add tests for:
- JobContext default includes approval_context
- with_approval_context() builder method
- Autonomous context blocks Always-approved tools unless explicitly allowed
- autonomous_with_tools allows specific tools
- Builder tool approval context configuration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): address critical approval context security issues

This commit addresses all security concerns raised in PR review:

1. Revert JobContext::default() to approval_context: None
   - Previously set ApprovalContext::autonomous() which was too permissive
   - Secure default requires explicit opt-in for autonomous execution
   - Any code using JobContext::default() now correctly blocks non-Never tools

2. Fix check_approval_in_context() to match worker behavior
   - Previously returned Ok(()) when approval_context was None (insecure)
   - Now uses ApprovalContext::is_blocked_or_default() for consistency
   - Prevents privilege escalation through sub-tool execution paths

3. Remove "http" from builder's allowed tools
   - Building software doesn't require direct http tool access
   - Shell commands (cargo, npm, pip) handle dependency fetching
   - Reduces attack surface for builder tool execution

4. Update tests to reflect new secure defaults
   - Tests now verify JobContext::default() blocks non-Never tools
   - New test added for secure default behavior

Security review references:
- Issue nearai#1: JobContext::default() behavioral change
- Issue nearai#3: check_approval_in_context more permissive than worker check
- Issue nearai#4: Builder allows http without justification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(worker): implement additive approval semantics for job + worker checks

This addresses the remaining security review concern from PR nearai#1125.

Previously, the worker used "precedence" semantics where job-level approval
context would completely bypass worker-level checks. This meant a tool's
job-level context could potentially override worker-level restrictions.

Changes:
- Worker now checks BOTH job-level AND worker-level approval contexts
- Tool is blocked if EITHER level blocks it (additive/intersection semantics)
- Maintains defense in depth: job-level cannot bypass worker-level restrictions

Tests added:
- test_additive_approval_semantics_both_levels_must_approve: verifies job-level
  blocks take effect even when worker-level allows
- test_additive_approval_worker_block_overrides_job_allow: verifies worker-level
  blocks take effect even when job-level allows
- test_additive_approval_both_levels_allow: verifies tool is allowed only when
  both levels approve

Security review reference:
- Issue nearai#3 from @G7CNF: "document or enforce additive semantics for job + worker
  approval checks"
- Issue nearai#2 from @zmanian: "Job-level context bypasses worker-level entirely"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(security): address PR nearai#1125 review feedback

- Restore requirement-aware is_blocked() semantics: Never and
  UnlessAutoApproved tools pass in autonomous context, Only Always
  tools require explicit allowlist entry
- Use AutonomousUnavailable error (with descriptive reason) instead
  of generic AuthRequired for approval blocking in worker
- Deduplicate approval_context propagation in scheduler dispatch
  (single update_context_and_get call instead of duplicated blocks)
- Remove http from builder tool allowlist (shell handles network)
- Add TODO comments for serde(skip) losing approval_context on DB
  restore in both libsql and postgres backends
- Add tests: Never tools in additive model, builder unlisted tool
  blocking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(worker): remove duplicate approval check and use normalized params

- Remove pre-existing worker-level-only approval check (lines 561-567)
  that duplicated the new additive check, using a different error type
  and missing job-level context
- Use normalized_params (not raw params) for requires_approval() so
  parameter-dependent approval (e.g. shell destructive detection) works
  correctly with coerced values
- Remove unused autonomous_unavailable_error import
- Add comment documenting unreachable else branch in scheduler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: ilblackdragon@gmail.com <ilblackdragon@gmail.com>
@ironclaw-ci ironclaw-ci bot mentioned this pull request Apr 8, 2026
@ironclaw-ci ironclaw-ci bot mentioned this pull request Apr 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: new First-time contributor risk: medium Business logic, config, or moderate-risk modules scope: agent Agent core (agent loop, router, scheduler) scope: docs Documentation scope: tool/builder Dynamic tool builder scope: tool Tool infrastructure scope: worker Container worker size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants