Skip to content

refactor: extract safety module into ironclaw_safety crate - #1024

Merged
ilblackdragon merged 4 commits into
stagingfrom
refactor/extract-ironclaw-safety-crate
Mar 12, 2026
Merged

ilblackdragon merged 4 commits into
stagingfrom
refactor/extract-ironclaw-safety-crate

Conversation

@ilblackdragon

Copy link
Copy Markdown
Member

Summary

  • Extracts src/safety/ into a standalone crates/ironclaw_safety/ crate (prompt injection defense, input validation, secret leak detection, policy enforcement)
  • SafetyConfig moves into the crate; env-var resolution stays in ironclaw's config module as a free function (resolve_safety_config())
  • src/safety/mod.rs becomes a thin pub use ironclaw_safety::* re-export — all existing crate::safety::* imports keep working
  • CLAUDE.md updated with guidance to migrate imports to ironclaw_safety when touching files

The safety module was a leaf dependency with no async, no database, and no ironclaw traits — only pure computation with pattern matching. External deps: regex, aho-corasick, serde_json, url, thiserror, tracing.

Test plan

🤖 Generated with Claude Code

ilblackdragon and others added 2 commits March 11, 2026 19:34
Move prompt injection defense, input validation, secret leak detection,
and safety policy enforcement into a standalone crate under crates/.
The safety module was a leaf dependency with no async, no database, and
no other ironclaw traits — only pure computation with pattern matching.

SafetyConfig (2 fields) moves into the crate; env-var resolution stays
in ironclaw's config module as a free function. src/safety/mod.rs becomes
a thin re-export so all existing `crate::safety::*` imports keep working.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add guidance to migrate imports from crate::safety to ironclaw_safety
when touching files. Update project structure to reflect crates/ dir.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 12, 2026 07:43
@github-actions github-actions Bot added scope: safety Prompt injection defense scope: docs Documentation scope: dependencies Dependency updates size: XL 500+ changed lines risk: high Safety, secrets, auth, or critical infrastructure contributor: core 20+ merged PRs labels Mar 12, 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 significantly refactors the project's architecture by isolating the safety-related functionalities into a dedicated Rust crate. This move enhances modularity, making the safety components more independent and reusable, as they consist primarily of pure computation without direct dependencies on ironclaw's core traits or I/O. The change aims to improve the overall maintainability and clarity of the codebase by separating concerns, while carefully managing the transition to ensure existing code remains functional.

Highlights

  • Module Extraction: The src/safety/ module has been extracted into a new, standalone crates/ironclaw_safety/ crate, encompassing prompt injection defense, input validation, secret leak detection, and policy enforcement.
  • Configuration Migration: The SafetyConfig struct has been moved into the new ironclaw_safety crate, with its environment variable resolution logic (resolve_safety_config()) remaining in ironclaw's config module.
  • Backward Compatibility: The original src/safety/mod.rs now acts as a thin pub use ironclaw_safety::* re-export, ensuring that all existing crate::safety::* imports continue to function without immediate changes.
  • Documentation Update: The CLAUDE.md file has been updated to include guidance for migrating imports to ironclaw_safety when modifying relevant files.
Changelog
  • CLAUDE.md
    • Added a new section 'Extracted Crates' to document the ironclaw_safety crate.
    • Updated the project structure diagram to include the new crates/ironclaw_safety directory.
    • Modified the description of src/safety/ to reflect its new role as a re-export shim.
  • Cargo.lock
    • Added ironclaw_safety to the list of project dependencies.
    • Included the package definition for ironclaw_safety with its version and dependencies.
  • Cargo.toml
    • Added crates/ironclaw_safety to the workspace members.
    • Declared ironclaw_safety as a local path dependency under the [dependencies] section.
  • crates/ironclaw_safety/Cargo.toml
    • Created a new Cargo manifest file for the ironclaw_safety crate, defining its metadata and dependencies.
  • crates/ironclaw_safety/src/credential_detect.rs
    • Renamed from src/safety/credential_detect.rs.
  • crates/ironclaw_safety/src/leak_detector.rs
    • Renamed from src/safety/leak_detector.rs.
    • Updated internal use crate::safety::leak_detector paths to use crate::leak_detector.
  • crates/ironclaw_safety/src/lib.rs
    • Added the core safety logic, including the SafetyLayer struct and related components, to the new crate.
    • Exported modules like credential_detect, leak_detector, policy, sanitizer, and validator.
    • Defined SafetyConfig and SafetyLayer structs and their implementations.
  • crates/ironclaw_safety/src/policy.rs
    • Renamed from src/safety/policy.rs.
  • crates/ironclaw_safety/src/sanitizer.rs
    • Renamed from src/safety/sanitizer.rs.
    • Updated internal use crate::safety::Severity to use crate::Severity.
  • crates/ironclaw_safety/src/validator.rs
    • Renamed from src/safety/validator.rs.
  • src/config/mod.rs
    • Imported resolve_safety_config from self::safety.
    • Updated the Config::resolve method to call resolve_safety_config() instead of SafetyConfig::resolve().
  • src/config/safety.rs
    • Removed the local SafetyConfig struct definition.
    • Replaced the local SafetyConfig with pub use ironclaw_safety::SafetyConfig;.
    • Converted the SafetyConfig::resolve() method into a free function resolve_safety_config() that returns ironclaw_safety::SafetyConfig.
  • src/safety/mod.rs
    • Simplified the file to re-export all public items from the ironclaw_safety crate using pub use ironclaw_safety::*;.
    • Removed all original module declarations and use statements.
