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
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,16 @@ conviso --help
## Usage (examples)
- Projects: `python -m conviso.app projects list --company-id 443 --all`
- Assets: `python -m conviso.app assets list --company-id 443 --tags cloud --attack-surface INTERNET_FACING --all`
- Requirements: `python -m conviso.app requirements create --company-id 443 --label "Req" --description "Desc" --activity "Login|Check login"`
- Requirements: `python -m conviso.app requirements create --company-id 443 --label "Req" --description "Desc" --activity "Login|Check login|REF-123"`
- Requirements (project): `python -m conviso.app requirements project --company-id 443 --project-id 26102`
- Requirements (activities): `python -m conviso.app requirements activities --company-id 443 --requirement-id 1503`
- Requirements (project activities): `python -m conviso.app requirements activities --company-id 443 --project-id 26102`
- Tasks (create from YAML): `python -m conviso.app tasks create --company-id 443 --project-id 26102 --label "Nuclei Scan" --yaml-file samples/task-nuclei.yaml`
- Tasks (append to requirement): `python -m conviso.app tasks create --company-id 443 --requirement-id 2174 --label "Nuclei Scan" --yaml-file samples/task-nuclei.yaml`
- Tasks (execute YAML from requirements): `python -m conviso.app tasks run --company-id 443 --project-id 26102`
- Tasks (list project): `python -m conviso.app tasks list --company-id 443 --project-id 26102`
- Tasks (only valid YAML): `python -m conviso.app tasks list --company-id 443 --project-id 26102 --only-valid`
- Tasks (create with inline YAML): `python -m conviso.app tasks create --company-id 443 --label "Quick Task" --yaml "name: quick\nsteps:\n - action: echo\n message: ok"`
- Vulnerabilities: `python -m conviso.app vulns list --company-id 443 --severities HIGH,CRITICAL --asset-tags cloud --all`

Output options: `--format table|json|csv`, `--output path` to save JSON/CSV.
Expand Down Expand Up @@ -186,13 +189,13 @@ Automatic normalizations:
| label | create/update | Text |
| description | create/update | Text |
| global | optional | true/false |
| activities | optional | Semicolon-separated; each activity uses `label|description|typeId|reference|item|category|actionPlan|templateId|sort` |
| activities | optional | Semicolon-separated; each activity uses `label|description|[typeId]|reference|item|category|actionPlan|templateId|sort` |

