fix(core): scope build_unchecked() to test-only, add build_lenient() (#475)#516
fix(core): scope build_unchecked() to test-only, add build_lenient() (#475)#516
Conversation
Add build_lenient() that runs security-critical validation (glob pattern syntax and path traversal checks) while skipping semantic warnings such as unknown tool names, unknown rule ID prefixes, and deprecated field warnings. This is the recommended method for embedders that need to accept future or unknown tool names without rebuilding the library. Feature-gate build_unchecked() behind #[cfg(any(test, feature = "__internal_unchecked"))] so it is only available in test contexts or with an explicit opt-in feature flag. Extract glob/path-traversal validation into a private validate_patterns() helper shared by both build() and build_lenient(). Migrate agnix-wasm from build_unchecked() to build_lenient() since its use case (accepting unknown tool names from the playground) is exactly what build_lenient() is designed for. Closes #475 (partial)
…) to build().unwrap() Tests that only set valid data (known tools, known rule IDs, fs mocks, tool versions, etc.) no longer need build_unchecked() since build() will accept them without warnings. This narrows the remaining build_unchecked() call sites to tests that genuinely need to bypass validation (deprecated fields, intentionally invalid patterns). Migrated files: - config/tests.rs: 7 call sites - pipeline.rs: 5 call sites - rules/roo.rs: 3 call sites - tests/lib_tests.rs: 1 call site
Add 6 unit tests in config/tests.rs verifying build_lenient() behavior: - Accepts unknown tool names (skips semantic validation) - Accepts deprecated target field - Accepts deprecated mcp_protocol_version field - Accepts unknown rule ID prefixes - Rejects invalid glob syntax (security-critical) - Rejects path traversal patterns (security-critical) Each "allows" test also asserts that build() rejects the same input, confirming the semantic/security distinction is correct. Add 2 cross-crate integration tests in cross_crate_integration.rs: - builder_build_lenient_allows_unknown_tools - builder_build_lenient_rejects_invalid_glob
Update the Key Abstractions section in CLAUDE.md and AGENTS.md to list build_lenient() and note that build_unchecked() is now gated behind #[cfg(any(test, feature = "__internal_unchecked"))].
…_lenient() validation - Add ConfigError::AbsolutePathPattern for absolute-path glob rejection - Add absolute path check to validate_patterns() so build_lenient() rejects /etc/passwd-style patterns alongside path traversal - Add 3 new unit tests: rejects_absolute_path_pattern, rejects_invalid_glob_in_files_config, rejects_path_traversal_in_files_config - Add 3 new cross-crate integration tests: rejects_path_traversal, rejects_absolute_path, allows_unknown_rule_prefixes - Improve cross-crate tests to match specific ConfigError variants via agnix_core::config::ConfigError - Improve test assertion precision: match error field names, remove redundant is_err() asserts - Add safety comment to WASM build_lenient().unwrap_or_default() explaining why it never fails - Migrate builder_via_lint_config_factory from build_unchecked() to build_lenient()
Summary of ChangesHello @avifenesh, 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 refines the configuration building process by introducing a new lenient build option and tightening the scope of the unchecked build. The primary goal is to provide a more flexible yet secure way for external applications, particularly WASM environments, to construct lint configurations without failing on future or unknown semantic elements, while still rigorously enforcing critical security validations like glob pattern safety. This change improves the robustness and adaptability of the configuration system for diverse use cases. Highlights
Changelog
Activity
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
Code Review
This pull request introduces build_lenient() to the LintConfigBuilder for flexible validation, adds ConfigError::AbsolutePathPattern to reject absolute paths, and scopes build_unchecked() for test/internal use. While these changes successfully implement security-critical validation for glob patterns and restrict build_unchecked() to test environments, a critical security gap remains. The new validation checks are bypassed when configuration is loaded directly from a file or deserialized, as the validation logic is currently confined to the builder pattern. Centralizing this validation is crucial for consistent security enforcement across all configuration loading paths, as highlighted by the security review comment.
There was a problem hiding this comment.
Pull request overview
Scopes LintConfigBuilder::build_unchecked() to test/internal usage and introduces a safer build_lenient() builder terminal for embedders (e.g., WASM) that need to accept unknown/future tool names while still enforcing security-critical glob validation.
Changes:
- Added
LintConfigBuilder::build_lenient()and refactoredbuild()to share security-critical glob validation viavalidate_patterns(). - Added
ConfigError::AbsolutePathPatternand corresponding validation/tests for absolute-path globs. - Migrated WASM and many tests from
build_unchecked()tobuild()/build_lenient(), plus added internal feature__internal_uncheckedfor integration-test access.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/cross_crate_integration.rs | Updates cross-crate tests to prefer build_lenient() and asserts structured ConfigError variants. |
| crates/agnix-wasm/src/lib.rs | Switches WASM playground config construction from build_unchecked() to build_lenient() fallback. |
| crates/agnix-core/tests/lib_tests.rs | Replaces build_unchecked() usage with strict build().unwrap() in core integration tests. |
| crates/agnix-core/src/rules/roo.rs | Updates rule tests to use build().unwrap() instead of unchecked builder. |
| crates/agnix-core/src/pipeline.rs | Updates pipeline tests to use strict build().unwrap(). |
| crates/agnix-core/src/config/tests.rs | Adds new build_lenient() tests and migrates valid configs from build_unchecked() to build(). |
| crates/agnix-core/src/config/builder.rs | Implements build_lenient(), gates build_unchecked(), and adds absolute-path glob rejection. |
| crates/agnix-core/src/config.rs | Adds ConfigError::AbsolutePathPattern and display formatting. |
| crates/agnix-core/Cargo.toml | Adds __internal_unchecked feature for internal/integration test use. |
| Cargo.toml | Enables __internal_unchecked for workspace-root dev-dependency tests. |
| CLAUDE.md | Updates documented builder API to include build_lenient() and note gated build_unchecked(). |
| AGENTS.md | Same documentation update as CLAUDE.md. |
| CHANGELOG.md | Documents new builder terminal, new error variant, and scoping of build_unchecked(). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Use expect() instead of unwrap_or_default() in agnix-wasm since build_lenient() is guaranteed to succeed when only tools() is set - Fix escaped backslashes in CHANGELOG.md entry for build_unchecked() scoping (\"...\" -> "..." so inline code renders correctly) - Add #[non_exhaustive] to ConfigError to make future variant additions non-breaking for downstream crates that match exhaustively
Summary
build_lenient()toLintConfigBuilder: runs security-critical glob validation (syntax, path traversal, absolute paths) while skipping semantic warnings (unknown tool names, deprecated fields, unknown rule prefixes). Intended for embedders like the WASM playground that need to accept future tool names without a library rebuild.ConfigError::AbsolutePathPatternvariant to surface absolute-path glob patterns (/etc/passwd,C:/foo/**) as hard errors in bothbuild()andbuild_lenient().build_unchecked()behind#[cfg(any(test, feature = "__internal_unchecked"))]and#[doc(hidden)]. The__internal_unchecked = []feature is available inagnix-corefor integration tests that construct intentionally-invalid configs.agnix-wasmfrombuild_unchecked()tobuild_lenient().unwrap_or_default().build_unchecked()tobuild().unwrap()where valid data was used.Test Plan
cargo test --workspacepasses (3600+ tests, 0 failures)build_lenient()(allows unknown tools/deprecated fields/unknown rule prefixes, rejects invalid globs/path traversal/absolute paths/files.* patterns)cargo check -p agnix-wasmpasses without__internal_uncheckedfeaturedocs_workspace_membershipparity test: CLAUDE.md and AGENTS.md byte-for-byte identicalRelated Issues
Closes #475