Scans Rust source for path-confinement checks (root-prefix / starts_with on a path) missing a canonicalize() call in the same function -- the exact shape of the untracked-intake sprint-3.42 Gate 4 bug (a lexical .starts_with check let a `..`/absolute path escape the declared root before refusal). Candidates for manual review, per Gate 1.5.6 item 1.
| Tier | tools |
| Role | unclassified (baselined) |
| Path | crates/tools/confinement-scan |
| Edition | 2021 |
| Targets | tools-confinement-scan, tools_confinement_scan |
| Public items | 3 across 0 modules |
| Tests | 10 |
What it is for
Path-confinement check scanner.
Finds the exact mechanical shape of the tools-untracked-intake sprint-3.42 Gate 4 bug: a function that does path-related work (mentions Path or PathBuf) and checks a path against a root via .starts_with(, but never calls .canonicalize() anywhere in that same function. That was precisely the defect — the file's OTHER path check (for --file) DID canonicalize; the dest check didn't, and a ../absolute destination passed the lexical starts_with and the write executed outside the declared root before the tool ever refused (Gate 1.5.6 item 1).
High-precision by construction (same discipline as tools-stub-scan): source is split into a code view with string/comment contents blanked first, so a doc comment or string literal that happens to mention starts_with or canonicalize never produces a finding.
Scope is per-FUNCTION, not per-file: a file can correctly canonicalize in one function and skip it in another (exactly what happened in tools-untracked-intake) — a file-level presence check would have missed it.
This is a candidate list for manual review, like tools-stub-scan/tools-script-scan — not a hard gate. A starts_with on a plain string prefix with no Path/PathBuf in scope is deliberately NOT flagged (too noisy to act on); a real path-confinement check that uses some other correct pattern (e.g. component-level allowlisting, tools-untracked-intake's own --dest fix) also won't trip this — it's a signal for the one specific pattern that has caused a real bug in this repo, not a general path-safety linter.
Capabilities
Finding
Path-confinement check scanner.
| Item |
|---|
pub struct Finding |
fn scan_source(rel : & str, src : & str, out : & mut Vec <Finding>) |
fn scan(repo_root : & Path) -> Result <Vec <Finding>> |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
No public modules: the crate root is its whole surface.
Public surface
`crate root`
| Item | What it is |
|---|---|
pub struct Finding | One candidate finding: a function whose body looks path-related, checks .starts_with(, and never calls .canonicalize(). |
fn scan_source(rel : & str, src : & str, out : & mut Vec <Finding>) | Scan one file's source (already read) for confinement-check candidates |
fn scan(repo_root : & Path) -> Result <Vec <Finding>> | Scan the tree rooted at repo_root (its crates/ and examples/ subdirectories) for path-confinement candidates. |
No pub use re-exports: every item above is declared in this crate.
Boundary
Depends on no other workspace tier.
Shares tier tools with 84 other crates: tools-advisory-reach, tools-archive-guard, tools-artifact-scaffold, tools-ask-ai-core, tools-ask-ais, tools-ask-gemini, tools-book, tools-book-report, … (84 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | tools |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/tools/confinement-scan |
| Vocabulary in force (lexicon) | current |
Dependencies
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
anyhow | ^1 | — | no | always |
Development. None.
Build. None.
Depended on by. Nothing in this workspace.
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| bin | tools-confinement-scan | `src/main.rs` |
| lib | tools_confinement_scan | `src/lib.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | none detected |
| async runtime | none detected |
| database access | none detected |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | yes |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
| Variable | Read in |
|---|---|
CARGO_MANIFEST_DIR | src/main.rs |
Related capabilities
No workspace crate depends on this one.
Verification
| Kind | Count |
|---|---|
| Unit tests | 10 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
crate root | 3 | 0 | 0 |
What the tests establish, by name:
does_not_flag_a_starts_with_unrelated_to_paths—src/lib.rsdoes_not_flag_when_canonicalize_precedes_starts_with_in_the_same_function—src/lib.rsexcludes_test_functions—src/lib.rsflags_a_starts_with_check_with_no_canonicalize_in_the_same_function—src/lib.rsignores_the_needle_words_inside_comments_and_string_literals—src/lib.rspath_relatedness_requires_a_word_boundary_not_a_bare_substring—src/lib.rspath_relatedness_still_matches_a_real_path_type_mention—src/lib.rsrecognizes_the_free_function_canonicalize_form_not_only_the_method_form—src/lib.rsrequires_path_relatedness_even_when_starts_with_and_no_canonicalize_both_present—src/lib.rsscopes_by_function_not_by_file_when_one_function_is_safe_and_another_is_not—src/lib.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 3 | 3 |
Public modules with a //! block | 0 | 0 |
pie showData
title Public items with rustdoc
"Documented" : 3
"No rustdoc detected" : 0
Metrics
| Metric | Value |
|---|---|
| Rust source files | 2 |
| Source lines | 636 |
| Code lines | 475 |
| Public API items | 3 |
| Public modules | 0 |
| Tests | 10 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 1 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"function" : 2
"struct" : 1
pie showData
title Rust source composition
"Code" : 475
"Blank or comment" : 161
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.