Skip to content

Scan and directory output overstate what was examined and what was found #241

Description

@kmondlane

Description

A hands-on usability audit of vettd 0.9.3 / skill-scanner 0.1.4 (built from source, Windows 11 x86_64, prod endpoint) surfaced a cluster of behaviors where the CLI reports more certainty than it has: it returns a clean bill of health on paths it never examined, shows 3 of 33 flagged artifacts, and runs for 76 minutes without indicating whether it is working or hung. A second cluster makes the public directory output insufficient to support an install decision.

Findings already covered by open issues are excluded and listed at the bottom.

A. The CLI reports success it hasn't earned

A1 — scan file <directory> and scan folder <file> report "clean" and exit 0 without scanning anything.

Same directory, two commands:

$ vettd scan file  <dir>            exit 0   "No AI execution artifacts detected."
$ vettd scan folder <dir>           exit 0   3 artifact(s), 1 FLAGGED risk 58

The mirror case fails the same way:

$ vettd scan folder <a single file>  exit 0  "No AI execution artifacts detected."

vettd scan repo <a non-repo dir> is tolerant and scans correctly, so the inconsistency is between subcommands. Path validation at crates/vettd-cli/src/cli.rs:1135-1150 checks only path.exists(); a directory exists, so File passes validation and the pipeline finds no single file to scan. A file/folder mix-up returns a green light on unexamined content, in the vocabulary of safety. Related to #180 (--out given a directory), but a different surface.

A2 — The overview renders only 3 risk cards regardless of how many artifacts are flagged.

vettd scan full on this host:

RISK  33 flagged · 105 review · 647 clear  (785 artifact(s))

✗ CRITICAL  skill  risk 100    ~\.claude\plugins\...\mcp-integration\SKILL.md
✗ CRITICAL  skill  risk 91     (...)
✗ CRITICAL  skill  risk 91     (...)

… and 782 more:                (path + artifact type only — no score, no severity, no reason)

30 flagged artifacts are collapsed into the "782 more" list, visually indistinguishable from the 647 clear ones. Confirmed not lite mode: no ~/.vettd/.vettd.toml exists. Same truncation occurs in scan quick ("… and 27 more"). After a 76-minute scan the tool states 33 artifacts need attention and displays three of them.

A3 — Long scans emit nothing, and cache reuse is never disclosed.

$ vettd scan full                    exit 0 · 4,574,717 ms (76.2 min)
   stdout: 0 bytes for the entire run, then 70,590 bytes at completion
   stderr: 166 bytes total, one warning emitted at the start

$ vettd scan quick --no-cache        84,579 ms · 30 artifacts
$ vettd scan quick                   10,501 ms · 30 artifacts
   occurrences of "cache" in either run's stdout/stderr: 0

A progress indicator exists but is TTY-gated (cli.rs:1160-1164), so this applies to non-interactive and agent callers. An 8× timing swing with no explanation also means a user who edits a file and re-scans cannot tell whether the result is fresh or replayed. Distinct from #211, which covers cache internals rather than user-facing disclosure.

A4 — A warning advertises a --deep flag that does not exist.

stderr: warning: scan depth capped at 5; some files may have been skipped (use --deep for a full scan)
$ vettd scan quick --deep
error: unexpected argument '--deep' found

Not listed in vettd scan quick --help. Same class as #201 (post-scan guidance printing invalid commands), on a different code path. The warning is also emitted 5 times byte-identically in a single run.

B. Directory output cannot support an install decision

B1 — --json omits the identity fields the API returns, so duplicate entries are unaddressable.

The API returns two genuinely distinct records under one slug:

API -> {'slug':'us-federal-tax-assistant','id':'cmsi19j0j01bbad07e9sin0gz','publicId':'e3b15630-…','sourceUrl':'https://github.com/calef/…'}
API -> {'slug':'us-federal-tax-assistant','id':'cmsi16jfv019yad07zd98f0gr','publicId':'074c871b-…','sourceUrl':'https://github.com/calef/…'}

CLI --json keys: slug, name, description, version, author, category, badgeStatus,
                 overallGrade, sourceType, scannerRunCount, freshness

DirectoryCard (crates/vettd-cli/src/directory.rs:54-88) does not declare id, publicId, or sourceUrl, per the documented "limited to what we actually render" policy at directory.rs:40-44. That policy is right for a terminal table and wrong for --json, whose consumers render nothing — one struct is serving as both the human render model and the public JSON schema. Since directory view accepts only a slug, the second record is unreachable from the CLI by any route.

B2 — Duplicate entries are indistinguishable, and compare cannot disambiguate them.

