fix: Only check first non-flag arg against NonDownloadCommands

Scanning all args caused false proxy bypasses when package names or
script arguments matched a NonDownloadCommands entry. For example:
- npm exec test → "test" matched, proxy incorrectly skipped
- npm update config → "config" matched, proxy skipped
- npm publish --tag version → "version" matched, proxy skipped

Fix by checking only the first non-flag argument (the subcommand).
If it is not in NonDownloadCommands we break immediately, so trailing
args never influence the classification. Applied to all four parsers:
npm, pip/pip3, uv, and poetry.

Regression tests added for the false positive cases.
This commit is contained in:
Sahilb315
2026-04-15 02:11:24 +05:30
parent 95b1df30d3
commit 7821c40312
3 changed files with 38 additions and 2 deletions
+8 -2
View File
@@ -134,12 +134,18 @@ func (npm *npmPackageManager) ParseCommand(args []string) (*ParsedCommand, error
}
if installCmdIndex == -1 {
// Check if this is a known non-download command (e.g., npm ls, npm outdated).
// Unknown commands are treated as potential downloads — fail safe.
// Check only the first non-flag arg (the subcommand) against NonDownloadCommands.
// Scanning all args would cause false positives when package names or script
// arguments happen to match a known non-download command (e.g. `npm exec test`,
// `npm update config`, `npm publish --tag version`).
for _, arg := range args {
if strings.HasPrefix(arg, "-") {
continue
}
if slices.Contains(npm.Config.NonDownloadCommands, arg) {
return &ParsedCommand{Command: command, IsKnownNonDownloadCommand: true}, nil
}
break
}
return &ParsedCommand{Command: command}, nil
+22
View File
@@ -561,6 +561,28 @@ func TestNpmProxyBehavior(t *testing.T) {
isKnownNonDownloadCmd: true,
isInstallationCommand: false,
},
// False positive regression: package/script names matching NonDownloadCommands words
{
name: "npm exec test — proxy runs (test is package arg, not subcommand)",
pm: func() (*npmPackageManager, error) { return NewNpmPackageManager(DefaultNpmPackageManagerConfig()) },
command: "npm exec test",
isKnownNonDownloadCmd: false,
isInstallationCommand: false,
},
{
name: "npm update config — proxy runs (config is package name, not subcommand)",
pm: func() (*npmPackageManager, error) { return NewNpmPackageManager(DefaultNpmPackageManagerConfig()) },
command: "npm update config",
isKnownNonDownloadCmd: false,
isInstallationCommand: false,
},
{
name: "npm publish --tag version — proxy runs (version is flag value, not subcommand)",
pm: func() (*npmPackageManager, error) { return NewNpmPackageManager(DefaultNpmPackageManagerConfig()) },
command: "npm publish --tag version",
isKnownNonDownloadCmd: false,
isInstallationCommand: false,
},
// Unknown commands default to proxy running (fail safe)
{
name: "unknown npm subcommand — proxy runs (fail safe)",
+8
View File
@@ -149,9 +149,13 @@ func (p *pipCommandParser) ParseCommand(args []string) (*ParsedCommand, error) {
if installCmdIndex == -1 {
for _, arg := range args {
if strings.HasPrefix(arg, "-") {
continue
}
if slices.Contains(p.config.NonDownloadCommands, arg) {
return &ParsedCommand{Command: command, IsKnownNonDownloadCommand: true}, nil
}
break
}
return &ParsedCommand{Command: command}, nil
@@ -376,9 +380,13 @@ func (p *poetryCommandParser) ParseCommand(args []string) (*ParsedCommand, error
if installCmdIndex == -1 {
for _, arg := range args {
if strings.HasPrefix(arg, "-") {
continue
}
if slices.Contains(p.config.NonDownloadCommands, arg) {
return &ParsedCommand{Command: command, IsKnownNonDownloadCommand: true}, nil
}
break
}
return &ParsedCommand{Command: command}, nil