- Examples:
- Create:
```
label,description,global,activities
Requirement A,Do X,true,"Login|Check login|1|REF||Category||123|1;Logout|Check logout|1"
Requirement A,Do X,true,"Login|Check login|REF||Category||123|1;Logout|Check logout|1"
```
- Update/Delete:
```
Expand Down
2 changes: 1 addition & 1 deletion src/conviso/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.5
0.2.6
56 changes: 33 additions & 23 deletions src/conviso/commands/bulk.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,26 +306,36 @@ def _parse_activities(val: str):
warning(f"Ignoring activity (expected at least label|description): {raw}")
continue
act = {"label": parts[0], "description": parts[1]}
if len(parts) > 2 and parts[2]:
try:
act["typeId"] = int(parts[2])
except Exception:
warning(f"Ignoring invalid typeId in activity: {parts[2]}")
if len(parts) > 3 and parts[3]:
act["reference"] = parts[3]
if len(parts) > 4 and parts[4]:
act["item"] = parts[4]
if len(parts) > 5 and parts[5]:
act["category"] = parts[5]
if len(parts) > 6 and parts[6]:
act["actionPlan"] = parts[6]
if len(parts) > 7 and parts[7]:
try:
act["vulnerabilityTemplateId"] = int(parts[7])
except Exception:
warning(f"Ignoring invalid vulnerabilityTemplateId in activity: {parts[7]}")
if len(parts) > 8 and parts[8]:
act["sort"] = parts[8]

rest = parts[2:]
if rest:
first = rest[0]
if first:
try:
act["typeId"] = int(first)
rest = rest[1:]
except Exception:
act["reference"] = first
rest = rest[1:]
else:
rest = rest[1:]

field_order = ["reference", "item", "category", "actionPlan", "vulnerabilityTemplateId", "sort"]
start_index = 1 if "reference" in act else 0
for idx, value in enumerate(rest):
field_idx = idx + start_index
if field_idx >= len(field_order):
break
if not value:
continue
field = field_order[field_idx]
if field == "vulnerabilityTemplateId":
try:
act[field] = int(value)
except Exception:
warning(f"Ignoring invalid vulnerabilityTemplateId in activity: {value}")
else:
act[field] = value
activities.append(act)
return activities

Expand Down Expand Up @@ -1322,14 +1332,14 @@ def _show_requirement_template():
table.add_row(
"activities",
"optional",
"Semicolon-separated activities; each activity uses pipe-separated fields: label|description|typeId|reference|item|category|actionPlan|templateId|sort",
"Login|Check login|1|REF||Category||123|1;Logout|Check logout|1",
"Semicolon-separated activities; each activity uses pipe-separated fields: label|description|[typeId]|reference|item|category|actionPlan|templateId|sort",
"Login|Check login|REF||Category||123|1;Logout|Check logout|1",
)

console.print(table)
console.print("\nExample create CSV:\n")
console.print("label,description,global,activities")
console.print("Req A,Do X,true,\"Login|Check login|1|REF||Category||123|1\"\n")
console.print("Req A,Do X,true,\"Login|Check login|REF||Category||123|1\"\n")

console.print("Example update CSV:\n")
console.print("id,label,description")
Expand Down
130 changes: 77 additions & 53 deletions src/conviso/commands/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""
Requirements Command Module
---------------------------
Lists requirements (playbooks) so users can pick valid IDs for project associations.
Lists requirements so users can pick valid IDs for project associations.
"""

import typer
Expand All @@ -12,7 +12,7 @@
from conviso.core.output_manager import export_data
from conviso.schemas.requirements_schema import schema

app = typer.Typer(help="List requirements/playbooks available in a given scope.")
app = typer.Typer(help="List requirements available in a given scope.")


@app.command("list")
Expand All @@ -25,7 +25,7 @@ def list_requirements(
fmt: str = typer.Option("table", "--format", "-f", help="Output format: table, json, csv."),
output: Optional[str] = typer.Option(None, "--output", "-o", help="Output file for json/csv."),
):
"""List requirements (playbooks) for a scope."""
"""List requirements for a scope."""
info(f"Listing requirements for company {company_id} (page {page}, per_page {per_page})...")

query = """
Expand Down Expand Up @@ -99,7 +99,7 @@ def list_project_requirements(
fmt: str = typer.Option("table", "--format", "-f", help="Output format: table, json, csv."),
output: Optional[str] = typer.Option(None, "--output", "-o", help="Output file for json/csv."),
):
"""List requirements (playbooks) associated with a project."""
"""List requirements associated with a project."""
info(f"Listing requirements for project {project_id} in company {company_id}...")

query_with_activities = """
Expand Down Expand Up @@ -213,7 +213,7 @@ def list_requirement_activities(
fmt: str = typer.Option("table", "--format", "-f", help="Output format: table, json, csv."),
output: Optional[str] = typer.Option(None, "--output", "-o", help="Output file for json/csv."),
):
"""List activities (checks) inside a requirement."""
"""List activities inside a requirement."""
if not requirement_id and not project_id:
error("You must provide either --requirement-id or --project-id.")
raise typer.Exit(code=1)
Expand Down Expand Up @@ -333,10 +333,14 @@ def create_requirement(
None,
"--activity",
"-a",
help="Activity in format 'label|description|typeId|reference|item|category|actionPlan|templateId|sort'. Omit trailing fields if not needed.",
help=(
"Activity format: 'label|description|[typeId]|reference|item|category|actionPlan|templateId|sort'. "
"Required: label, description. If you don't have typeId, use 'label|description|reference' or "
"'label|description||reference'. Omit trailing fields if not needed."
),
),
):
"""Create a requirement (playbook)."""
"""Create a requirement."""
info(f"Creating requirement '{label}' for company {company_id}...")

mutation = """
Expand All @@ -360,31 +364,37 @@ def _parse_activities(raw_list: Optional[list[str]]):
if len(parts) < 2:
warning(f"Ignoring activity (expected at least label|description): {raw}")
continue
act = {
"label": parts[0],
"description": parts[1],
}
# Optional fields by position
if len(parts) > 2 and parts[2]:
try:
act["typeId"] = int(parts[2])
except ValueError:
warning(f"Ignoring invalid typeId in activity: {parts[2]}")
if len(parts) > 3 and parts[3]:
act["reference"] = parts[3]
if len(parts) > 4 and parts[4]:
act["item"] = parts[4]
if len(parts) > 5 and parts[5]:
act["category"] = parts[5]
if len(parts) > 6 and parts[6]:
act["actionPlan"] = parts[6]
if len(parts) > 7 and parts[7]:
try:
act["vulnerabilityTemplateId"] = int(parts[7])
except ValueError:
warning(f"Ignoring invalid vulnerabilityTemplateId in activity: {parts[7]}")
if len(parts) > 8 and parts[8]:
act["sort"] = parts[8]
act = {"label": parts[0], "description": parts[1]}

rest = parts[2:]
if rest:
first = rest[0]
if first:
try:
act["typeId"] = int(first)
rest = rest[1:]
except ValueError:
act["reference"] = first
rest = rest[1:]
else:
rest = rest[1:]

field_order = ["reference", "item", "category", "actionPlan", "vulnerabilityTemplateId", "sort"]
start_index = 1 if "reference" in act else 0
for idx, value in enumerate(rest):
field_idx = idx + start_index
if field_idx >= len(field_order):
break
if not value:
continue
field = field_order[field_idx]
if field == "vulnerabilityTemplateId":
try:
act[field] = int(value)
except ValueError:
warning(f"Ignoring invalid vulnerabilityTemplateId in activity: {value}")
else:
act[field] = value
parsed.append(act)
return parsed

Expand Down Expand Up @@ -421,7 +431,11 @@ def update_requirement(
None,
"--activity",
"-a",
help="Activity in format 'label|description|typeId|reference|item|category|actionPlan|templateId|sort'. Omit trailing fields if not needed.",
help=(
"Activity format: 'label|description|[typeId]|reference|item|category|actionPlan|templateId|sort'. "
"Required: label, description. If you don't have typeId, use 'label|description|reference' or "
"'label|description||reference'. Omit trailing fields if not needed."
),
),
):
"""Update an existing requirement."""
Expand Down Expand Up @@ -449,26 +463,36 @@ def _parse_activities(raw_list: Optional[list[str]]):
warning(f"Ignoring activity (expected at least label|description): {raw}")
continue
act = {"label": parts[0], "description": parts[1]}
if len(parts) > 2 and parts[2]:
try:
act["typeId"] = int(parts[2])
except ValueError:
warning(f"Ignoring invalid typeId in activity: {parts[2]}")
if len(parts) > 3 and parts[3]:
act["reference"] = parts[3]
if len(parts) > 4 and parts[4]:
act["item"] = parts[4]
if len(parts) > 5 and parts[5]:
act["category"] = parts[5]
if len(parts) > 6 and parts[6]:
act["actionPlan"] = parts[6]
if len(parts) > 7 and parts[7]:
try:
act["vulnerabilityTemplateId"] = int(parts[7])
except ValueError:
warning(f"Ignoring invalid vulnerabilityTemplateId in activity: {parts[7]}")
if len(parts) > 8 and parts[8]:
act["sort"] = parts[8]

rest = parts[2:]
if rest:
first = rest[0]
if first:
try:
act["typeId"] = int(first)
rest = rest[1:]
except ValueError:
act["reference"] = first
rest = rest[1:]
else:
rest = rest[1:]

field_order = ["reference", "item", "category", "actionPlan", "vulnerabilityTemplateId", "sort"]
start_index = 1 if "reference" in act else 0
for idx, value in enumerate(rest):
field_idx = idx + start_index
if field_idx >= len(field_order):
break
if not value:
continue
field = field_order[field_idx]
if field == "vulnerabilityTemplateId":
try:
act[field] = int(value)
except ValueError:
warning(f"Ignoring invalid vulnerabilityTemplateId in activity: {value}")
else:
act[field] = value
parsed.append(act)
return parsed

Expand Down
Loading