Activity
  • The author confirmed that cargo check passes successfully.
  • The ironclaw_safety crate passed cargo clippy with zero warnings across all targets.
  • All 81 tests within the ironclaw_safety crate passed.
  • The re-export mechanism was verified to be working correctly by running cargo test -p ironclaw --lib -- safety.
  • The author plans to verify that open pull requests touching the original src/safety/ directory resolve conflicts cleanly with these changes.
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. ↩

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Extracts the src/safety/ module into a standalone crates/ironclaw_safety/ crate, replacing the original module with a thin pub use ironclaw_safety::* re-export shim. The SafetyConfig struct moves to the new crate while env-var resolution remains in the main crate.

Changes:

  • New crates/ironclaw_safety/ crate with all safety submodules (sanitizer, validator, policy, leak_detector, credential_detect)
  • src/safety/mod.rs reduced to a re-export shim; src/config/safety.rs updated to use a free function for config resolution
  • CLAUDE.md and Cargo workspace updated with guidance and dependency wiring

Reviewed changes

Copilot reviewed 9 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
File Description
crates/ironclaw_safety/Cargo.toml New crate manifest
crates/ironclaw_safety/src/lib.rs Crate root with SafetyConfig, SafetyLayer, and re-exports
crates/ironclaw_safety/src/policy.rs Moved policy module
crates/ironclaw_safety/src/sanitizer.rs Updated import path
crates/ironclaw_safety/src/validator.rs Moved validator module
crates/ironclaw_safety/src/credential_detect.rs Moved credential detection module
crates/ironclaw_safety/src/leak_detector.rs Updated import paths in tests
src/safety/mod.rs Replaced with re-export shim
src/config/safety.rs Changed from impl method to free function
src/config/mod.rs Updated call site for config resolution
Cargo.toml Added workspace member and dependency
Cargo.lock Updated lockfile
CLAUDE.md Added guidance for extracted crate

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request is a well-executed refactoring that extracts the safety module into a new ironclaw_safety crate. The changes are clean, and the use of a re-export shim in src/safety/mod.rs ensures backward compatibility, which is a great approach for a change of this scale. The new crate structure improves modularity and separation of concerns. I have one minor suggestion to improve the performance of XML attribute escaping by avoiding multiple string allocations, aligning with our guidelines on minimizing heap allocations for performance.