[C] [ok]  us-federal-tax-assistant  github  2 scanners  Use this skill when helping users prepare…
[C] [ok]  us-federal-tax-assistant  github  2 scanners  Use this skill when helping users prepare…
[A] [ok]  visa-doc-translate        github  2 scanners  Translate visa application documents (ima…
[A] [ok]  visa-doc-translate        github  2 scanners  ビザ申請書類(画像)を英語に翻訳し…

directory view <slug> silently resolves to one record with no indication a sibling exists. directory compare <slug> <slug> with the same slug twice exits 0 and renders two identical columns without noticing. The visa-doc-translate pair differs only in description language.

B3 — Grades and freshness states have no legend, and freshness vocabulary differs per command.

Search and compare render [ok] / [offline] / [?]; view renders [current] for the same concept. Grades [A]–[F] appear with no scale, no derivation, and no explanation of what is being graded, in any help text or output. Observed mapping, undocumented: 1 medium + 12 info → B, 4 medium + 14 info → C, 1 critical + 1 medium + 17 info → F.

B4 — MCP capability scope does not affect the risk score.

mcp.json: server-filesystem "/"        -> risk 20  ✓ CLEAR
mcp.json: server-filesystem "./docs"   -> risk 20  ✓ CLEAR
mcp.json: command "echo", args ["hi"]  -> risk 20  ✓ CLEAR

All three score the mcp_config type base (risk_engine.rs:79) and nothing else. Mount scope and npx -y fetch-and-execute are not represented in any weight table. Note the scanner handles real secrets correctly — an AWS-shaped key in the same file escalates to CRITICAL/45 — so this is specifically a capability-scope gap. Adjacent to #202/#207 but not covered by either.

B5 — Risk rationale is truncated without saying so.

The highest-scoring artifact found shows risk 100 with reasons dangerous_combo:shell+network+fs (+30), keyword:shell (+15) — 45 of 100 points explained. risk_engine.rs:166 truncates the reasons list to the top 2 contributions with no "top 2 of N" label, and never shows the artifact-type base; score.min(100) at :161 clamps the total, so the pre-clamp score is unknowable. The gap scales with severity and is largest where a user most needs to understand the verdict.

Minor

  • Mixed path separators inside one rendered path: ~\AppData\Roaming\Code/User\globalStorage\...\settings/.
  • YAML block-scalar markers leak into descriptions: directory list shows descriptions of | and >- for feishu-wiki, bigquery-basics, firebase-basics. Present in the raw API response, so platform-side, but the CLI is where users meet it.
  • README Quick start line 1 is wrong: documents vettd as an "Interactive wizard"; bare vettd prints help and exits 0 in both TTY and non-TTY modes. No wizard:: call is reachable without a subcommand — the picker is at cli.rs:1122 inside the scan handler. docs/user-flows.md describes this correctly.
  • README states the full scan is uncapped: README:308 says "full scan enumerates the entire filesystem with no caps"; discovery.rs:24 sets MAX_ROOT_SCAN_FILES = 500_000 and the binary warns accordingly at runtime. Runtime behavior is correct; the doc is not. Related to Scanner silently truncates its scan surface and still reports success #204/Docs, dead code, and stale tooling have drifted from the real codebase #210.

Scope

In scope:

  • Path-type validation for scan file / scan folder (A1)
  • Artifact rendering limits in the overview formatter (A2)
  • Non-interactive progress and cache-reuse disclosure (A3)
  • The --deep warning text and its duplicate emission (A4)
  • DirectoryCard field coverage for --json, and slug-ambiguity handling in view / compare (B1, B2)
  • Grade and freshness legends; freshness vocabulary consistency across search / view / compare (B3)
  • MCP capability-scope contribution to risk score (B4)
  • Risk-rationale disclosure in the formatter (B5)
  • The listed doc corrections

Out of scope:

Acceptance Criteria

  • scan file given a directory, and scan folder given a file, fail with a message naming the correct subcommand and exit non-zero
  • A scan reporting N flagged artifacts displays all N; truncation applies only to artifacts below the flagged threshold
  • Non-interactive scans emit periodic progress to stderr, or the absence of progress in non-interactive mode is a documented decision
  • Scan output states when results were served from cache and how to force a fresh scan
  • The depth-cap warning names a flag that exists, and is emitted once per run
  • directory search --json includes id, publicId, and sourceUrl; view / findings / compare accept a publicId
  • directory view on an ambiguous slug indicates that more than one record matched; compare warns when given the same identity twice
  • Grade and freshness values carry a legend reachable from CLI output or help
  • search, view, and compare use one freshness vocabulary
  • MCP filesystem mount scope contributes to the risk score, with a regression test asserting root-scope scores above project-scope
  • Risk rationale shows the artifact-type base and labels any truncation, or shows all contributions
  • README no longer describes bare vettd as an interactive wizard, and no longer claims the full scan is uncapped

Audit environment: vettd 0.9.3 / skill-scanner 0.1.4, built from source at b0845a0; Windows 11 26200 x86_64; prod endpoint https://vettd.agentichighway.ai; contract v2.4.0. All evidence above is from observed command output. Interactive/TTY paths were not tested — no PTY was available in the audit environment — and authenticated flows (inventory, --submit) were not exercised for lack of a valid API key.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions