diff --git a/README.md b/README.md index 5c6fbc3..d4a970a 100644 --- a/README.md +++ b/README.md @@ -111,14 +111,15 @@ Notes: - Each activity must have **exactly one step** in the YAML. - In `vulns.create`, `assetId` is required. If defined in YAML, it takes precedence. Otherwise it is resolved via `inputs.assets`. If it cannot be resolved, the command fails. - To auto-create assets in `vulns.create`, use `asset.create_if_missing: true` and set `asset.map.name` to a field from the tool output. -- Supported actions: `assets.create`, `assets.update`, `vulns.create`. +- Supported actions: `assets.create`, `assets.update`, `assets.enrich`, `vulns.create`. +- `assets.enrich` only updates existing assets (requires `id`/`assetId` or `name`); it does not create new assets. - Asset fields allowed in `assets.create` and `asset.map` (for `create_if_missing`): `name`, `description`, `businessImpact`, `dataClassification`, `assetsTagList`, `integrations`, `environmentCompromised`, `exploitability`. - Asset fields allowed in `assets.update`: `id` or `assetId` plus the same fields as `assets.create`. - YAML examples: `samples/task-nmap-nuclei.yaml`, `samples/task-nuclei.yaml`, `samples/task-assets-create.yaml`, `samples/task-assets-update.yaml` - `scan-json-lines` example: `samples/task-naabu.yaml` - Subdomains -> resolve -> ports pipeline: `samples/task-subfinder-dnsx-naabu.yaml` - Execution: - - Dry-run with confirmation: `python -m conviso.app tasks run --company-id 443 --project-id 26102` + - Dry-run only (default): `python -m conviso.app tasks run --company-id 443 --project-id 26102` - Apply directly: `python -m conviso.app tasks run --company-id 443 --project-id 26102 --apply` - Pentest guide: `docs/pentest-tasks-guide.md` diff --git a/docs/pentest-tasks-guide.md b/docs/pentest-tasks-guide.md index 2716361..4f768ef 100644 --- a/docs/pentest-tasks-guide.md +++ b/docs/pentest-tasks-guide.md @@ -7,6 +7,10 @@ This guide explains how to define requirements with YAML tasks and run them from 2. Run `tasks run` from the CLI for the project. 3. The CLI executes the commands, parses output, and creates assets/vulnerabilities. +## Execution +- Dry-run only (default): `python -m conviso.app tasks run --company-id 443 --project-id 26102` +- Apply directly: `python -m conviso.app tasks run --company-id 443 --project-id 26102 --apply` + ## Minimal YAML Structure ```yaml version: 1 @@ -39,8 +43,30 @@ Example files: ## Supported Actions - `assets.create` - `assets.update` +- `assets.enrich` - `vulns.create` +`assets.enrich` only updates existing assets (requires `id`/`assetId` or `name`); it does not create new assets. + +Minimal example: +```yaml +version: 1 +name: "Enrich asset" +steps: + - id: enrich_asset + name: "Enrich asset" + run: + cmd: "echo '{\"name\": \"example.com\", \"businessImpact\": \"HIGH\", \"dataClassification\": \"SENSITIVE\"}'" + parse: + format: "scan-json-lines" + actions: + - type: "assets.enrich" + map: + name: "${finding.name}" + businessImpact: "${finding.businessImpact}" + dataClassification: "${finding.dataClassification}" +``` + ## Assets Fields Allowed fields for `assets.create` and `asset.map` (used by `create_if_missing`): - `name`, `description`, `businessImpact`, `dataClassification`, `assetsTagList`, `integrations`, `environmentCompromised`, `exploitability` diff --git a/src/conviso/commands/tasks.py b/src/conviso/commands/tasks.py index fa155a3..8fdb680 100644 --- a/src/conviso/commands/tasks.py +++ b/src/conviso/commands/tasks.py @@ -843,6 +843,32 @@ def _apply_actions(planned: List[Dict[str, Any]], company_id: int, apply: bool) updated_id = _update_asset(company_id, filtered, apply) if updated_id: counts["assets_updated"] += 1 + elif action_type == "assets.enrich": + asset_id = payload.get("id") or payload.get("assetId") + asset_name = payload.get("name") + if asset_id in (None, ""): + resolved = None + if asset_name: + assets_cache = (context.get("assets") or {}).get("by_name") or {} + normalized_name = _normalize_asset_key(str(asset_name)) + if str(asset_name) in assets_cache: + resolved = assets_cache.get(str(asset_name)) + elif normalized_name in assets_cache: + resolved = assets_cache.get(normalized_name) + else: + resolved = _find_asset_by_name(company_id, str(asset_name)) + if not resolved: + warning("Skipping assets.enrich: asset not found (use assets.create or provide assetId).") + counts["skipped"] += 1 + continue + asset_id = resolved + if isinstance(asset_id, str) and asset_id.isdigit(): + asset_id = int(asset_id) + payload["id"] = asset_id + payload.pop("assetId", None) + updated_id = _update_asset(company_id, payload, apply) + if updated_id: + counts["assets_updated"] += 1 elif action_type == "vulns.create": # Normalize severity values from common scanners severity = payload.get("severity") @@ -1072,7 +1098,7 @@ def create_task( project_id: Optional[int] = typer.Option(None, "--project-id", "-p", help="Attach requirement to project."), global_flag: bool = typer.Option(False, "--global", help="Mark new requirement as global."), ): - """Create a TASK requirement with a YAML activity.""" + """Create a TASK requirement with a YAML activity (supports assets.create, assets.update, assets.enrich, vulns.create).""" _require_yaml() if bool(yaml_text) == bool(yaml_file): @@ -1264,7 +1290,7 @@ def run_task( company_id: int = typer.Option(..., "--company-id", "-c", help="Company/Scope ID."), project_id: int = typer.Option(..., "--project-id", "-p", help="Project ID."), requirement_prefix: str = typer.Option(TASK_PREFIX_DEFAULT, "--prefix", help="Requirement label prefix to match."), - apply: bool = typer.Option(False, "--apply", help="Apply actions without dry-run prompt."), + dryrun: bool = typer.Option(True, "--dryrun/--apply", help="Run in dry-run mode (default). Use --apply to apply actions."), ): """Execute tasks defined as YAML in activity descriptions.""" _require_yaml() @@ -1448,10 +1474,7 @@ def run_task( planned = _actions_from_parsed(actions, parsed_items, context) - do_apply = apply - if not apply: - confirm = typer.confirm("Apply actions now?", default=False) - do_apply = confirm + do_apply = not dryrun counts = _apply_actions(planned, company_id, do_apply) summary( f"Vulnerabilities created={counts['created']} skipped={counts['skipped']} assets_created={counts['assets_created']} assets_updated={counts['assets_updated']}"