Comment on lines +214 to +219
fn escape_xml_attr(s: &str) -> String {
s.replace('&', "&amp;")
.replace('"', "&quot;")
.replace('<', "&lt;")
.replace('>', "&gt;")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The current implementation of escape_xml_attr with chained replace calls can be inefficient, as each call might allocate a new String if the character to be replaced is found. A more performant approach is to iterate over the string's characters once and build the new escaped string in a single pass. This avoids intermediate string allocations.

fn escape_xml_attr(s: &str) -> String {
    // Pre-allocating with the original string's length is a good starting point.
    let mut escaped = String::with_capacity(s.len());
    for c in s.chars() {
        match c {
            '&' => escaped.push_str("&amp;"),
            '"' => escaped.push_str("&quot;"),
            '<' => escaped.push_str("&lt;"),
            '>' => escaped.push_str("&gt;"),
            _ => escaped.push(c),
        }
    }
    escaped
}
References
  1. To improve performance, avoid unnecessary heap allocations. When processing string parts, build the result in a single pass to prevent intermediate string allocations.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in b78c336 — rewrote to single-pass char iteration with String::with_capacity. O(n) with no intermediate allocations.

Split fuzz infrastructure:
- crates/ironclaw_safety/fuzz/ — 5 safety-only targets (sanitizer,
  validator, leak_detector, credential_detect, config_env) depending
  only on ironclaw_safety for faster builds
- fuzz/ — keeps fuzz_tool_params which needs ironclaw::tools

Add seed corpus files (51 total) covering each pattern family:
sanitizer injection patterns, validator edge cases, leak detector
secret formats, credential detect HTTP param shapes.

Add new fuzz_credential_detect target exercising
params_contain_manual_credentials with arbitrary JSON.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite escape_xml_attr from chained .replace() to single-pass char
iteration (O(n) instead of O(4n) with intermediate allocations). Add
version = "0.1.0" to ironclaw_safety path dep to satisfy cargo-deny
wildcards = "deny".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 12, 2026 17:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 67 out of 77 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ilblackdragon
ilblackdragon merged commit 5a62cea into staging Mar 12, 2026
14 checks passed
@ilblackdragon
ilblackdragon deleted the refactor/extract-ironclaw-safety-crate branch March 12, 2026 17:54
bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
* refactor: extract safety module into ironclaw_safety crate

Move prompt injection defense, input validation, secret leak detection,
and safety policy enforcement into a standalone crate under crates/.
The safety module was a leaf dependency with no async, no database, and
no other ironclaw traits — only pure computation with pattern matching.

SafetyConfig (2 fields) moves into the crate; env-var resolution stays
in ironclaw's config module as a free function. src/safety/mod.rs becomes
a thin re-export so all existing `crate::safety::*` imports keep working.

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

* docs: update CLAUDE.md for ironclaw_safety crate extraction

Add guidance to migrate imports from crate::safety to ironclaw_safety
when touching files. Update project structure to reflect crates/ dir.

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

* refactor: move safety fuzz targets into ironclaw_safety crate

Split fuzz infrastructure:
- crates/ironclaw_safety/fuzz/ — 5 safety-only targets (sanitizer,
  validator, leak_detector, credential_detect, config_env) depending
  only on ironclaw_safety for faster builds
- fuzz/ — keeps fuzz_tool_params which needs ironclaw::tools

Add seed corpus files (51 total) covering each pattern family:
sanitizer injection patterns, validator edge cases, leak detector
secret formats, credential detect HTTP param shapes.

Add new fuzz_credential_detect target exercising
params_contain_manual_credentials with arbitrary JSON.

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

* fix: address PR review — single-pass XML escaping and versioned path dep

Rewrite escape_xml_attr from chained .replace() to single-pass char
iteration (O(n) instead of O(4n) with intermediate allocations). Add
version = "0.1.0" to ironclaw_safety path dep to satisfy cargo-deny
wildcards = "deny".

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
drchirag1991 pushed a commit to drchirag1991/ironclaw that referenced this pull request Apr 8, 2026
* refactor: extract safety module into ironclaw_safety crate

Move prompt injection defense, input validation, secret leak detection,
and safety policy enforcement into a standalone crate under crates/.
The safety module was a leaf dependency with no async, no database, and
no other ironclaw traits — only pure computation with pattern matching.

SafetyConfig (2 fields) moves into the crate; env-var resolution stays
in ironclaw's config module as a free function. src/safety/mod.rs becomes
a thin re-export so all existing `crate::safety::*` imports keep working.

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

* docs: update CLAUDE.md for ironclaw_safety crate extraction

Add guidance to migrate imports from crate::safety to ironclaw_safety
when touching files. Update project structure to reflect crates/ dir.

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

* refactor: move safety fuzz targets into ironclaw_safety crate

Split fuzz infrastructure:
- crates/ironclaw_safety/fuzz/ — 5 safety-only targets (sanitizer,
  validator, leak_detector, credential_detect, config_env) depending
  only on ironclaw_safety for faster builds
- fuzz/ — keeps fuzz_tool_params which needs ironclaw::tools

Add seed corpus files (51 total) covering each pattern family:
sanitizer injection patterns, validator edge cases, leak detector
secret formats, credential detect HTTP param shapes.

Add new fuzz_credential_detect target exercising
params_contain_manual_credentials with arbitrary JSON.

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

* fix: address PR review — single-pass XML escaping and versioned path dep

Rewrite escape_xml_attr from chained .replace() to single-pass char
iteration (O(n) instead of O(4n) with intermediate allocations). Add
version = "0.1.0" to ironclaw_safety path dep to satisfy cargo-deny
wildcards = "deny".

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: high Safety, secrets, auth, or critical infrastructure scope: dependencies Dependency updates scope: docs Documentation scope: safety Prompt injection defense size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants