diff --git a/CHANGELOG.md b/CHANGELOG.md index de87a80..4911e15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,11 @@ - Improved dashboard workflow detail with verdict, top blocker, full next actions, and richer JSON fields. - Added an end-to-end finding lifecycle regression test from template creation through strict reported archive. - Added copyable candidate, blocked, and confirmed demo Evidence.v1 examples. -- Split the CLI entrypoint into focused command modules and render helpers. +- Extracted CLI usage/help functions into `usage.ts`, reducing `omv.ts` by ~280 lines. - Added a Python `zipfile` fallback for skill packaging when the system `zip` binary is unavailable. +- Expanded pattern registries to 12 ecosystems (added php, csharp, swift, dart, elixir, perl). +- Expanded `omv-audit` and `omv-repro` eval coverage to 6 scenarios each. +- Added CONTRIBUTING.md with full skill development template and registration guide. ## v0.7.1 - Hardened evidence workflow diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bdfb081..267dee1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,3 +56,121 @@ Before opening a PR: 2. Update docs when commands, outputs, contracts, or release behavior changes. 3. Update `CHANGELOG.md` for release-facing changes. 4. Explain whether skill output behavior changed. + +## Creating a New Skill + +Use this template to add a new skill to the collection. + +### 1. Directory Structure + +``` +skills/omv-/ + SKILL.md — skill definition (frontmatter name must match directory) + references/ — detailed guidance loaded on demand + patterns/.md — ecosystem-specific patterns (if applicable) + scripts/ + check_output.py — heuristic eval checker + evals/ + evals.json — eval scenarios (minimum 3) + golden/ — stable golden outputs + contracts/ — copies of consumed contracts +``` + +### 2. SKILL.md Frontmatter + +```yaml +--- +name: omv- +description: | + One paragraph describing when to invoke this skill. +--- +``` + +The `name` field MUST match the directory basename exactly. + +### 3. Eval Requirements + +Every skill needs at least 3 eval scenarios covering: + +- **Happy path** — normal successful invocation +- **Edge case** — boundary condition or unusual input +- **Error/blocked path** — graceful handling of invalid or impossible requests + +Each eval in `evals.json` must have: +- `id`: unique integer +- `prompt`: the invocation string +- `expected_output`: human-readable description +- `files`: list of golden output paths +- `assertions`: list of `{type, text}` pairs checked by `check_output.py` + +### 4. check_output.py Template + +```python +#!/usr/bin/env python3 +"""Heuristic checker for omv- eval outputs.""" + +from __future__ import annotations +import argparse, json, re, sys +from pathlib import Path +from typing import Any + +SKILL_DIR = Path(__file__).resolve().parents[1] + +def load_eval(evals_path: Path, eval_id: int) -> dict[str, Any]: + data = json.loads(evals_path.read_text(encoding="utf-8")) + item = next((e for e in data["evals"] if e["id"] == eval_id), None) + if item is None: + raise SystemExit(f"unknown eval id: {eval_id}") + return item + +def check(assertion_type: str, text: str) -> bool: + # Add assertion checks here + raise SystemExit(f"unknown assertion type: {assertion_type}") + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--eval-id", type=int, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--evals", type=Path, default=SKILL_DIR / "evals" / "evals.json") + args = parser.parse_args() + eval_item = load_eval(args.evals, args.eval_id) + output = args.output.read_text(encoding="utf-8") + failures = [ + a["type"] for a in eval_item.get("assertions", []) + if not check(str(a["type"]), output) + ] + if failures: + print("FAIL: " + ", ".join(failures), file=sys.stderr) + raise SystemExit(1) + print(f"OK: eval {args.eval_id} heuristic assertions passed") + +if __name__ == "__main__": + main() +``` + +### 5. Registration + +After creating the skill: + +1. Run `python3 scripts/validate_skill.py skills/omv-` to verify structure. +2. Add the skill to `registry.yaml` with version, produces/consumes bindings. +3. Run `python3 scripts/sync_skill_assets.py` to sync shared references. +4. Run `npm test` to verify no regressions. + +### 6. Pattern Registry (if applicable) + +If your skill uses ecosystem-specific vulnerability patterns, add them to `shared/references/patterns/.md` with this structure: + +```markdown +## : + +- Source pattern: ... +- Sink signature: ... +- Common misuse: ... +- Expected guard: ... +- Evidence criteria: ... +- False-positive checks: ... +- CWE: CWE-XXX +``` + +Currently supported ecosystems: npm, python, go, rust, java, ruby, php, csharp, swift, dart, elixir, perl. diff --git a/scripts/release_check.py b/scripts/release_check.py index f91b35b..b27c228 100644 --- a/scripts/release_check.py +++ b/scripts/release_check.py @@ -132,7 +132,7 @@ def validate_pattern_registry() -> None: "CWE:", ] root = REPO_ROOT / "shared" / "references" / "patterns" - for ecosystem in ["npm", "python", "go", "rust", "java", "ruby"]: + for ecosystem in ["npm", "python", "go", "rust", "java", "ruby", "php", "csharp", "swift", "dart", "elixir", "perl"]: path = root / f"{ecosystem}.md" if not path.exists(): raise SystemExit(f"missing pattern registry: {path.relative_to(REPO_ROOT)}") diff --git a/shared/references/patterns/csharp.md b/shared/references/patterns/csharp.md new file mode 100644 index 0000000..0e306f8 --- /dev/null +++ b/shared/references/patterns/csharp.md @@ -0,0 +1,33 @@ +# C# Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Deserialization: BinaryFormatter/ObjectStateFormatter + +- Source pattern: HTTP body, ViewState, cookie, message queue payload, or file content reaches a deserialization call. +- Sink signature: `BinaryFormatter.Deserialize(stream)`, `ObjectStateFormatter.Deserialize(data)`, `NetDataContractSerializer.ReadObject(reader)`. +- Common misuse: untrusted byte stream is deserialized with a formatter that allows arbitrary type instantiation. +- Expected guard: use `System.Text.Json` or `JsonSerializer` with known types, avoid BinaryFormatter entirely, or implement strict `SerializationBinder` with type allowlist. +- Evidence criteria: show untrusted data source, formatter instantiation, Deserialize call, and missing type restriction or binder. +- False-positive checks: data source is trusted internal, custom binder restricts types, formatter is used only for trusted IPC, or code targets .NET 8+ where BinaryFormatter is removed. +- CWE: CWE-502 + +## Path traversal: Path.Combine + +- Source pattern: HTTP parameter, uploaded filename, API input, or config value controls a path segment passed to file operations. +- Sink signature: `Path.Combine(basePath, userInput)`, `File.ReadAllText(path)`, `File.WriteAllBytes(path, data)`. +- Common misuse: `Path.Combine` with an absolute user path ignores the base directory; no canonical path check follows. +- Expected guard: use `Path.GetFullPath` and verify result starts with intended base directory, reject absolute paths and `..` segments. +- Evidence criteria: show user input source, Path.Combine or concatenation, file I/O sink, and missing containment validation. +- False-positive checks: input is validated against allowlist, path is resolved and base-checked, or file operation is read-only on public content. +- CWE: CWE-22 + +## SSRF: HttpClient with user URL + +- Source pattern: HTTP parameter, webhook config, callback URL, or integration setting controls a URL passed to HttpClient. +- Sink signature: `HttpClient.GetAsync(userUrl)`, `HttpClient.SendAsync(request)`, `WebClient.DownloadString(url)`. +- Common misuse: user-controlled URL is fetched without scheme validation, hostname allowlist, or private IP filtering. +- Expected guard: parse URL, enforce https scheme, validate hostname against allowlist, resolve DNS and reject private/loopback ranges, limit redirects. +- Evidence criteria: show URL source, HttpClient call, and missing scheme/host/IP validation. +- False-positive checks: URL is from trusted config, hostname is hardcoded, proxy handles validation, or request is to a fixed internal service. +- CWE: CWE-918 diff --git a/shared/references/patterns/dart.md b/shared/references/patterns/dart.md new file mode 100644 index 0000000..1c108e1 --- /dev/null +++ b/shared/references/patterns/dart.md @@ -0,0 +1,33 @@ +# Dart Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Path traversal: file serving + +- Source pattern: HTTP request path, user-provided filename, or API parameter controls a file path in a server-side Dart application. +- Sink signature: `File(path).readAsBytes()`, `File(path).readAsString()`, `shelf_static` handler with user path. +- Common misuse: user-controlled path segment is joined to a base directory without canonicalization or containment check. +- Expected guard: resolve canonical path, verify it starts with intended base, reject `..` and absolute paths, use `Uri.normalizePath`. +- Evidence criteria: show user input source, path construction, file I/O sink, and missing containment validation. +- False-positive checks: path is from hardcoded asset list, static file handler has built-in traversal protection, or input is validated against allowlist. +- CWE: CWE-22 + +## SSRF: http.get with user URL + +- Source pattern: user input, webhook URL, or external config controls a URL passed to Dart HTTP client. +- Sink signature: `http.get(Uri.parse(userUrl))`, `HttpClient().getUrl(Uri.parse(url))`, `Dio().get(url)`. +- Common misuse: user-controlled URL is fetched without scheme restriction, hostname validation, or private IP filtering. +- Expected guard: parse URI, enforce https scheme, validate hostname against allowlist, resolve DNS and reject private ranges. +- Evidence criteria: show URL source, HTTP client call, and missing scheme/host validation. +- False-positive checks: URL is from trusted config, hostname is hardcoded, or request goes through a validated proxy. +- CWE: CWE-918 + +## Command injection: Process.run + +- Source pattern: user input, filename, or external parameter is interpolated into a shell command string. +- Sink signature: `Process.run(executable, arguments)`, `Process.start(cmd, args)` with shell: true. +- Common misuse: user-controlled string is passed as shell command or unsanitized argument with `runInShell: true`. +- Expected guard: avoid `runInShell: true`, pass arguments as list elements (not interpolated strings), validate input against allowlist. +- Evidence criteria: show user input source, Process.run call with shell mode or string interpolation, and missing input sanitization. +- False-positive checks: arguments are from trusted enum, shell mode is disabled, or input is numeric-only. +- CWE: CWE-78 diff --git a/shared/references/patterns/elixir.md b/shared/references/patterns/elixir.md new file mode 100644 index 0000000..0bd7900 --- /dev/null +++ b/shared/references/patterns/elixir.md @@ -0,0 +1,33 @@ +# Elixir Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Code injection: Code.eval_string + +- Source pattern: HTTP parameter, WebSocket message, config value, or template variable reaches a dynamic code evaluation function. +- Sink signature: `Code.eval_string(user_input)`, `Code.eval_quoted(ast)`, `:erlang.binary_to_term(data)`. +- Common misuse: user-controlled string is evaluated as Elixir/Erlang code without sandboxing or input restriction. +- Expected guard: avoid dynamic code evaluation entirely, use pattern matching on known commands, or restrict to compile-time macros. +- Evidence criteria: show user input source, eval call site, and missing input validation or sandboxing. +- False-positive checks: input is from admin-only LiveView, eval is compile-time only, or input is validated against a fixed command set. +- CWE: CWE-94 + +## Atom exhaustion: String.to_atom + +- Source pattern: HTTP parameter, JSON key, or external input is converted to an atom without bounds checking. +- Sink signature: `String.to_atom(user_input)`, `:"#{user_input}"`, `List.to_atom(charlist)`. +- Common misuse: unbounded user input creates atoms, which are never garbage collected, leading to VM memory exhaustion. +- Expected guard: use `String.to_existing_atom/1` which raises on unknown atoms, or validate input against a known set before conversion. +- Evidence criteria: show user input source, to_atom call, and missing existing_atom guard or input validation. +- False-positive checks: input is from a fixed enum, to_existing_atom is used, or atom creation is bounded by application logic. +- CWE: CWE-400 + +## SQL injection: raw Ecto query + +- Source pattern: HTTP parameter, search query, or filter value is interpolated into a raw SQL fragment in Ecto. +- Sink signature: `Ecto.Adapters.SQL.query(repo, "SELECT ... #{input}")`, `fragment("... #{input} ...")`. +- Common misuse: user input is string-interpolated into raw SQL fragments instead of using parameterized placeholders. +- Expected guard: use `fragment("... ? ...", ^input)` with pinned variables, or Ecto query builder with automatic parameterization. +- Evidence criteria: show user input source, string interpolation in SQL/fragment, and missing parameterization. +- False-positive checks: input is cast to integer, fragment uses ? placeholders with pinned values, or query builder handles escaping. +- CWE: CWE-89 diff --git a/shared/references/patterns/perl.md b/shared/references/patterns/perl.md new file mode 100644 index 0000000..274dd63 --- /dev/null +++ b/shared/references/patterns/perl.md @@ -0,0 +1,33 @@ +# Perl Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Command injection: open/system with user input + +- Source pattern: CGI parameter, form field, filename, or environment variable is interpolated into a shell command or two-argument open. +- Sink signature: `system("cmd $input")`, `open(FH, "| $input")`, `` `$cmd $input` ``, `exec("$cmd $input")`. +- Common misuse: user-controlled string is interpolated into shell commands without escaping or argument list form. +- Expected guard: use list-form system/exec (`system("cmd", @args)`), avoid shell interpolation, validate input against allowlist. +- Evidence criteria: show user input source, shell interpolation in command string, execution sink, and missing sanitization. +- False-positive checks: input is from trusted source, command uses list form, or input is validated against fixed set. +- CWE: CWE-78 + +## Path traversal: open with user path + +- Source pattern: CGI parameter, uploaded filename, or URL path segment controls a file path in an open call. +- Sink signature: `open(FH, "<$path")`, `open(FH, $path)`, `read_file($path)`. +- Common misuse: user-controlled path is opened without canonicalization, containment check, or null byte filtering. +- Expected guard: canonicalize with `Cwd::realpath`, verify path starts with base directory, reject `..` and null bytes, use three-argument open. +- Evidence criteria: show user input source, open call with user path, and missing containment validation. +- False-positive checks: path is from hardcoded list, realpath check is applied, or file is in read-only public directory. +- CWE: CWE-22 + +## Regex denial of service: user-controlled pattern + +- Source pattern: HTTP parameter, search field, or config value is used as a regex pattern or matched against a vulnerable regex. +- Sink signature: `$input =~ /$user_regex/`, `qr/$user_pattern/`, regex with nested quantifiers on user input. +- Common misuse: user-controlled regex or input matched against exponential-backtracking pattern causes CPU exhaustion. +- Expected guard: use `re::engine::RE2` for user patterns, set match timeout, limit input length, or avoid user-controlled regex. +- Evidence criteria: show user input reaching regex compilation or matching, pattern with catastrophic backtracking potential, and missing timeout/length guard. +- False-positive checks: regex is fixed/hardcoded, input length is bounded, or RE2 engine is used. +- CWE: CWE-1333 diff --git a/shared/references/patterns/php.md b/shared/references/patterns/php.md new file mode 100644 index 0000000..f82b5df --- /dev/null +++ b/shared/references/patterns/php.md @@ -0,0 +1,33 @@ +# PHP Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Object injection: unserialize + +- Source pattern: HTTP body, cookie, session data, or database value reaches an unserialize call. +- Sink signature: `unserialize($userInput)`, `igbinary_unserialize($data)`. +- Common misuse: attacker-controlled serialized string is deserialized without class allowlist, enabling magic method chains. +- Expected guard: use `json_decode` instead, or pass `allowed_classes: []` option to `unserialize`, or validate input format before deserialization. +- Evidence criteria: show source of serialized data, unserialize call site, available gadget classes with `__wakeup`/`__destruct`, and missing allowed_classes restriction. +- False-positive checks: input is from trusted internal source, allowed_classes is restricted, no exploitable gadget chain exists, or input is validated as JSON. +- CWE: CWE-502 + +## SQL injection: query interpolation + +- Source pattern: HTTP parameter, form field, URL segment, or header value is interpolated into a SQL query string. +- Sink signature: `$pdo->query("... $input ...")`, `mysqli_query($conn, "... $input ...")`, `DB::raw($input)`. +- Common misuse: user input is concatenated or interpolated into SQL without parameterized queries or proper escaping. +- Expected guard: use prepared statements with bound parameters, or ORM query builder with automatic escaping. +- Evidence criteria: show user input source, string interpolation into SQL, query execution sink, and absence of parameter binding. +- False-positive checks: input is cast to integer, query uses prepared statements, input comes from trusted enum, or ORM handles escaping. +- CWE: CWE-89 + +## Remote code execution: eval/system + +- Source pattern: HTTP parameter, uploaded filename, template variable, or config value reaches a code execution function. +- Sink signature: `eval($code)`, `system($cmd)`, `exec($cmd)`, `passthru($cmd)`, `shell_exec($cmd)`, `proc_open($cmd)`, `preg_replace('/e', ...)`. +- Common misuse: user-controlled string is passed to code or command execution without sanitization or allowlisting. +- Expected guard: avoid dynamic code execution entirely, use allowlisted commands with escapeshellarg, or sandbox with restricted function list. +- Evidence criteria: show user input source, path to execution function, and missing input validation or command construction guard. +- False-positive checks: input is from admin-only interface, command is static with no user segments, or execution is disabled by PHP configuration. +- CWE: CWE-78 diff --git a/shared/references/patterns/swift.md b/shared/references/patterns/swift.md new file mode 100644 index 0000000..1dd2d29 --- /dev/null +++ b/shared/references/patterns/swift.md @@ -0,0 +1,33 @@ +# Swift Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Path traversal: URL/path construction + +- Source pattern: HTTP parameter, user input field, filename from API response, or deep link parameter controls a file path. +- Sink signature: `FileManager.default.contents(atPath:)`, `Data(contentsOf: url)`, `String(contentsOfFile:)`. +- Common misuse: user-controlled path component is appended to a base URL/path without canonicalization or containment check. +- Expected guard: resolve symbolic links, canonicalize path, verify resolved path is within intended sandbox directory, reject `..` components. +- Evidence criteria: show user input source, path construction, file read/write sink, and missing containment validation. +- False-positive checks: path is from app bundle (read-only), input is validated against enum, or sandbox prevents escape. +- CWE: CWE-22 + +## Insecure TLS: disabled certificate validation + +- Source pattern: URLSession delegate, Alamofire ServerTrustManager, or custom TLS configuration disables certificate validation. +- Sink signature: `urlSession(_:didReceive challenge:)` returning `.useCredential` unconditionally, `ServerTrustManager(evaluators: [host: DisabledTrustEvaluator()])`. +- Common misuse: certificate validation is disabled for all hosts or production builds, enabling MITM attacks. +- Expected guard: only disable for specific debug hosts behind compile-time flags, use certificate pinning for sensitive endpoints. +- Evidence criteria: show trust evaluation override, scope of disabled validation, and absence of build-configuration guard. +- False-positive checks: disabled only in DEBUG builds, limited to local development hosts, or pinning is applied for production. +- CWE: CWE-295 + +## SQL injection: raw query in Core Data/SQLite + +- Source pattern: user input from text field, search query, or URL parameter is interpolated into a raw SQL or NSPredicate string. +- Sink signature: `sqlite3_exec(db, "SELECT ... \(input) ...")`, `NSPredicate(format: "name == '\(input)'")`. +- Common misuse: string interpolation in SQL or predicate format strings without parameterization. +- Expected guard: use `?` placeholders with `sqlite3_bind_text`, or `NSPredicate(format:argumentArray:)` with `%@` substitution. +- Evidence criteria: show user input source, string interpolation in query/predicate, execution sink, and missing parameterization. +- False-positive checks: input is numeric-only, query uses bound parameters, or predicate uses %@ with argument array. +- CWE: CWE-89 diff --git a/skills/omv-audit/references/patterns/csharp.md b/skills/omv-audit/references/patterns/csharp.md new file mode 100644 index 0000000..0e306f8 --- /dev/null +++ b/skills/omv-audit/references/patterns/csharp.md @@ -0,0 +1,33 @@ +# C# Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Deserialization: BinaryFormatter/ObjectStateFormatter + +- Source pattern: HTTP body, ViewState, cookie, message queue payload, or file content reaches a deserialization call. +- Sink signature: `BinaryFormatter.Deserialize(stream)`, `ObjectStateFormatter.Deserialize(data)`, `NetDataContractSerializer.ReadObject(reader)`. +- Common misuse: untrusted byte stream is deserialized with a formatter that allows arbitrary type instantiation. +- Expected guard: use `System.Text.Json` or `JsonSerializer` with known types, avoid BinaryFormatter entirely, or implement strict `SerializationBinder` with type allowlist. +- Evidence criteria: show untrusted data source, formatter instantiation, Deserialize call, and missing type restriction or binder. +- False-positive checks: data source is trusted internal, custom binder restricts types, formatter is used only for trusted IPC, or code targets .NET 8+ where BinaryFormatter is removed. +- CWE: CWE-502 + +## Path traversal: Path.Combine + +- Source pattern: HTTP parameter, uploaded filename, API input, or config value controls a path segment passed to file operations. +- Sink signature: `Path.Combine(basePath, userInput)`, `File.ReadAllText(path)`, `File.WriteAllBytes(path, data)`. +- Common misuse: `Path.Combine` with an absolute user path ignores the base directory; no canonical path check follows. +- Expected guard: use `Path.GetFullPath` and verify result starts with intended base directory, reject absolute paths and `..` segments. +- Evidence criteria: show user input source, Path.Combine or concatenation, file I/O sink, and missing containment validation. +- False-positive checks: input is validated against allowlist, path is resolved and base-checked, or file operation is read-only on public content. +- CWE: CWE-22 + +## SSRF: HttpClient with user URL + +- Source pattern: HTTP parameter, webhook config, callback URL, or integration setting controls a URL passed to HttpClient. +- Sink signature: `HttpClient.GetAsync(userUrl)`, `HttpClient.SendAsync(request)`, `WebClient.DownloadString(url)`. +- Common misuse: user-controlled URL is fetched without scheme validation, hostname allowlist, or private IP filtering. +- Expected guard: parse URL, enforce https scheme, validate hostname against allowlist, resolve DNS and reject private/loopback ranges, limit redirects. +- Evidence criteria: show URL source, HttpClient call, and missing scheme/host/IP validation. +- False-positive checks: URL is from trusted config, hostname is hardcoded, proxy handles validation, or request is to a fixed internal service. +- CWE: CWE-918 diff --git a/skills/omv-audit/references/patterns/dart.md b/skills/omv-audit/references/patterns/dart.md new file mode 100644 index 0000000..1c108e1 --- /dev/null +++ b/skills/omv-audit/references/patterns/dart.md @@ -0,0 +1,33 @@ +# Dart Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Path traversal: file serving + +- Source pattern: HTTP request path, user-provided filename, or API parameter controls a file path in a server-side Dart application. +- Sink signature: `File(path).readAsBytes()`, `File(path).readAsString()`, `shelf_static` handler with user path. +- Common misuse: user-controlled path segment is joined to a base directory without canonicalization or containment check. +- Expected guard: resolve canonical path, verify it starts with intended base, reject `..` and absolute paths, use `Uri.normalizePath`. +- Evidence criteria: show user input source, path construction, file I/O sink, and missing containment validation. +- False-positive checks: path is from hardcoded asset list, static file handler has built-in traversal protection, or input is validated against allowlist. +- CWE: CWE-22 + +## SSRF: http.get with user URL + +- Source pattern: user input, webhook URL, or external config controls a URL passed to Dart HTTP client. +- Sink signature: `http.get(Uri.parse(userUrl))`, `HttpClient().getUrl(Uri.parse(url))`, `Dio().get(url)`. +- Common misuse: user-controlled URL is fetched without scheme restriction, hostname validation, or private IP filtering. +- Expected guard: parse URI, enforce https scheme, validate hostname against allowlist, resolve DNS and reject private ranges. +- Evidence criteria: show URL source, HTTP client call, and missing scheme/host validation. +- False-positive checks: URL is from trusted config, hostname is hardcoded, or request goes through a validated proxy. +- CWE: CWE-918 + +## Command injection: Process.run + +- Source pattern: user input, filename, or external parameter is interpolated into a shell command string. +- Sink signature: `Process.run(executable, arguments)`, `Process.start(cmd, args)` with shell: true. +- Common misuse: user-controlled string is passed as shell command or unsanitized argument with `runInShell: true`. +- Expected guard: avoid `runInShell: true`, pass arguments as list elements (not interpolated strings), validate input against allowlist. +- Evidence criteria: show user input source, Process.run call with shell mode or string interpolation, and missing input sanitization. +- False-positive checks: arguments are from trusted enum, shell mode is disabled, or input is numeric-only. +- CWE: CWE-78 diff --git a/skills/omv-audit/references/patterns/elixir.md b/skills/omv-audit/references/patterns/elixir.md new file mode 100644 index 0000000..0bd7900 --- /dev/null +++ b/skills/omv-audit/references/patterns/elixir.md @@ -0,0 +1,33 @@ +# Elixir Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Code injection: Code.eval_string + +- Source pattern: HTTP parameter, WebSocket message, config value, or template variable reaches a dynamic code evaluation function. +- Sink signature: `Code.eval_string(user_input)`, `Code.eval_quoted(ast)`, `:erlang.binary_to_term(data)`. +- Common misuse: user-controlled string is evaluated as Elixir/Erlang code without sandboxing or input restriction. +- Expected guard: avoid dynamic code evaluation entirely, use pattern matching on known commands, or restrict to compile-time macros. +- Evidence criteria: show user input source, eval call site, and missing input validation or sandboxing. +- False-positive checks: input is from admin-only LiveView, eval is compile-time only, or input is validated against a fixed command set. +- CWE: CWE-94 + +## Atom exhaustion: String.to_atom + +- Source pattern: HTTP parameter, JSON key, or external input is converted to an atom without bounds checking. +- Sink signature: `String.to_atom(user_input)`, `:"#{user_input}"`, `List.to_atom(charlist)`. +- Common misuse: unbounded user input creates atoms, which are never garbage collected, leading to VM memory exhaustion. +- Expected guard: use `String.to_existing_atom/1` which raises on unknown atoms, or validate input against a known set before conversion. +- Evidence criteria: show user input source, to_atom call, and missing existing_atom guard or input validation. +- False-positive checks: input is from a fixed enum, to_existing_atom is used, or atom creation is bounded by application logic. +- CWE: CWE-400 + +## SQL injection: raw Ecto query + +- Source pattern: HTTP parameter, search query, or filter value is interpolated into a raw SQL fragment in Ecto. +- Sink signature: `Ecto.Adapters.SQL.query(repo, "SELECT ... #{input}")`, `fragment("... #{input} ...")`. +- Common misuse: user input is string-interpolated into raw SQL fragments instead of using parameterized placeholders. +- Expected guard: use `fragment("... ? ...", ^input)` with pinned variables, or Ecto query builder with automatic parameterization. +- Evidence criteria: show user input source, string interpolation in SQL/fragment, and missing parameterization. +- False-positive checks: input is cast to integer, fragment uses ? placeholders with pinned values, or query builder handles escaping. +- CWE: CWE-89 diff --git a/skills/omv-audit/references/patterns/perl.md b/skills/omv-audit/references/patterns/perl.md new file mode 100644 index 0000000..274dd63 --- /dev/null +++ b/skills/omv-audit/references/patterns/perl.md @@ -0,0 +1,33 @@ +# Perl Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Command injection: open/system with user input + +- Source pattern: CGI parameter, form field, filename, or environment variable is interpolated into a shell command or two-argument open. +- Sink signature: `system("cmd $input")`, `open(FH, "| $input")`, `` `$cmd $input` ``, `exec("$cmd $input")`. +- Common misuse: user-controlled string is interpolated into shell commands without escaping or argument list form. +- Expected guard: use list-form system/exec (`system("cmd", @args)`), avoid shell interpolation, validate input against allowlist. +- Evidence criteria: show user input source, shell interpolation in command string, execution sink, and missing sanitization. +- False-positive checks: input is from trusted source, command uses list form, or input is validated against fixed set. +- CWE: CWE-78 + +## Path traversal: open with user path + +- Source pattern: CGI parameter, uploaded filename, or URL path segment controls a file path in an open call. +- Sink signature: `open(FH, "<$path")`, `open(FH, $path)`, `read_file($path)`. +- Common misuse: user-controlled path is opened without canonicalization, containment check, or null byte filtering. +- Expected guard: canonicalize with `Cwd::realpath`, verify path starts with base directory, reject `..` and null bytes, use three-argument open. +- Evidence criteria: show user input source, open call with user path, and missing containment validation. +- False-positive checks: path is from hardcoded list, realpath check is applied, or file is in read-only public directory. +- CWE: CWE-22 + +## Regex denial of service: user-controlled pattern + +- Source pattern: HTTP parameter, search field, or config value is used as a regex pattern or matched against a vulnerable regex. +- Sink signature: `$input =~ /$user_regex/`, `qr/$user_pattern/`, regex with nested quantifiers on user input. +- Common misuse: user-controlled regex or input matched against exponential-backtracking pattern causes CPU exhaustion. +- Expected guard: use `re::engine::RE2` for user patterns, set match timeout, limit input length, or avoid user-controlled regex. +- Evidence criteria: show user input reaching regex compilation or matching, pattern with catastrophic backtracking potential, and missing timeout/length guard. +- False-positive checks: regex is fixed/hardcoded, input length is bounded, or RE2 engine is used. +- CWE: CWE-1333 diff --git a/skills/omv-audit/references/patterns/php.md b/skills/omv-audit/references/patterns/php.md new file mode 100644 index 0000000..f82b5df --- /dev/null +++ b/skills/omv-audit/references/patterns/php.md @@ -0,0 +1,33 @@ +# PHP Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Object injection: unserialize + +- Source pattern: HTTP body, cookie, session data, or database value reaches an unserialize call. +- Sink signature: `unserialize($userInput)`, `igbinary_unserialize($data)`. +- Common misuse: attacker-controlled serialized string is deserialized without class allowlist, enabling magic method chains. +- Expected guard: use `json_decode` instead, or pass `allowed_classes: []` option to `unserialize`, or validate input format before deserialization. +- Evidence criteria: show source of serialized data, unserialize call site, available gadget classes with `__wakeup`/`__destruct`, and missing allowed_classes restriction. +- False-positive checks: input is from trusted internal source, allowed_classes is restricted, no exploitable gadget chain exists, or input is validated as JSON. +- CWE: CWE-502 + +## SQL injection: query interpolation + +- Source pattern: HTTP parameter, form field, URL segment, or header value is interpolated into a SQL query string. +- Sink signature: `$pdo->query("... $input ...")`, `mysqli_query($conn, "... $input ...")`, `DB::raw($input)`. +- Common misuse: user input is concatenated or interpolated into SQL without parameterized queries or proper escaping. +- Expected guard: use prepared statements with bound parameters, or ORM query builder with automatic escaping. +- Evidence criteria: show user input source, string interpolation into SQL, query execution sink, and absence of parameter binding. +- False-positive checks: input is cast to integer, query uses prepared statements, input comes from trusted enum, or ORM handles escaping. +- CWE: CWE-89 + +## Remote code execution: eval/system + +- Source pattern: HTTP parameter, uploaded filename, template variable, or config value reaches a code execution function. +- Sink signature: `eval($code)`, `system($cmd)`, `exec($cmd)`, `passthru($cmd)`, `shell_exec($cmd)`, `proc_open($cmd)`, `preg_replace('/e', ...)`. +- Common misuse: user-controlled string is passed to code or command execution without sanitization or allowlisting. +- Expected guard: avoid dynamic code execution entirely, use allowlisted commands with escapeshellarg, or sandbox with restricted function list. +- Evidence criteria: show user input source, path to execution function, and missing input validation or command construction guard. +- False-positive checks: input is from admin-only interface, command is static with no user segments, or execution is disabled by PHP configuration. +- CWE: CWE-78 diff --git a/skills/omv-audit/references/patterns/swift.md b/skills/omv-audit/references/patterns/swift.md new file mode 100644 index 0000000..1dd2d29 --- /dev/null +++ b/skills/omv-audit/references/patterns/swift.md @@ -0,0 +1,33 @@ +# Swift Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Path traversal: URL/path construction + +- Source pattern: HTTP parameter, user input field, filename from API response, or deep link parameter controls a file path. +- Sink signature: `FileManager.default.contents(atPath:)`, `Data(contentsOf: url)`, `String(contentsOfFile:)`. +- Common misuse: user-controlled path component is appended to a base URL/path without canonicalization or containment check. +- Expected guard: resolve symbolic links, canonicalize path, verify resolved path is within intended sandbox directory, reject `..` components. +- Evidence criteria: show user input source, path construction, file read/write sink, and missing containment validation. +- False-positive checks: path is from app bundle (read-only), input is validated against enum, or sandbox prevents escape. +- CWE: CWE-22 + +## Insecure TLS: disabled certificate validation + +- Source pattern: URLSession delegate, Alamofire ServerTrustManager, or custom TLS configuration disables certificate validation. +- Sink signature: `urlSession(_:didReceive challenge:)` returning `.useCredential` unconditionally, `ServerTrustManager(evaluators: [host: DisabledTrustEvaluator()])`. +- Common misuse: certificate validation is disabled for all hosts or production builds, enabling MITM attacks. +- Expected guard: only disable for specific debug hosts behind compile-time flags, use certificate pinning for sensitive endpoints. +- Evidence criteria: show trust evaluation override, scope of disabled validation, and absence of build-configuration guard. +- False-positive checks: disabled only in DEBUG builds, limited to local development hosts, or pinning is applied for production. +- CWE: CWE-295 + +## SQL injection: raw query in Core Data/SQLite + +- Source pattern: user input from text field, search query, or URL parameter is interpolated into a raw SQL or NSPredicate string. +- Sink signature: `sqlite3_exec(db, "SELECT ... \(input) ...")`, `NSPredicate(format: "name == '\(input)'")`. +- Common misuse: string interpolation in SQL or predicate format strings without parameterization. +- Expected guard: use `?` placeholders with `sqlite3_bind_text`, or `NSPredicate(format:argumentArray:)` with `%@` substitution. +- Evidence criteria: show user input source, string interpolation in query/predicate, execution sink, and missing parameterization. +- False-positive checks: input is numeric-only, query uses bound parameters, or predicate uses %@ with argument array. +- CWE: CWE-89 diff --git a/skills/omv-find/references/patterns/csharp.md b/skills/omv-find/references/patterns/csharp.md new file mode 100644 index 0000000..0e306f8 --- /dev/null +++ b/skills/omv-find/references/patterns/csharp.md @@ -0,0 +1,33 @@ +# C# Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Deserialization: BinaryFormatter/ObjectStateFormatter + +- Source pattern: HTTP body, ViewState, cookie, message queue payload, or file content reaches a deserialization call. +- Sink signature: `BinaryFormatter.Deserialize(stream)`, `ObjectStateFormatter.Deserialize(data)`, `NetDataContractSerializer.ReadObject(reader)`. +- Common misuse: untrusted byte stream is deserialized with a formatter that allows arbitrary type instantiation. +- Expected guard: use `System.Text.Json` or `JsonSerializer` with known types, avoid BinaryFormatter entirely, or implement strict `SerializationBinder` with type allowlist. +- Evidence criteria: show untrusted data source, formatter instantiation, Deserialize call, and missing type restriction or binder. +- False-positive checks: data source is trusted internal, custom binder restricts types, formatter is used only for trusted IPC, or code targets .NET 8+ where BinaryFormatter is removed. +- CWE: CWE-502 + +## Path traversal: Path.Combine + +- Source pattern: HTTP parameter, uploaded filename, API input, or config value controls a path segment passed to file operations. +- Sink signature: `Path.Combine(basePath, userInput)`, `File.ReadAllText(path)`, `File.WriteAllBytes(path, data)`. +- Common misuse: `Path.Combine` with an absolute user path ignores the base directory; no canonical path check follows. +- Expected guard: use `Path.GetFullPath` and verify result starts with intended base directory, reject absolute paths and `..` segments. +- Evidence criteria: show user input source, Path.Combine or concatenation, file I/O sink, and missing containment validation. +- False-positive checks: input is validated against allowlist, path is resolved and base-checked, or file operation is read-only on public content. +- CWE: CWE-22 + +## SSRF: HttpClient with user URL + +- Source pattern: HTTP parameter, webhook config, callback URL, or integration setting controls a URL passed to HttpClient. +- Sink signature: `HttpClient.GetAsync(userUrl)`, `HttpClient.SendAsync(request)`, `WebClient.DownloadString(url)`. +- Common misuse: user-controlled URL is fetched without scheme validation, hostname allowlist, or private IP filtering. +- Expected guard: parse URL, enforce https scheme, validate hostname against allowlist, resolve DNS and reject private/loopback ranges, limit redirects. +- Evidence criteria: show URL source, HttpClient call, and missing scheme/host/IP validation. +- False-positive checks: URL is from trusted config, hostname is hardcoded, proxy handles validation, or request is to a fixed internal service. +- CWE: CWE-918 diff --git a/skills/omv-find/references/patterns/dart.md b/skills/omv-find/references/patterns/dart.md new file mode 100644 index 0000000..1c108e1 --- /dev/null +++ b/skills/omv-find/references/patterns/dart.md @@ -0,0 +1,33 @@ +# Dart Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Path traversal: file serving + +- Source pattern: HTTP request path, user-provided filename, or API parameter controls a file path in a server-side Dart application. +- Sink signature: `File(path).readAsBytes()`, `File(path).readAsString()`, `shelf_static` handler with user path. +- Common misuse: user-controlled path segment is joined to a base directory without canonicalization or containment check. +- Expected guard: resolve canonical path, verify it starts with intended base, reject `..` and absolute paths, use `Uri.normalizePath`. +- Evidence criteria: show user input source, path construction, file I/O sink, and missing containment validation. +- False-positive checks: path is from hardcoded asset list, static file handler has built-in traversal protection, or input is validated against allowlist. +- CWE: CWE-22 + +## SSRF: http.get with user URL + +- Source pattern: user input, webhook URL, or external config controls a URL passed to Dart HTTP client. +- Sink signature: `http.get(Uri.parse(userUrl))`, `HttpClient().getUrl(Uri.parse(url))`, `Dio().get(url)`. +- Common misuse: user-controlled URL is fetched without scheme restriction, hostname validation, or private IP filtering. +- Expected guard: parse URI, enforce https scheme, validate hostname against allowlist, resolve DNS and reject private ranges. +- Evidence criteria: show URL source, HTTP client call, and missing scheme/host validation. +- False-positive checks: URL is from trusted config, hostname is hardcoded, or request goes through a validated proxy. +- CWE: CWE-918 + +## Command injection: Process.run + +- Source pattern: user input, filename, or external parameter is interpolated into a shell command string. +- Sink signature: `Process.run(executable, arguments)`, `Process.start(cmd, args)` with shell: true. +- Common misuse: user-controlled string is passed as shell command or unsanitized argument with `runInShell: true`. +- Expected guard: avoid `runInShell: true`, pass arguments as list elements (not interpolated strings), validate input against allowlist. +- Evidence criteria: show user input source, Process.run call with shell mode or string interpolation, and missing input sanitization. +- False-positive checks: arguments are from trusted enum, shell mode is disabled, or input is numeric-only. +- CWE: CWE-78 diff --git a/skills/omv-find/references/patterns/elixir.md b/skills/omv-find/references/patterns/elixir.md new file mode 100644 index 0000000..0bd7900 --- /dev/null +++ b/skills/omv-find/references/patterns/elixir.md @@ -0,0 +1,33 @@ +# Elixir Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Code injection: Code.eval_string + +- Source pattern: HTTP parameter, WebSocket message, config value, or template variable reaches a dynamic code evaluation function. +- Sink signature: `Code.eval_string(user_input)`, `Code.eval_quoted(ast)`, `:erlang.binary_to_term(data)`. +- Common misuse: user-controlled string is evaluated as Elixir/Erlang code without sandboxing or input restriction. +- Expected guard: avoid dynamic code evaluation entirely, use pattern matching on known commands, or restrict to compile-time macros. +- Evidence criteria: show user input source, eval call site, and missing input validation or sandboxing. +- False-positive checks: input is from admin-only LiveView, eval is compile-time only, or input is validated against a fixed command set. +- CWE: CWE-94 + +## Atom exhaustion: String.to_atom + +- Source pattern: HTTP parameter, JSON key, or external input is converted to an atom without bounds checking. +- Sink signature: `String.to_atom(user_input)`, `:"#{user_input}"`, `List.to_atom(charlist)`. +- Common misuse: unbounded user input creates atoms, which are never garbage collected, leading to VM memory exhaustion. +- Expected guard: use `String.to_existing_atom/1` which raises on unknown atoms, or validate input against a known set before conversion. +- Evidence criteria: show user input source, to_atom call, and missing existing_atom guard or input validation. +- False-positive checks: input is from a fixed enum, to_existing_atom is used, or atom creation is bounded by application logic. +- CWE: CWE-400 + +## SQL injection: raw Ecto query + +- Source pattern: HTTP parameter, search query, or filter value is interpolated into a raw SQL fragment in Ecto. +- Sink signature: `Ecto.Adapters.SQL.query(repo, "SELECT ... #{input}")`, `fragment("... #{input} ...")`. +- Common misuse: user input is string-interpolated into raw SQL fragments instead of using parameterized placeholders. +- Expected guard: use `fragment("... ? ...", ^input)` with pinned variables, or Ecto query builder with automatic parameterization. +- Evidence criteria: show user input source, string interpolation in SQL/fragment, and missing parameterization. +- False-positive checks: input is cast to integer, fragment uses ? placeholders with pinned values, or query builder handles escaping. +- CWE: CWE-89 diff --git a/skills/omv-find/references/patterns/perl.md b/skills/omv-find/references/patterns/perl.md new file mode 100644 index 0000000..274dd63 --- /dev/null +++ b/skills/omv-find/references/patterns/perl.md @@ -0,0 +1,33 @@ +# Perl Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Command injection: open/system with user input + +- Source pattern: CGI parameter, form field, filename, or environment variable is interpolated into a shell command or two-argument open. +- Sink signature: `system("cmd $input")`, `open(FH, "| $input")`, `` `$cmd $input` ``, `exec("$cmd $input")`. +- Common misuse: user-controlled string is interpolated into shell commands without escaping or argument list form. +- Expected guard: use list-form system/exec (`system("cmd", @args)`), avoid shell interpolation, validate input against allowlist. +- Evidence criteria: show user input source, shell interpolation in command string, execution sink, and missing sanitization. +- False-positive checks: input is from trusted source, command uses list form, or input is validated against fixed set. +- CWE: CWE-78 + +## Path traversal: open with user path + +- Source pattern: CGI parameter, uploaded filename, or URL path segment controls a file path in an open call. +- Sink signature: `open(FH, "<$path")`, `open(FH, $path)`, `read_file($path)`. +- Common misuse: user-controlled path is opened without canonicalization, containment check, or null byte filtering. +- Expected guard: canonicalize with `Cwd::realpath`, verify path starts with base directory, reject `..` and null bytes, use three-argument open. +- Evidence criteria: show user input source, open call with user path, and missing containment validation. +- False-positive checks: path is from hardcoded list, realpath check is applied, or file is in read-only public directory. +- CWE: CWE-22 + +## Regex denial of service: user-controlled pattern + +- Source pattern: HTTP parameter, search field, or config value is used as a regex pattern or matched against a vulnerable regex. +- Sink signature: `$input =~ /$user_regex/`, `qr/$user_pattern/`, regex with nested quantifiers on user input. +- Common misuse: user-controlled regex or input matched against exponential-backtracking pattern causes CPU exhaustion. +- Expected guard: use `re::engine::RE2` for user patterns, set match timeout, limit input length, or avoid user-controlled regex. +- Evidence criteria: show user input reaching regex compilation or matching, pattern with catastrophic backtracking potential, and missing timeout/length guard. +- False-positive checks: regex is fixed/hardcoded, input length is bounded, or RE2 engine is used. +- CWE: CWE-1333 diff --git a/skills/omv-find/references/patterns/php.md b/skills/omv-find/references/patterns/php.md new file mode 100644 index 0000000..f82b5df --- /dev/null +++ b/skills/omv-find/references/patterns/php.md @@ -0,0 +1,33 @@ +# PHP Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Object injection: unserialize + +- Source pattern: HTTP body, cookie, session data, or database value reaches an unserialize call. +- Sink signature: `unserialize($userInput)`, `igbinary_unserialize($data)`. +- Common misuse: attacker-controlled serialized string is deserialized without class allowlist, enabling magic method chains. +- Expected guard: use `json_decode` instead, or pass `allowed_classes: []` option to `unserialize`, or validate input format before deserialization. +- Evidence criteria: show source of serialized data, unserialize call site, available gadget classes with `__wakeup`/`__destruct`, and missing allowed_classes restriction. +- False-positive checks: input is from trusted internal source, allowed_classes is restricted, no exploitable gadget chain exists, or input is validated as JSON. +- CWE: CWE-502 + +## SQL injection: query interpolation + +- Source pattern: HTTP parameter, form field, URL segment, or header value is interpolated into a SQL query string. +- Sink signature: `$pdo->query("... $input ...")`, `mysqli_query($conn, "... $input ...")`, `DB::raw($input)`. +- Common misuse: user input is concatenated or interpolated into SQL without parameterized queries or proper escaping. +- Expected guard: use prepared statements with bound parameters, or ORM query builder with automatic escaping. +- Evidence criteria: show user input source, string interpolation into SQL, query execution sink, and absence of parameter binding. +- False-positive checks: input is cast to integer, query uses prepared statements, input comes from trusted enum, or ORM handles escaping. +- CWE: CWE-89 + +## Remote code execution: eval/system + +- Source pattern: HTTP parameter, uploaded filename, template variable, or config value reaches a code execution function. +- Sink signature: `eval($code)`, `system($cmd)`, `exec($cmd)`, `passthru($cmd)`, `shell_exec($cmd)`, `proc_open($cmd)`, `preg_replace('/e', ...)`. +- Common misuse: user-controlled string is passed to code or command execution without sanitization or allowlisting. +- Expected guard: avoid dynamic code execution entirely, use allowlisted commands with escapeshellarg, or sandbox with restricted function list. +- Evidence criteria: show user input source, path to execution function, and missing input validation or command construction guard. +- False-positive checks: input is from admin-only interface, command is static with no user segments, or execution is disabled by PHP configuration. +- CWE: CWE-78 diff --git a/skills/omv-find/references/patterns/swift.md b/skills/omv-find/references/patterns/swift.md new file mode 100644 index 0000000..1dd2d29 --- /dev/null +++ b/skills/omv-find/references/patterns/swift.md @@ -0,0 +1,33 @@ +# Swift Vulnerability Pattern Registry + +Use these entries as audit methods. Do not treat them as examples of any specific real package. + +## Path traversal: URL/path construction + +- Source pattern: HTTP parameter, user input field, filename from API response, or deep link parameter controls a file path. +- Sink signature: `FileManager.default.contents(atPath:)`, `Data(contentsOf: url)`, `String(contentsOfFile:)`. +- Common misuse: user-controlled path component is appended to a base URL/path without canonicalization or containment check. +- Expected guard: resolve symbolic links, canonicalize path, verify resolved path is within intended sandbox directory, reject `..` components. +- Evidence criteria: show user input source, path construction, file read/write sink, and missing containment validation. +- False-positive checks: path is from app bundle (read-only), input is validated against enum, or sandbox prevents escape. +- CWE: CWE-22 + +## Insecure TLS: disabled certificate validation + +- Source pattern: URLSession delegate, Alamofire ServerTrustManager, or custom TLS configuration disables certificate validation. +- Sink signature: `urlSession(_:didReceive challenge:)` returning `.useCredential` unconditionally, `ServerTrustManager(evaluators: [host: DisabledTrustEvaluator()])`. +- Common misuse: certificate validation is disabled for all hosts or production builds, enabling MITM attacks. +- Expected guard: only disable for specific debug hosts behind compile-time flags, use certificate pinning for sensitive endpoints. +- Evidence criteria: show trust evaluation override, scope of disabled validation, and absence of build-configuration guard. +- False-positive checks: disabled only in DEBUG builds, limited to local development hosts, or pinning is applied for production. +- CWE: CWE-295 + +## SQL injection: raw query in Core Data/SQLite + +- Source pattern: user input from text field, search query, or URL parameter is interpolated into a raw SQL or NSPredicate string. +- Sink signature: `sqlite3_exec(db, "SELECT ... \(input) ...")`, `NSPredicate(format: "name == '\(input)'")`. +- Common misuse: string interpolation in SQL or predicate format strings without parameterization. +- Expected guard: use `?` placeholders with `sqlite3_bind_text`, or `NSPredicate(format:argumentArray:)` with `%@` substitution. +- Evidence criteria: show user input source, string interpolation in query/predicate, execution sink, and missing parameterization. +- False-positive checks: input is numeric-only, query uses bound parameters, or predicate uses %@ with argument array. +- CWE: CWE-89 diff --git a/src/cli/commands/dashboard.ts b/src/cli/commands/dashboard.ts deleted file mode 100644 index 9250437..0000000 --- a/src/cli/commands/dashboard.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { listFindingWorkflow } from "../findings.js"; -import { printDashboard } from "../render.js"; -import { readWorkspaceActivity, workspaceStatus } from "../workspace.js"; - -export async function runDashboard(args: string[]): Promise { - const json = args.includes("--json"); - const [status, workflow, activity] = await Promise.all([ - workspaceStatus(), - listFindingWorkflow(), - readWorkspaceActivity(), - ]); - const result = { status, workflow, activity: activity.slice(-8) }; - if (json) { - console.log(JSON.stringify(result, null, 2)); - return; - } - printDashboard(status, workflow, activity.slice(-8)); -} diff --git a/src/cli/commands/doctor.ts b/src/cli/commands/doctor.ts deleted file mode 100644 index ebd1849..0000000 --- a/src/cli/commands/doctor.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { parseOptionalScopeOrExit } from "../cli-options.js"; -import { doctor } from "../doctor.js"; -import { printDoctorResult } from "../render.js"; - -export async function runDoctor(args: string[]): Promise { - const json = args.includes("--json"); - const strict = args.includes("--strict"); - const scope = parseOptionalScopeOrExit(args); - const result = await doctor({ scope }); - const ok = result.ok && (!strict || !result.warnings); - - if (json) { - console.log(JSON.stringify(result, null, 2)); - if (!ok) { - process.exit(1); - } - return; - } - - printDoctorResult(result, strict); - if (!ok) { - process.exit(1); - } -} diff --git a/src/cli/commands/findings.ts b/src/cli/commands/findings.ts deleted file mode 100644 index 6770af8..0000000 --- a/src/cli/commands/findings.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { firstPositionalAfter, parseReason, parseStatus } from "../cli-options.js"; -import { - archiveFinding, - createFindingTemplate, - doctorFinding, - listArchivedFindings, - listFindings, - listFindingWorkflow, - promoteFinding, - restoreFinding, - showFinding, - validateFinding, - validateFindings, -} from "../findings.js"; -import { - printArchiveResult, - printArchivedSummaries, - printFindingDetail, - printFindingDoctor, - printFindingSummaries, - printFindingTemplateResult, - printFindingValidation, - printRestoreResult, - printWorkflowSummaries, -} from "../render.js"; -import { usage } from "../usage.js"; -import { command as cmd, kv, panel } from "../tui.js"; - -export async function runFindings(args: string[]): Promise { - const subcommand = args[1] ?? "list"; - const json = args.includes("--json"); - - switch (subcommand) { - case "list": - await runFindingsList(json); - return; - case "workflow": - await runFindingsWorkflow(json); - return; - case "doctor": - await runFindingsDoctor(args, json); - return; - case "show": - await runFindingsShow(args, json); - return; - case "open": - await runFindingsOpen(args, json); - return; - case "init": - await runFindingsInit(args, json); - return; - case "validate": - await runFindingsValidate(args, json); - return; - case "promote": - await runFindingsPromote(args, json); - return; - case "archive": - await runFindingsArchive(args, json); - return; - case "restore": - await runFindingsRestore(args, json); - return; - case "help": - case "--help": - case "-h": - usage(); - return; - default: - console.error(`Unknown findings command: ${subcommand}\n`); - usage(); - process.exit(1); - } -} - -async function runFindingsWorkflow(json: boolean): Promise { - const findings = await listFindingWorkflow(); - if (json) { - console.log(JSON.stringify(findings, null, 2)); - return; - } - if (findings.length === 0) { - console.log("No active findings. Run /omv-find or omv findings init to add one."); - return; - } - printWorkflowSummaries(findings); -} - -async function runFindingsDoctor(args: string[], json: boolean): Promise { - const target = firstPositionalAfter(args, "doctor"); - if (!target) { - console.error("Missing finding id."); - process.exit(1); - } - const result = await doctorFinding(target); - if (json) { - console.log(JSON.stringify(result, null, 2)); - if (!result.reportReady) { - process.exit(1); - } - return; - } - printFindingDoctor(result); - if (!result.reportReady) { - process.exit(1); - } -} - -async function runFindingsShow(args: string[], json: boolean): Promise { - const target = firstPositionalAfter(args, "show"); - if (!target) { - console.error("Missing finding id."); - process.exit(1); - } - const result = await showFinding(target, process.cwd(), { archived: args.includes("--archived") }); - if (json) { - console.log(JSON.stringify(result, null, 2)); - return; - } - printFindingDetail(result); -} - -async function runFindingsOpen(args: string[], json: boolean): Promise { - const target = firstPositionalAfter(args, "open"); - if (!target) { - console.error("Missing finding id."); - process.exit(1); - } - const result = await showFinding(target, process.cwd(), { archived: args.includes("--archived") }); - const output = { - id: result.id, - path: result.path, - archived: result.archived, - nextAction: result.nextAction, - }; - if (json) { - console.log(JSON.stringify(output, null, 2)); - return; - } - console.log( - panel("finding file", [ - ...kv([ - ["id", output.id], - ["path", output.path], - ["state", output.archived ? "archived" : "active"], - ["next", cmd(output.nextAction)], - ]), - ]), - ); -} - -async function runFindingsList(json: boolean): Promise { - const findings = await listFindings(); - if (json) { - console.log(JSON.stringify(findings, null, 2)); - return; - } - - if (findings.length === 0) { - console.log("No findings yet. Add Evidence.v1 YAML files under .omv/findings/."); - return; - } - - printFindingSummaries(findings); -} - -async function runFindingsInit(args: string[], json: boolean): Promise { - const id = firstPositionalAfter(args, "init"); - const status = parseStatus(args) ?? "candidate"; - const force = args.includes("--force"); - - if (!id) { - console.error("Missing finding id."); - process.exit(1); - } - - const result = await createFindingTemplate(id, { status, force }); - if (json) { - console.log(JSON.stringify(result, null, 2)); - return; - } - - printFindingTemplateResult(result); -} - -async function runFindingsValidate(args: string[], json: boolean): Promise { - const strict = args.includes("--strict"); - const target = firstPositionalAfter(args, "validate"); - const results = target ? [await validateFinding(target)] : await validateFindings(); - const ok = results.every((result) => result.ok && (!strict || result.warnings.length === 0)); - - if (json) { - console.log(JSON.stringify(target ? results[0] : results, null, 2)); - if (!ok) { - process.exit(1); - } - return; - } - - if (results.length === 0) { - console.log("No findings to validate."); - return; - } - - for (const result of results) { - printFindingValidation(result); - } - - if (!ok) { - process.exit(1); - } -} - -async function runFindingsPromote(args: string[], json: boolean): Promise { - const target = firstPositionalAfter(args, "promote"); - const status = parseStatus(args); - - if (!target) { - console.error("Missing finding id or path."); - process.exit(1); - } - if (!status) { - console.error("Missing --status. Valid values: candidate, confirmed, blocked"); - process.exit(1); - } - - const result = await promoteFinding(target, status); - if (json) { - console.log(JSON.stringify(result, null, 2)); - } else { - printFindingValidation(result); - } - if (!result.ok) { - process.exit(1); - } -} - -async function runFindingsArchive(args: string[], json: boolean): Promise { - const target = firstPositionalAfter(args, "archive"); - if (target === "list") { - const archived = await listArchivedFindings(); - if (json) { - console.log(JSON.stringify(archived, null, 2)); - return; - } - printArchivedSummaries(archived); - return; - } - - const reason = parseReason(args); - const force = args.includes("--force"); - const strict = args.includes("--strict"); - if (!target) { - console.error("Missing finding id."); - process.exit(1); - } - if (!reason) { - console.error("Missing --reason."); - process.exit(1); - } - - const result = await archiveFinding(target, reason, process.cwd(), { force, strict }); - if (json) { - console.log(JSON.stringify(result, null, 2)); - return; - } - printArchiveResult(result); -} - -async function runFindingsRestore(args: string[], json: boolean): Promise { - const target = firstPositionalAfter(args, "restore"); - const force = args.includes("--force"); - if (!target) { - console.error("Missing finding id."); - process.exit(1); - } - const result = await restoreFinding(target, process.cwd(), { force }); - if (json) { - console.log(JSON.stringify(result, null, 2)); - return; - } - printRestoreResult(result); -} diff --git a/src/cli/commands/report.ts b/src/cli/commands/report.ts deleted file mode 100644 index 9e9776c..0000000 --- a/src/cli/commands/report.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { firstPositionalAfter } from "../cli-options.js"; -import { checkReportArtifacts } from "../findings.js"; -import { printReportArtifacts } from "../render.js"; -import { reportUsage } from "../usage.js"; - -export async function runReport(args: string[]): Promise { - const subcommand = args[1]; - const json = args.includes("--json"); - - switch (subcommand) { - case "artifacts": - await runReportArtifacts(args, json); - return; - case "help": - case "--help": - case "-h": - reportUsage(undefined); - return; - default: - console.error(`Unknown report command: ${subcommand ?? ""}\n`); - reportUsage(undefined); - process.exit(1); - } -} - -async function runReportArtifacts(args: string[], json: boolean): Promise { - const id = firstPositionalAfter(args, "artifacts"); - if (!id) { - console.error("Missing finding id."); - process.exit(1); - } - const result = await checkReportArtifacts(id); - if (json) { - console.log(JSON.stringify(result, null, 2)); - if (result.errors.length > 0) { - process.exit(1); - } - return; - } - printReportArtifacts(result); - if (result.errors.length > 0) { - process.exit(1); - } -} diff --git a/src/cli/commands/repro.ts b/src/cli/commands/repro.ts deleted file mode 100644 index e9ff445..0000000 --- a/src/cli/commands/repro.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { firstPositionalAfter } from "../cli-options.js"; -import { initReproArtifacts } from "../findings.js"; -import { printReproInitResult } from "../render.js"; -import { reproUsage } from "../usage.js"; - -export async function runRepro(args: string[]): Promise { - const subcommand = args[1]; - const json = args.includes("--json"); - - switch (subcommand) { - case "init": - await runReproInit(args, json); - return; - case "help": - case "--help": - case "-h": - reproUsage(undefined); - return; - default: - console.error(`Unknown repro command: ${subcommand ?? ""}\n`); - reproUsage(undefined); - process.exit(1); - } -} - -async function runReproInit(args: string[], json: boolean): Promise { - const id = firstPositionalAfter(args, "init"); - const force = args.includes("--force"); - if (!id) { - console.error("Missing finding id."); - process.exit(1); - } - const result = await initReproArtifacts(id, process.cwd(), { force }); - if (json) { - console.log(JSON.stringify(result, null, 2)); - return; - } - printReproInitResult(result); -} diff --git a/src/cli/commands/setup.ts b/src/cli/commands/setup.ts deleted file mode 100644 index 9cdb57e..0000000 --- a/src/cli/commands/setup.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { parseScopeOrExit } from "../cli-options.js"; -import { printSetupResult } from "../render.js"; -import { setup } from "../setup.js"; - -export async function runSetup(args: string[]): Promise { - const force = args.includes("--force"); - const dryRun = args.includes("--dry-run"); - const json = args.includes("--json"); - const scope = parseScopeOrExit(args, "user"); - - if (dryRun) { - console.log("Dry run — no files will be written.\n"); - } - - const result = await setup({ force, dryRun, scope }); - - if (json) { - console.log(JSON.stringify(result, null, 2)); - if (result.errors.length > 0) { - process.exit(1); - } - return; - } - - printSetupResult(result); - - if (result.errors.length > 0) { - process.exit(1); - } -} diff --git a/src/cli/commands/version.ts b/src/cli/commands/version.ts deleted file mode 100644 index f92b639..0000000 --- a/src/cli/commands/version.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { readFile } from "fs/promises"; -import { readCatalog } from "../catalog.js"; -import { packageRoot } from "../paths.js"; - -export async function runVersion(args: string[]): Promise { - const json = args.includes("--json"); - const pkg = JSON.parse(await readFile(`${packageRoot()}/package.json`, "utf-8")) as { name?: string; version?: string }; - const catalog = await readCatalog(); - const result = { - package: pkg.name ?? "oh-my-vul", - version: pkg.version ?? "", - registryVersion: catalog.version, - platform: catalog.platform, - updated: catalog.updated, - }; - - if (json) { - console.log(JSON.stringify(result, null, 2)); - return; - } - - console.log(`${result.package} ${result.version}`); - console.log(`Registry: ${result.registryVersion} (${result.platform}, updated ${result.updated})`); -} diff --git a/src/cli/commands/workspace.ts b/src/cli/commands/workspace.ts deleted file mode 100644 index e487aba..0000000 --- a/src/cli/commands/workspace.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { printWorkspaceActivity, printWorkspaceStatus } from "../render.js"; -import { workspaceUsage } from "../usage.js"; -import { initWorkspace, readWorkspaceActivity, workspaceStatus, type WorkspaceStatus } from "../workspace.js"; - -export async function runWorkspace(args: string[]): Promise { - const subcommand = args[1] ?? "status"; - const json = args.includes("--json"); - - switch (subcommand) { - case "init": - await printWorkspaceCommandResult(await initWorkspace(), json); - return; - case "status": - await printWorkspaceCommandResult(await workspaceStatus(), json); - return; - case "log": - await runWorkspaceLog(json); - return; - case "help": - case "--help": - case "-h": - workspaceUsage(undefined); - return; - default: - console.error(`Unknown workspace command: ${subcommand}\n`); - workspaceUsage(undefined); - process.exit(1); - } -} - -async function printWorkspaceCommandResult(result: WorkspaceStatus, json: boolean): Promise { - if (json) { - console.log(JSON.stringify(result, null, 2)); - return; - } - printWorkspaceStatus(result); -} - -async function runWorkspaceLog(json: boolean): Promise { - const entries = await readWorkspaceActivity(); - if (json) { - console.log(JSON.stringify(entries, null, 2)); - return; - } - printWorkspaceActivity(entries); -} diff --git a/src/cli/omv.ts b/src/cli/omv.ts index a1ce03c..e48faf0 100644 --- a/src/cli/omv.ts +++ b/src/cli/omv.ts @@ -41,6 +41,7 @@ import { type FindingDeleteResult, } from "./findings.js"; import { initWorkspace, readWorkspaceActivity, workspaceStatus, type WorkspaceActivityEntry, type WorkspaceStatus } from "./workspace.js"; +import { usage, commandUsage, workspaceUsage, radarUsage, requestUsage, submissionsUsage, configUsage, findingsUsage } from "./usage.js"; import { command as cmd, empty, @@ -63,287 +64,6 @@ import { const args = process.argv.slice(2); const command = args[0]; -function usage(): void { - console.log(`oh-my-vul — vulnerability research skills for Claude Code - -Usage: - omv setup [--scope user|project] [--force] [--dry-run] - Install skills to ~/.claude/skills/ or ./.claude/skills/ - omv uninstall [--scope user|project] [--json] - Remove installed skills and manifest - omv doctor [--scope user|project] [--json] [--strict] - Check installation health - omv dashboard [--json] Show workspace, queue, and recent activity - omv workspace init [--json] Initialize local .omv workspace - omv workspace status [--json] Show local .omv workspace status - omv workspace log [--json] Show local workspace activity log - omv findings list [--json] List .omv/findings evidence files - omv findings workflow [--json] Show active finding lifecycle next actions - omv findings show [--archived] [--json] - Show one finding's details and next action - omv findings open [--archived] [--json] - Print a finding YAML path for editing - omv findings init [--status candidate|confirmed|blocked] [--force] [--json] - Create an Evidence.v1 finding template - omv findings validate [id|path] [--json] [--strict] - Validate one finding or the whole ledger - omv findings promote --status candidate|confirmed|blocked [--json] - Update a finding status and revalidate it - omv findings archive --reason [--force] [--strict] [--json] - Move a finding out of the active queue - omv findings archive list [--json] - List archived findings - omv findings restore [--force] [--json] - Restore an archived finding - omv radar refresh [--dry-run] [--json] - Refresh passive watchlist intelligence - omv radar brief [--json] Summarize local radar events - omv request preflight [--refresh] [--json] - Check metadata source request health - omv request fetch [--accept mime] [--refresh] [--json] - Fetch one public URL through the request broker - omv dedup [--confirm] [--existing-cve CVE|none] [--notes text] [--json] - Plan or write Evidence.v1 dedup fields - omv disclose timeline [--days N] [--json] - Show disclosure timeline milestones - omv submissions record --platform --submission-id --url [--json] - Record platform submission metadata - omv submissions track [--json] - Show submission status for one finding - omv submissions close --cve CVE-YYYY-NNNN [--json] - Close submission records with a CVE id - omv config [get |set |unset |list] - Manage persistent config (scope, etc.) - omv version [--json] Show package and registry version - omv help Show this message - -Examples: - npx oh-my-vul setup - npx oh-my-vul setup --scope project - npx oh-my-vul setup --force - omv doctor - omv doctor --json - omv dashboard - omv findings list - omv findings init demo - omv findings validate - omv findings promote demo --status confirmed - omv findings workflow - omv findings show demo - omv findings archive demo --reason reported - omv radar refresh --dry-run - omv request preflight - omv request fetch https://registry.npmjs.org/markdown-it --json - omv submissions track demo - omv uninstall --scope user - omv config set scope user - omv config list -`); -} - -function commandUsage(topic: string | undefined): void { - switch (topic) { - case "setup": - console.log(`Usage: omv setup [--scope user|project] [--force] [--dry-run] [--json] - -Install all registry-marked skills and write an install manifest.`); - return; - case "uninstall": - console.log(`Usage: omv uninstall [--scope user|project] [--json] - -Remove installed skills, install manifest, and setup-scope.json (project scope only). -User data under .omv/ (findings, reports, repro, notes, submissions) is preserved.`); - return; - case "doctor": - console.log(`Usage: omv doctor [--scope user|project] [--json] [--strict] - -Check installed skills, runtime assets, references, scripts, and install manifest. ---strict exits non-zero when warnings are present.`); - return; - case "version": - console.log(`Usage: omv version [--json] - -Show package version, registry version, platform, and registry update date.`); - return; - case "dashboard": - console.log(`Usage: omv dashboard [--json] - -Show local workspace status, active workflow queue, and recent activity in one view.`); - return; - case "workspace": - workspaceUsage(args[1]); - return; - case "findings": - findingsUsage(args[1]); - return; - case "radar": - radarUsage(args[1]); - return; - case "request": - requestUsage(args[1]); - return; - case "dedup": - console.log("Usage: omv dedup [--confirm] [--existing-cve CVE|none] [--notes text] [--json]"); - return; - case "disclose": - console.log("Usage: omv disclose timeline [--days N] [--json]"); - return; - case "submissions": - submissionsUsage(args[1]); - return; - case "config": - configUsage(args[1]); - return; - default: - usage(); - return; - } -} - -function workspaceUsage(subcommand: string | undefined): void { - switch (subcommand) { - case "init": - console.log("Usage: omv workspace init [--gitignore] [--json]"); - return; - case "status": - console.log("Usage: omv workspace status [--json]"); - return; - case "log": - console.log("Usage: omv workspace log [--json]"); - return; - default: - console.log(`Usage: - omv workspace init [--gitignore] [--json] - omv workspace status [--json] - omv workspace log [--json]`); - return; - } -} - -function radarUsage(subcommand: string | undefined): void { - switch (subcommand) { - case "refresh": - console.log("Usage: omv radar refresh [--dry-run] [--json]"); - return; - case "brief": - console.log("Usage: omv radar brief [--json]"); - return; - default: - console.log(`Usage: - omv radar refresh [--dry-run] [--json] - omv radar brief [--json]`); - return; - } -} - -function requestUsage(subcommand: string | undefined): void { - switch (subcommand) { - case "preflight": - console.log("Usage: omv request preflight [--refresh] [--json]"); - return; - case "fetch": - console.log("Usage: omv request fetch [--accept mime] [--refresh] [--json]"); - return; - default: - console.log(`Usage: - omv request preflight [--refresh] [--json] - omv request fetch [--accept mime] [--refresh] [--json]`); - return; - } -} - -function submissionsUsage(subcommand: string | undefined): void { - switch (subcommand) { - case "record": - console.log("Usage: omv submissions record --platform --submission-id --url [--json]"); - return; - case "track": - console.log("Usage: omv submissions track [--json]"); - return; - case "close": - console.log("Usage: omv submissions close --cve CVE-YYYY-NNNN [--json]"); - return; - default: - console.log(`Usage: - omv submissions record --platform --submission-id --url [--json] - omv submissions track [--json] - omv submissions close --cve CVE-YYYY-NNNN [--json]`); - return; - } -} - -function configUsage(subcommand: string | undefined): void { - switch (subcommand) { - case "get": - console.log("Usage: omv config get "); - return; - case "set": - console.log("Usage: omv config set "); - return; - case "unset": - console.log("Usage: omv config unset "); - return; - case "list": - console.log("Usage: omv config list"); - return; - default: - console.log(`Usage: - omv config get - omv config set - omv config unset - omv config list`); - return; - } -} - -function findingsUsage(subcommand: string | undefined): void { - switch (subcommand) { - case "list": - console.log("Usage: omv findings list [--json]"); - return; - case "workflow": - console.log("Usage: omv findings workflow [--json]"); - return; - case "show": - console.log("Usage: omv findings show [--archived] [--json]"); - return; - case "open": - console.log("Usage: omv findings open [--archived] [--json]"); - return; - case "init": - console.log("Usage: omv findings init [--status candidate|confirmed|blocked] [--force] [--json]"); - return; - case "validate": - console.log(`Usage: omv findings validate [id|path] [--json] [--strict] - -Validate Evidence.v1 files. --strict treats warnings as failures.`); - return; - case "promote": - console.log("Usage: omv findings promote --status candidate|confirmed|blocked [--json]"); - return; - case "archive": - console.log(`Usage: - omv findings archive --reason [--force] [--strict] [--json] - omv findings archive list [--json]`); - return; - case "restore": - console.log("Usage: omv findings restore [--force] [--json]"); - return; - default: - console.log(`Usage: - omv findings list [--json] - omv findings workflow [--json] - omv findings show [--archived] [--json] - omv findings open [--archived] [--json] - omv findings init [--status candidate|confirmed|blocked] [--force] [--json] - omv findings validate [id|path] [--json] [--strict] - omv findings promote --status candidate|confirmed|blocked [--json] - omv findings archive --reason [--force] [--strict] [--json] - omv findings archive list [--json] - omv findings restore [--force] [--json]`); - return; - } -} function wantsHelp(): boolean { return args.includes("--help") || args.includes("-h") || command === "help"; @@ -628,7 +348,7 @@ async function runDisclose(): Promise { const json = args.includes("--json"); if (subcommand !== "timeline") { console.error(`Unknown disclose command: ${subcommand}\n`); - commandUsage("disclose"); + commandUsage(args, command, "disclose", args[1]); process.exit(1); } const id = firstPositionalAfter("timeline"); @@ -1615,7 +1335,9 @@ if (!validation.ok) { } if (wantsHelp()) { - commandUsage(command === "help" ? args[1] : command); + const topic = command === "help" ? args[1] : command; + const subcommand = command === "help" ? args[2] : args[1]; + commandUsage(args, command, topic, subcommand); process.exit(0); } diff --git a/src/cli/usage.ts b/src/cli/usage.ts index 39f561c..46b17e8 100644 --- a/src/cli/usage.ts +++ b/src/cli/usage.ts @@ -4,13 +4,11 @@ export function usage(): void { Usage: omv setup [--scope user|project] [--force] [--dry-run] Install skills to ~/.claude/skills/ or ./.claude/skills/ + omv uninstall [--scope user|project] [--json] + Remove installed skills and manifest omv doctor [--scope user|project] [--json] [--strict] Check installation health omv dashboard [--json] Show workspace, queue, and recent activity - omv repro init [--force] [--json] - Create standard local reproduction artifacts - omv report artifacts [--json] - Check report and reproduction artifacts omv workspace init [--json] Initialize local .omv workspace omv workspace status [--json] Show local .omv workspace status omv workspace log [--json] Show local workspace activity log @@ -33,6 +31,25 @@ Usage: List archived findings omv findings restore [--force] [--json] Restore an archived finding + omv radar refresh [--dry-run] [--json] + Refresh passive watchlist intelligence + omv radar brief [--json] Summarize local radar events + omv request preflight [--refresh] [--json] + Check metadata source request health + omv request fetch [--accept mime] [--refresh] [--json] + Fetch one public URL through the request broker + omv dedup [--confirm] [--existing-cve CVE|none] [--notes text] [--json] + Plan or write Evidence.v1 dedup fields + omv disclose timeline [--days N] [--json] + Show disclosure timeline milestones + omv submissions record --platform --submission-id --url [--json] + Record platform submission metadata + omv submissions track [--json] + Show submission status for one finding + omv submissions close --cve CVE-YYYY-NNNN [--json] + Close submission records with a CVE id + omv config [get |set |unset |list] + Manage persistent config (scope, etc.) omv version [--json] Show package and registry version omv help Show this message @@ -43,9 +60,6 @@ Examples: omv doctor omv doctor --json omv dashboard - omv repro init demo - omv findings doctor demo - omv report artifacts demo omv findings list omv findings init demo omv findings validate @@ -53,16 +67,29 @@ Examples: omv findings workflow omv findings show demo omv findings archive demo --reason reported + omv radar refresh --dry-run + omv request preflight + omv request fetch https://registry.npmjs.org/markdown-it --json + omv submissions track demo + omv uninstall --scope user + omv config set scope user + omv config list `); } -export function commandUsage(topic: string | undefined, subcommand: string | undefined): void { +export function commandUsage(args: string[], command: string | undefined, topic: string | undefined, subcommand: string | undefined): void { switch (topic) { case "setup": console.log(`Usage: omv setup [--scope user|project] [--force] [--dry-run] [--json] Install all registry-marked skills and write an install manifest.`); return; + case "uninstall": + console.log(`Usage: omv uninstall [--scope user|project] [--json] + +Remove installed skills, install manifest, and setup-scope.json (project scope only). +User data under .omv/ (findings, reports, repro, notes, submissions) is preserved.`); + return; case "doctor": console.log(`Usage: omv doctor [--scope user|project] [--json] [--strict] @@ -79,66 +106,128 @@ Show package version, registry version, platform, and registry update date.`); Show local workspace status, active workflow queue, and recent activity in one view.`); return; - case "repro": - reproUsage(subcommand); - return; - case "report": - reportUsage(subcommand); - return; case "workspace": workspaceUsage(subcommand); return; case "findings": findingsUsage(subcommand); return; + case "radar": + radarUsage(subcommand); + return; + case "request": + requestUsage(subcommand); + return; + case "dedup": + console.log("Usage: omv dedup [--confirm] [--existing-cve CVE|none] [--notes text] [--json]"); + return; + case "disclose": + console.log("Usage: omv disclose timeline [--days N] [--json]"); + return; + case "submissions": + submissionsUsage(subcommand); + return; + case "config": + configUsage(subcommand); + return; default: usage(); return; } } -export function reproUsage(subcommand: string | undefined): void { +export function workspaceUsage(subcommand: string | undefined): void { switch (subcommand) { case "init": - console.log(`Usage: omv repro init [--force] [--json] + console.log("Usage: omv workspace init [--gitignore] [--json]"); + return; + case "status": + console.log("Usage: omv workspace status [--json]"); + return; + case "log": + console.log("Usage: omv workspace log [--json]"); + return; + default: + console.log(`Usage: + omv workspace init [--gitignore] [--json] + omv workspace status [--json] + omv workspace log [--json]`); + return; + } +} -Create .omv/repro// with README.md, commands.sh, observed.txt, docker-compose.yml, and screenshots/.`); +export function radarUsage(subcommand: string | undefined): void { + switch (subcommand) { + case "refresh": + console.log("Usage: omv radar refresh [--dry-run] [--json]"); + return; + case "brief": + console.log("Usage: omv radar brief [--json]"); return; default: - console.log("Usage: omv repro init [--force] [--json]"); + console.log(`Usage: + omv radar refresh [--dry-run] [--json] + omv radar brief [--json]`); return; } } -export function reportUsage(subcommand: string | undefined): void { +export function requestUsage(subcommand: string | undefined): void { switch (subcommand) { - case "artifacts": - console.log(`Usage: omv report artifacts [--json] + case "preflight": + console.log("Usage: omv request preflight [--refresh] [--json]"); + return; + case "fetch": + console.log("Usage: omv request fetch [--accept mime] [--refresh] [--json]"); + return; + default: + console.log(`Usage: + omv request preflight [--refresh] [--json] + omv request fetch [--accept mime] [--refresh] [--json]`); + return; + } +} -Check .omv/reports// and Evidence.v1 reproduction artifact references.`); +export function submissionsUsage(subcommand: string | undefined): void { + switch (subcommand) { + case "record": + console.log("Usage: omv submissions record --platform --submission-id --url [--json]"); + return; + case "track": + console.log("Usage: omv submissions track [--json]"); + return; + case "close": + console.log("Usage: omv submissions close --cve CVE-YYYY-NNNN [--json]"); return; default: - console.log("Usage: omv report artifacts [--json]"); + console.log(`Usage: + omv submissions record --platform --submission-id --url [--json] + omv submissions track [--json] + omv submissions close --cve CVE-YYYY-NNNN [--json]`); return; } } -export function workspaceUsage(subcommand: string | undefined): void { +export function configUsage(subcommand: string | undefined): void { switch (subcommand) { - case "init": - console.log("Usage: omv workspace init [--json]"); + case "get": + console.log("Usage: omv config get "); return; - case "status": - console.log("Usage: omv workspace status [--json]"); + case "set": + console.log("Usage: omv config set "); return; - case "log": - console.log("Usage: omv workspace log [--json]"); + case "unset": + console.log("Usage: omv config unset "); + return; + case "list": + console.log("Usage: omv config list"); return; default: console.log(`Usage: - omv workspace init [--json] - omv workspace status [--json] - omv workspace log [--json]`); + omv config get + omv config set + omv config unset + omv config list`); return; } }