Skip to content

fix(core): scope build_unchecked() to test-only, add build_lenient() (#475)#516

Merged
avifenesh merged 9 commits intomainfrom
fix/scope-build-unchecked-475
Feb 20, 2026
Merged

fix(core): scope build_unchecked() to test-only, add build_lenient() (#475)#516
avifenesh merged 9 commits intomainfrom
fix/scope-build-unchecked-475

Conversation

@avifenesh
Copy link
Copy Markdown
Collaborator

Summary

  • Add build_lenient() to LintConfigBuilder: 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.
  • Add ConfigError::AbsolutePathPattern variant to surface absolute-path glob patterns (/etc/passwd, C:/foo/**) as hard errors in both build() and build_lenient().
  • Feature-gate build_unchecked() behind #[cfg(any(test, feature = "__internal_unchecked"))] and #[doc(hidden)]. The __internal_unchecked = [] feature is available in agnix-core for integration tests that construct intentionally-invalid configs.
  • Migrate agnix-wasm from build_unchecked() to build_lenient().unwrap_or_default().
  • Migrate ~16 test call sites from build_unchecked() to build().unwrap() where valid data was used.

Test Plan

  • cargo test --workspace passes (3600+ tests, 0 failures)
  • 9 new unit tests for build_lenient() (allows unknown tools/deprecated fields/unknown rule prefixes, rejects invalid globs/path traversal/absolute paths/files.* patterns)
  • 7 new cross-crate integration tests
  • cargo check -p agnix-wasm passes without __internal_unchecked feature
  • docs_workspace_membership parity test: CLAUDE.md and AGENTS.md byte-for-byte identical

Related Issues

Closes #475

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()
Copilot AI review requested due to automatic review settings February 20, 2026 03:43
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @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

  • New build_lenient() method: Introduced LintConfigBuilder::build_lenient() which performs security-critical glob pattern validation (syntax, path traversal, absolute paths) but skips semantic warnings like unknown tool names or deprecated fields. This is intended for embedders that need to accept future tool names without requiring a library rebuild.
  • Scoped build_unchecked(): The LintConfigBuilder::build_unchecked() method has been feature-gated behind #[cfg(any(test, feature = "__internal_unchecked"))] and marked #[doc(hidden)], limiting its use to internal tests or specific integration scenarios. External users should now prefer build_lenient().
  • Absolute Path Pattern Error: Added a new ConfigError::AbsolutePathPattern variant to explicitly catch and report glob patterns that use absolute paths (e.g., /etc/passwd or C:/foo/**), enforcing relative paths only for security.
  • WASM Integration Update: The agnix-wasm crate has been updated to use build_lenient().unwrap_or_default() instead of build_unchecked() for its configuration, ensuring security checks are still applied while maintaining flexibility for unknown tools.
  • Test Migrations: Approximately 16 test call sites were migrated from build_unchecked() to build().unwrap() where valid configuration data was used, aligning tests with the new validation behavior.
Changelog
  • CHANGELOG.md
    • Added build_lenient() on LintConfigBuilder for security-critical glob pattern validation while skipping semantic warnings. Includes ConfigError::AbsolutePathPattern for absolute path glob patterns.
    • Scoped build_unchecked() to test/internal use, now gated behind #[cfg(any(test, feature = "__internal_unchecked"))] and marked #[doc(hidden)]. External embedders should migrate to build_lenient().
Activity
  • Added 9 new unit tests specifically for build_lenient() to verify its behavior regarding allowed and rejected patterns.
  • Implemented 7 new cross-crate integration tests to ensure build_lenient() functions correctly across different parts of the system.
  • Migrated approximately 16 existing test call sites from build_unchecked() to build().unwrap() to reflect the intended usage of the build() method for valid configurations.
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.

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

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.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 refactored build() to share security-critical glob validation via validate_patterns().
  • Added ConfigError::AbsolutePathPattern and corresponding validation/tests for absolute-path globs.
  • Migrated WASM and many tests from build_unchecked() to build() / build_lenient(), plus added internal feature __internal_unchecked for 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
@avifenesh avifenesh merged commit 6f4d76e into main Feb 20, 2026
29 checks passed
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.

Architecture: scope build_unchecked() to prevent external misuse

2 participants