# Invalid input handling

- **Pattern:** `ab-000654` (`cli-quality.error-handling.invalid-input`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000654
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000654)

## Why it matters

When a CLI silently ignores unknown flags (via `.allowUnknownOption()` or equivalent), user typos produce no error — `--foramt json` is swallowed, the command runs with the wrong output format, and the user thinks it succeeded. CWE-20 (Improper Input Validation) covers this precisely: inputs that fall outside the accepted set must be rejected, not ignored. The same principle applies to typed arguments: a `--count abc` that coerces to `NaN` and propagates through downstream logic produces silent corruption that can be harder to diagnose than a stack trace. Strict input rejection at the boundary turns typos into immediate feedback instead of silent misconfigurations.

## Severity rationale

Medium because silent acceptance of invalid inputs produces wrong results without any error signal, which is harder to diagnose than a crash.

## Remediation

Never set `.allowUnknownOption()` globally. Validate typed arguments at parse time and fail with a specific message naming the offending value:

```ts
program
  .command('resize <width>')
  .option('--format <type>', 'Output format', (val) => {
    const formats = ['png', 'jpg', 'webp']
    if (!formats.includes(val)) {
      throw new InvalidArgumentError(`Must be one of: ${formats.join(', ')}`)
    }
    return val
  })
  .action((width, opts) => {
    const w = parseInt(width, 10)
    if (isNaN(w) || w <= 0) {
      console.error(`Error: width must be a positive number, got '${width}'`)
      process.exit(2)
    }
  })
```

For enum values in click, use `click.Choice` to get automatic validation and "did you mean?" hints:

```python
@cli.command()
@click.option('--format', type=click.Choice(['png', 'jpg', 'webp']), required=True)
def resize(format):
    pass  # click rejects invalid choices automatically
```

## Detection

- **ID:** `invalid-input`
- **Severity:** `medium`
- **What to look for:** Enumerate every input validation point. For each, check what happens when users provide invalid flags, unknown options, or wrong argument types. Verify that unknown flags produce a specific error (most frameworks do this by default — check that `allowUnknownOption()` or equivalent is NOT set unless intentional). Check that type-coerced arguments (numbers, enums) are validated. Look for "did you mean?" suggestions for typos.
- **Pass criteria:** Unknown flags produce a specific error message naming the unrecognized flag. Invalid argument values (wrong type, out of range, invalid enum) produce specific errors. The CLI does not silently ignore unknown flags — 100% of invalid inputs must produce a descriptive error rather than a stack trace. Report: "X validation points found, all Y reject invalid input gracefully."
- **Fail criteria:** Unknown flags are silently ignored (leading to confusion), or invalid values cause an unhandled exception instead of a helpful error, or `.allowUnknownOption()` is set globally without a clear reason.
- **Skip (N/A) when:** All checks skip when no CLI entry point is detected.
- **Detail on fail:** `"program.allowUnknownOption() is set — any flag like --asdf is silently ignored. User typos won't produce errors"` or `"--count flag accepts any string — passing '--count abc' causes NaN to propagate through the program instead of validating the input as a number"`
- **Remediation:** Strict input validation prevents silent misconfigurations:

  ```ts
  // commander — validate custom types
  program
    .command('resize <width>')
    .option('--format <type>', 'Output format', (val) => {
      const formats = ['png', 'jpg', 'webp']
      if (!formats.includes(val)) {
        throw new InvalidArgumentError(`Must be one of: ${formats.join(', ')}`)
      }
      return val
    })
    .action((width, opts) => {
      const w = parseInt(width, 10)
      if (isNaN(w) || w <= 0) {
        console.error(`Error: width must be a positive number, got '${width}'`)
        process.exit(2)
      }
    })
  ```

  Do NOT set `.allowUnknownOption()` globally — it masks user typos.

## External references

- cwe CWE-20

Taxons: code-quality, user-experience

HTML version: https://auditbuffet.com/patterns/ab-000654
