Skip to content

Commit 0c5aff9

Browse files
authored
Add asset.enrich in task command and implement dryrun (#8)
Add asset.enrich in task command and implement dryrun
2 parents b649564 + 5df40d6 commit 0c5aff9

3 files changed

Lines changed: 58 additions & 8 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,15 @@ Notes:
111111
- Each activity must have **exactly one step** in the YAML.
112112
- 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.
113113
- 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.
114-
- Supported actions: `assets.create`, `assets.update`, `vulns.create`.
114+
- Supported actions: `assets.create`, `assets.update`, `assets.enrich`, `vulns.create`.
115+
- `assets.enrich` only updates existing assets (requires `id`/`assetId` or `name`); it does not create new assets.
115116
- Asset fields allowed in `assets.create` and `asset.map` (for `create_if_missing`): `name`, `description`, `businessImpact`, `dataClassification`, `assetsTagList`, `integrations`, `environmentCompromised`, `exploitability`.
116117
- Asset fields allowed in `assets.update`: `id` or `assetId` plus the same fields as `assets.create`.
117118
- YAML examples: `samples/task-nmap-nuclei.yaml`, `samples/task-nuclei.yaml`, `samples/task-assets-create.yaml`, `samples/task-assets-update.yaml`
118119
- `scan-json-lines` example: `samples/task-naabu.yaml`
119120
- Subdomains -> resolve -> ports pipeline: `samples/task-subfinder-dnsx-naabu.yaml`
120121
- Execution:
121-
- Dry-run with confirmation: `python -m conviso.app tasks run --company-id 443 --project-id 26102`
122+
- Dry-run only (default): `python -m conviso.app tasks run --company-id 443 --project-id 26102`
122123
- Apply directly: `python -m conviso.app tasks run --company-id 443 --project-id 26102 --apply`
123124
- Pentest guide: `docs/pentest-tasks-guide.md`
124125

docs/pentest-tasks-guide.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ This guide explains how to define requirements with YAML tasks and run them from
77
2. Run `tasks run` from the CLI for the project.
88
3. The CLI executes the commands, parses output, and creates assets/vulnerabilities.
99

10+
## Execution
11+
- Dry-run only (default): `python -m conviso.app tasks run --company-id 443 --project-id 26102`
12+
- Apply directly: `python -m conviso.app tasks run --company-id 443 --project-id 26102 --apply`
13+
1014
## Minimal YAML Structure
1115
```yaml
1216
version: 1
@@ -39,8 +43,30 @@ Example files:
3943
## Supported Actions
4044
- `assets.create`
4145
- `assets.update`
46+
- `assets.enrich`
4247
- `vulns.create`
4348

49+
`assets.enrich` only updates existing assets (requires `id`/`assetId` or `name`); it does not create new assets.
50+
51+
Minimal example:
52+
```yaml
53+
version: 1
54+
name: "Enrich asset"
55+
steps:
56+
- id: enrich_asset
57+
name: "Enrich asset"
58+
run:
59+
cmd: "echo '{\"name\": \"example.com\", \"businessImpact\": \"HIGH\", \"dataClassification\": \"SENSITIVE\"}'"
60+
parse:
61+
format: "scan-json-lines"
62+
actions:
63+
- type: "assets.enrich"
64+
map:
65+
name: "${finding.name}"
66+
businessImpact: "${finding.businessImpact}"
67+
dataClassification: "${finding.dataClassification}"
68+
```
69+
4470
## Assets Fields
4571
Allowed fields for `assets.create` and `asset.map` (used by `create_if_missing`):
4672
- `name`, `description`, `businessImpact`, `dataClassification`, `assetsTagList`, `integrations`, `environmentCompromised`, `exploitability`

src/conviso/commands/tasks.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,32 @@ def _apply_actions(planned: List[Dict[str, Any]], company_id: int, apply: bool)
843843
updated_id = _update_asset(company_id, filtered, apply)
844844
if updated_id:
845845
counts["assets_updated"] += 1
846+
elif action_type == "assets.enrich":
847+
asset_id = payload.get("id") or payload.get("assetId")
848+
asset_name = payload.get("name")
849+
if asset_id in (None, ""):
850+
resolved = None
851+
if asset_name:
852+
assets_cache = (context.get("assets") or {}).get("by_name") or {}
853+
normalized_name = _normalize_asset_key(str(asset_name))
854+
if str(asset_name) in assets_cache:
855+
resolved = assets_cache.get(str(asset_name))
856+
elif normalized_name in assets_cache:
857+
resolved = assets_cache.get(normalized_name)
858+
else:
859+
resolved = _find_asset_by_name(company_id, str(asset_name))
860+
if not resolved:
861+
warning("Skipping assets.enrich: asset not found (use assets.create or provide assetId).")
862+
counts["skipped"] += 1
863+
continue
864+
asset_id = resolved
865+
if isinstance(asset_id, str) and asset_id.isdigit():
866+
asset_id = int(asset_id)
867+
payload["id"] = asset_id
868+
payload.pop("assetId", None)
869+
updated_id = _update_asset(company_id, payload, apply)
870+
if updated_id:
871+
counts["assets_updated"] += 1
846872
elif action_type == "vulns.create":
847873
# Normalize severity values from common scanners
848874
severity = payload.get("severity")
@@ -1072,7 +1098,7 @@ def create_task(
10721098
project_id: Optional[int] = typer.Option(None, "--project-id", "-p", help="Attach requirement to project."),
10731099
global_flag: bool = typer.Option(False, "--global", help="Mark new requirement as global."),
10741100
):
1075-
"""Create a TASK requirement with a YAML activity."""
1101+
"""Create a TASK requirement with a YAML activity (supports assets.create, assets.update, assets.enrich, vulns.create)."""
10761102
_require_yaml()
10771103

10781104
if bool(yaml_text) == bool(yaml_file):
@@ -1264,7 +1290,7 @@ def run_task(
12641290
company_id: int = typer.Option(..., "--company-id", "-c", help="Company/Scope ID."),
12651291
project_id: int = typer.Option(..., "--project-id", "-p", help="Project ID."),
12661292
requirement_prefix: str = typer.Option(TASK_PREFIX_DEFAULT, "--prefix", help="Requirement label prefix to match."),
1267-
apply: bool = typer.Option(False, "--apply", help="Apply actions without dry-run prompt."),
1293+
dryrun: bool = typer.Option(True, "--dryrun/--apply", help="Run in dry-run mode (default). Use --apply to apply actions."),
12681294
):
12691295
"""Execute tasks defined as YAML in activity descriptions."""
12701296
_require_yaml()
@@ -1448,10 +1474,7 @@ def run_task(
14481474

14491475
planned = _actions_from_parsed(actions, parsed_items, context)
14501476

1451-
do_apply = apply
1452-
if not apply:
1453-
confirm = typer.confirm("Apply actions now?", default=False)
1454-
do_apply = confirm
1477+
do_apply = not dryrun
14551478
counts = _apply_actions(planned, company_id, do_apply)
14561479
summary(
14571480
f"Vulnerabilities created={counts['created']} skipped={counts['skipped']} assets_created={counts['assets_created']} assets_updated={counts['assets_updated']}"

0 commit comments

Comments
 (0)