Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
26 changes: 26 additions & 0 deletions docs/pentest-tasks-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down
35 changes: 29 additions & 6 deletions src/conviso/commands/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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']}"
Expand Down