The resolver tier flag advertises a fixed set of accepted values, but command execution does not validate the value. Unknown tokens are treated like the default on-disk path.
Evidence
cmd/license-tool/commands.go:151 to cmd/license-tool/commands.go:157 binds the resolver tier as a plain string.
cmd/license-tool/commands.go:98 to cmd/license-tool/commands.go:103 passes the raw value into report options and only checks whether it equals tool.
internal/report/report.go:321 to internal/report/report.go:333 treats any value other than off as dependency resolution enabled.
Reproduction
Run audit with a resolver tier value that is not one of the documented tokens.
Observe that the command exits successfully and behaves like on-disk resolution rather than rejecting the bad value.
Why It Matters
A typo in CI can silently change dependency-resolution behavior. This is especially risky for a compliance-oriented command where resolver tier affects audit completeness.
Suggested Fix
Validate the resolver tier at flag parsing or command execution time. Reject unknown values with a usage error and add command tests for accepted and rejected tokens.
## Finding
The resolver tier flag advertises a fixed set of accepted values, but command execution does not validate the value. Unknown tokens are treated like the default on-disk path.
## Evidence
- `cmd/license-tool/commands.go:151` to `cmd/license-tool/commands.go:157` binds the resolver tier as a plain string.
- `cmd/license-tool/commands.go:98` to `cmd/license-tool/commands.go:103` passes the raw value into report options and only checks whether it equals `tool`.
- `internal/report/report.go:321` to `internal/report/report.go:333` treats any value other than `off` as dependency resolution enabled.
## Reproduction
1. Run audit with a resolver tier value that is not one of the documented tokens.
2. Observe that the command exits successfully and behaves like on-disk resolution rather than rejecting the bad value.
## Why It Matters
A typo in CI can silently change dependency-resolution behavior. This is especially risky for a compliance-oriented command where resolver tier affects audit completeness.
## Suggested Fix
Validate the resolver tier at flag parsing or command execution time. Reject unknown values with a usage error and add command tests for accepted and rejected tokens.
Status: confirmed — an unknown --resolve-deps token exits 0 and runs dependency resolution as if ondisk; only off (and tool, for shell-out) are special-cased, so every other string is silently accepted.
Severity: low (label is bug, no severity label; low is the right call). Default --deps=true + default --resolve-deps=ondisk means a typo lands on the same on-disk behavior the user almost certainly wanted, so audit completeness is unchanged for the common case. The one genuinely surprising path is --resolve-deps=of (typo for off): the user intends to disable resolution and instead silently re-enables it. That's a real footgun for a compliance gate but narrow in blast radius — hence low, not medium.
Verified root cause: there is no tier validator anywhere. Repro: built main (v0.2.1), ran audit . --resolve-deps=bogus-typo-tier --format=json in a temp dir → exit 0, full report rendered, dependencies: [] (resolution path entered). For contrast --format=bogus-fmt correctly exits 2. The defect lives across three spots, all matching the issue's cites:
cmd/license-tool/commands.go:bindAuditFlags:156 — cmd.Flags().StringVar(&f.resolveDeps, "resolve-deps", "ondisk", ...) binds a raw string with no validation.
cmd/license-tool/commands.go:newAuditCmd:101-102 and newCheckCmd:133-134 — the raw value is forwarded into report.Options{ResolveDeps: f.resolveDeps, AllowToolShellOut: f.resolveDeps == "tool"}; the only comparison is == "tool".
internal/report/report.go:Audit:324 — if opts.IncludeDeps && opts.ResolveDeps != "off" && pipe.ResolveDeps != nil — the gate is != "off", so any non-off token (including garbage) enables resolution. ParseFormat (report.go:48) is the only token validator in the package; the tier has no equivalent.
Suggested fix: add a report.ParseResolveDeps(raw string) (string, error) mirroring the existing ParseFormat precedent (report.go:48) — accept ondisk/tool/off (and "" → ondisk), reject everything else with fmt.Errorf("report: unknown resolver tier %q", raw). Call it in both command RunE bodies (newAuditCmd ~line 94 and newCheckCmd ~line 126, right next to the existing ParseFormat calls) before constructing report.Options, so audit and check reject identically — this is the same dual-boundary discipline the v0.2.1 universal-shebang fix applied (both the INSERT path in internal/render/render.go and the PreserveBoundary in internal/detect/detect.go). Note there is no filetype-table analog here (tier values aren't per-language), so the "filetype table" leg of the usual three-touch pattern doesn't apply to this issue. Keep the 100% gate green: add cmd and/or report tests exercising each accepted token plus at least one rejected token (the report_test.goParseFormat table at line 27 is the template). Validating centrally in report also lets AllowToolShellOut stay derived from the now-validated value.
Corrections to the issue: none material. Line references (commands.go 151-157 / 98-103, report.go 321-333) all land on the right code; the exact decision points are commands.go:156 + 102/134 and report.go:324. One framing nuance worth flagging for the maintainer (not an error in the body): impact is narrower than "silently change dependency-resolution behavior" implies — an unknown token resolves to the default (ondisk) behavior, so it only diverges from intent when the user meant off; it does not, e.g., silently turn on tool shell-out (that still requires the literal tool).
Open questions:
Where should validation live — in the cmd layer (parse-time, like ParseFormat) or defensively in report.Audit as well? Doing both is safest but the cmd-layer parse is the user-facing one; pick one as the source of truth to avoid a redundant untested branch fighting the 100% gate.
Should --resolve-deps also imply/interact with --deps? Today --deps=false makes the tier moot (report.go:324 short-circuits on IncludeDeps), so --deps=false --resolve-deps=off is redundant — decide whether to leave that as-is or warn.
Empty-string handling: confirm "" should map to ondisk (matching the flag default) rather than erroring, since the default is set at bind time and "" shouldn't normally reach the parser.
Overlaps: none found among the cited code paths.
Automated maintainer review.
**Status**: confirmed — an unknown `--resolve-deps` token exits 0 and runs dependency resolution as if `ondisk`; only `off` (and `tool`, for shell-out) are special-cased, so every other string is silently accepted.
**Severity**: low (label is `bug`, no severity label; low is the right call). Default `--deps=true` + default `--resolve-deps=ondisk` means a typo lands on the same on-disk behavior the user almost certainly wanted, so audit *completeness* is unchanged for the common case. The one genuinely surprising path is `--resolve-deps=of` (typo for `off`): the user intends to disable resolution and instead silently re-enables it. That's a real footgun for a compliance gate but narrow in blast radius — hence low, not medium.
**Verified root cause**: there is no tier validator anywhere. Repro: built `main` (`v0.2.1`), ran `audit . --resolve-deps=bogus-typo-tier --format=json` in a temp dir → exit 0, full report rendered, `dependencies: []` (resolution path entered). For contrast `--format=bogus-fmt` correctly exits 2. The defect lives across three spots, all matching the issue's cites:
- `cmd/license-tool/commands.go:bindAuditFlags:156` — `cmd.Flags().StringVar(&f.resolveDeps, "resolve-deps", "ondisk", ...)` binds a raw string with no validation.
- `cmd/license-tool/commands.go:newAuditCmd:101-102` and `newCheckCmd:133-134` — the raw value is forwarded into `report.Options{ResolveDeps: f.resolveDeps, AllowToolShellOut: f.resolveDeps == "tool"}`; the only comparison is `== "tool"`.
- `internal/report/report.go:Audit:324` — `if opts.IncludeDeps && opts.ResolveDeps != "off" && pipe.ResolveDeps != nil` — the gate is `!= "off"`, so any non-`off` token (including garbage) enables resolution. `ParseFormat` (report.go:48) is the *only* token validator in the package; the tier has no equivalent.
**Suggested fix**: add a `report.ParseResolveDeps(raw string) (string, error)` mirroring the existing `ParseFormat` precedent (report.go:48) — accept `ondisk`/`tool`/`off` (and `""` → `ondisk`), reject everything else with `fmt.Errorf("report: unknown resolver tier %q", raw)`. Call it in **both** command RunE bodies (`newAuditCmd` ~line 94 and `newCheckCmd` ~line 126, right next to the existing `ParseFormat` calls) before constructing `report.Options`, so audit and check reject identically — this is the same dual-boundary discipline the v0.2.1 universal-shebang fix applied (both the INSERT path in `internal/render/render.go` and the `PreserveBoundary` in `internal/detect/detect.go`). Note there is no filetype-table analog here (tier values aren't per-language), so the "filetype table" leg of the usual three-touch pattern doesn't apply to this issue. Keep the 100% gate green: add `cmd` and/or `report` tests exercising each accepted token plus at least one rejected token (the `report_test.go` `ParseFormat` table at line 27 is the template). Validating centrally in `report` also lets `AllowToolShellOut` stay derived from the now-validated value.
**Corrections to the issue**: none material. Line references (commands.go 151-157 / 98-103, report.go 321-333) all land on the right code; the exact decision points are commands.go:156 + 102/134 and report.go:324. One framing nuance worth flagging for the maintainer (not an error in the body): impact is narrower than "silently change dependency-resolution behavior" implies — an unknown token resolves to the *default* (`ondisk`) behavior, so it only diverges from intent when the user meant `off`; it does not, e.g., silently turn on tool shell-out (that still requires the literal `tool`).
**Open questions**:
- Where should validation live — in the `cmd` layer (parse-time, like `ParseFormat`) or defensively in `report.Audit` as well? Doing both is safest but the `cmd`-layer parse is the user-facing one; pick one as the source of truth to avoid a redundant untested branch fighting the 100% gate.
- Should `--resolve-deps` also imply/interact with `--deps`? Today `--deps=false` makes the tier moot (report.go:324 short-circuits on `IncludeDeps`), so `--deps=false --resolve-deps=off` is redundant — decide whether to leave that as-is or warn.
- Empty-string handling: confirm `""` should map to `ondisk` (matching the flag default) rather than erroring, since the default is set at bind time and `""` shouldn't normally reach the parser.
**Overlaps**: none found among the cited code paths.
_Automated maintainer review._
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Finding
The resolver tier flag advertises a fixed set of accepted values, but command execution does not validate the value. Unknown tokens are treated like the default on-disk path.
Evidence
cmd/license-tool/commands.go:151tocmd/license-tool/commands.go:157binds the resolver tier as a plain string.cmd/license-tool/commands.go:98tocmd/license-tool/commands.go:103passes the raw value into report options and only checks whether it equalstool.internal/report/report.go:321tointernal/report/report.go:333treats any value other thanoffas dependency resolution enabled.Reproduction
Why It Matters
A typo in CI can silently change dependency-resolution behavior. This is especially risky for a compliance-oriented command where resolver tier affects audit completeness.
Suggested Fix
Validate the resolver tier at flag parsing or command execution time. Reject unknown values with a usage error and add command tests for accepted and rejected tokens.
Status: confirmed — an unknown
--resolve-depstoken exits 0 and runs dependency resolution as ifondisk; onlyoff(andtool, for shell-out) are special-cased, so every other string is silently accepted.Severity: low (label is
bug, no severity label; low is the right call). Default--deps=true+ default--resolve-deps=ondiskmeans a typo lands on the same on-disk behavior the user almost certainly wanted, so audit completeness is unchanged for the common case. The one genuinely surprising path is--resolve-deps=of(typo foroff): the user intends to disable resolution and instead silently re-enables it. That's a real footgun for a compliance gate but narrow in blast radius — hence low, not medium.Verified root cause: there is no tier validator anywhere. Repro: built
main(v0.2.1), ranaudit . --resolve-deps=bogus-typo-tier --format=jsonin a temp dir → exit 0, full report rendered,dependencies: [](resolution path entered). For contrast--format=bogus-fmtcorrectly exits 2. The defect lives across three spots, all matching the issue's cites:cmd/license-tool/commands.go:bindAuditFlags:156—cmd.Flags().StringVar(&f.resolveDeps, "resolve-deps", "ondisk", ...)binds a raw string with no validation.cmd/license-tool/commands.go:newAuditCmd:101-102andnewCheckCmd:133-134— the raw value is forwarded intoreport.Options{ResolveDeps: f.resolveDeps, AllowToolShellOut: f.resolveDeps == "tool"}; the only comparison is== "tool".internal/report/report.go:Audit:324—if opts.IncludeDeps && opts.ResolveDeps != "off" && pipe.ResolveDeps != nil— the gate is!= "off", so any non-offtoken (including garbage) enables resolution.ParseFormat(report.go:48) is the only token validator in the package; the tier has no equivalent.Suggested fix: add a
report.ParseResolveDeps(raw string) (string, error)mirroring the existingParseFormatprecedent (report.go:48) — acceptondisk/tool/off(and""→ondisk), reject everything else withfmt.Errorf("report: unknown resolver tier %q", raw). Call it in both command RunE bodies (newAuditCmd~line 94 andnewCheckCmd~line 126, right next to the existingParseFormatcalls) before constructingreport.Options, so audit and check reject identically — this is the same dual-boundary discipline the v0.2.1 universal-shebang fix applied (both the INSERT path ininternal/render/render.goand thePreserveBoundaryininternal/detect/detect.go). Note there is no filetype-table analog here (tier values aren't per-language), so the "filetype table" leg of the usual three-touch pattern doesn't apply to this issue. Keep the 100% gate green: addcmdand/orreporttests exercising each accepted token plus at least one rejected token (thereport_test.goParseFormattable at line 27 is the template). Validating centrally inreportalso letsAllowToolShellOutstay derived from the now-validated value.Corrections to the issue: none material. Line references (commands.go 151-157 / 98-103, report.go 321-333) all land on the right code; the exact decision points are commands.go:156 + 102/134 and report.go:324. One framing nuance worth flagging for the maintainer (not an error in the body): impact is narrower than "silently change dependency-resolution behavior" implies — an unknown token resolves to the default (
ondisk) behavior, so it only diverges from intent when the user meantoff; it does not, e.g., silently turn on tool shell-out (that still requires the literaltool).Open questions:
cmdlayer (parse-time, likeParseFormat) or defensively inreport.Auditas well? Doing both is safest but thecmd-layer parse is the user-facing one; pick one as the source of truth to avoid a redundant untested branch fighting the 100% gate.--resolve-depsalso imply/interact with--deps? Today--deps=falsemakes the tier moot (report.go:324 short-circuits onIncludeDeps), so--deps=false --resolve-deps=offis redundant — decide whether to leave that as-is or warn.""should map toondisk(matching the flag default) rather than erroring, since the default is set at bind time and""shouldn't normally reach the parser.Overlaps: none found among the cited code paths.
Automated maintainer review.
Resolved on
developin commit742c124e6ec2afa9312075db3112bc2def3c91ff.Validation:
Closing as fixed.