cyntrisec 0.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cyntrisec/__init__.py +3 -0
- cyntrisec/__main__.py +6 -0
- cyntrisec/aws/__init__.py +6 -0
- cyntrisec/aws/collectors/__init__.py +17 -0
- cyntrisec/aws/collectors/ec2.py +30 -0
- cyntrisec/aws/collectors/iam.py +116 -0
- cyntrisec/aws/collectors/lambda_.py +45 -0
- cyntrisec/aws/collectors/network.py +70 -0
- cyntrisec/aws/collectors/rds.py +38 -0
- cyntrisec/aws/collectors/s3.py +68 -0
- cyntrisec/aws/collectors/usage.py +188 -0
- cyntrisec/aws/credentials.py +153 -0
- cyntrisec/aws/normalizers/__init__.py +17 -0
- cyntrisec/aws/normalizers/ec2.py +115 -0
- cyntrisec/aws/normalizers/iam.py +182 -0
- cyntrisec/aws/normalizers/lambda_.py +83 -0
- cyntrisec/aws/normalizers/network.py +225 -0
- cyntrisec/aws/normalizers/rds.py +130 -0
- cyntrisec/aws/normalizers/s3.py +184 -0
- cyntrisec/aws/relationship_builder.py +1359 -0
- cyntrisec/aws/scanner.py +303 -0
- cyntrisec/cli/__init__.py +5 -0
- cyntrisec/cli/analyze.py +747 -0
- cyntrisec/cli/ask.py +412 -0
- cyntrisec/cli/can.py +307 -0
- cyntrisec/cli/comply.py +226 -0
- cyntrisec/cli/cuts.py +231 -0
- cyntrisec/cli/diff.py +332 -0
- cyntrisec/cli/errors.py +105 -0
- cyntrisec/cli/explain.py +348 -0
- cyntrisec/cli/main.py +114 -0
- cyntrisec/cli/manifest.py +893 -0
- cyntrisec/cli/output.py +117 -0
- cyntrisec/cli/remediate.py +643 -0
- cyntrisec/cli/report.py +462 -0
- cyntrisec/cli/scan.py +207 -0
- cyntrisec/cli/schemas.py +391 -0
- cyntrisec/cli/serve.py +164 -0
- cyntrisec/cli/setup.py +260 -0
- cyntrisec/cli/validate.py +101 -0
- cyntrisec/cli/waste.py +323 -0
- cyntrisec/core/__init__.py +31 -0
- cyntrisec/core/business_config.py +110 -0
- cyntrisec/core/business_logic.py +131 -0
- cyntrisec/core/compliance.py +437 -0
- cyntrisec/core/cost_estimator.py +301 -0
- cyntrisec/core/cuts.py +360 -0
- cyntrisec/core/diff.py +361 -0
- cyntrisec/core/graph.py +202 -0
- cyntrisec/core/paths.py +830 -0
- cyntrisec/core/schema.py +317 -0
- cyntrisec/core/simulator.py +371 -0
- cyntrisec/core/waste.py +309 -0
- cyntrisec/mcp/__init__.py +5 -0
- cyntrisec/mcp/server.py +862 -0
- cyntrisec/storage/__init__.py +7 -0
- cyntrisec/storage/filesystem.py +344 -0
- cyntrisec/storage/memory.py +113 -0
- cyntrisec/storage/protocol.py +92 -0
- cyntrisec-0.1.7.dist-info/METADATA +672 -0
- cyntrisec-0.1.7.dist-info/RECORD +65 -0
- cyntrisec-0.1.7.dist-info/WHEEL +4 -0
- cyntrisec-0.1.7.dist-info/entry_points.txt +2 -0
- cyntrisec-0.1.7.dist-info/licenses/LICENSE +190 -0
- cyntrisec-0.1.7.dist-info/licenses/NOTICE +5 -0
cyntrisec/cli/output.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared output utilities for Cyntrisec CLI commands.
|
|
3
|
+
|
|
4
|
+
Provides:
|
|
5
|
+
- Format detection that defaults to JSON when stdout is not a TTY
|
|
6
|
+
- Agent format envelope with suggested actions
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from collections.abc import Iterable, Sequence
|
|
14
|
+
|
|
15
|
+
import typer
|
|
16
|
+
from pydantic import BaseModel
|
|
17
|
+
|
|
18
|
+
from cyntrisec.cli.schemas import (
|
|
19
|
+
ActionModel,
|
|
20
|
+
AgentEnvelope,
|
|
21
|
+
ArtifactPathsModel,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
Action = dict[str, str]
|
|
25
|
+
SCHEMA_VERSION = "1.0"
|
|
26
|
+
|
|
27
|
+
# Error taxonomy (keep small and stable)
|
|
28
|
+
AWS_ACCESS_DENIED = "AWS_ACCESS_DENIED"
|
|
29
|
+
AWS_THROTTLED = "AWS_THROTTLED"
|
|
30
|
+
AWS_REGION_DISABLED = "AWS_REGION_DISABLED"
|
|
31
|
+
SNAPSHOT_NOT_FOUND = "SNAPSHOT_NOT_FOUND"
|
|
32
|
+
SCHEMA_MISMATCH = "SCHEMA_MISMATCH"
|
|
33
|
+
INVALID_QUERY = "INVALID_QUERY"
|
|
34
|
+
INTERNAL_ERROR = "INTERNAL_ERROR"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def resolve_format(
|
|
38
|
+
format_option: str | None,
|
|
39
|
+
*,
|
|
40
|
+
default_tty: str,
|
|
41
|
+
allowed: Sequence[str],
|
|
42
|
+
) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Choose an output format, defaulting to JSON when stdout is piped.
|
|
45
|
+
"""
|
|
46
|
+
chosen = format_option or ("json" if not sys.stdout.isatty() else default_tty)
|
|
47
|
+
|
|
48
|
+
if chosen not in allowed:
|
|
49
|
+
raise typer.BadParameter(f"Invalid format '{chosen}'. Choose from {', '.join(allowed)}")
|
|
50
|
+
|
|
51
|
+
return chosen
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def suggested_actions(actions: Iterable[tuple[str, str]]) -> list[Action]:
|
|
55
|
+
"""Normalize suggested actions to a list of dicts."""
|
|
56
|
+
return [
|
|
57
|
+
{"command": command, "reason": reason} for command, reason in actions if command and reason
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def emit_agent_or_json(
|
|
62
|
+
format: str,
|
|
63
|
+
data: dict[str, object],
|
|
64
|
+
*,
|
|
65
|
+
suggested: Sequence[Action] | None = None,
|
|
66
|
+
status: str = "success",
|
|
67
|
+
artifact_paths: dict[str, str] | None = None,
|
|
68
|
+
error_code: str | None = None,
|
|
69
|
+
message: str | None = None,
|
|
70
|
+
schema: type[BaseModel] | None = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Emit JSON or agent-formatted output to stdout.
|
|
74
|
+
"""
|
|
75
|
+
validated_data: object = data
|
|
76
|
+
if schema:
|
|
77
|
+
validated_data = schema.model_validate(data).model_dump(mode="json")
|
|
78
|
+
|
|
79
|
+
actions: list[ActionModel] | None = (
|
|
80
|
+
[ActionModel.model_validate(a) for a in suggested] if suggested else None
|
|
81
|
+
)
|
|
82
|
+
artifacts_model: ArtifactPathsModel | None = (
|
|
83
|
+
ArtifactPathsModel.model_validate(artifact_paths) if artifact_paths else None
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
envelope = AgentEnvelope(
|
|
87
|
+
schema_version=SCHEMA_VERSION,
|
|
88
|
+
status=status,
|
|
89
|
+
message=message,
|
|
90
|
+
error_code=error_code,
|
|
91
|
+
data=validated_data,
|
|
92
|
+
artifact_paths=artifacts_model,
|
|
93
|
+
suggested_actions=actions,
|
|
94
|
+
)
|
|
95
|
+
typer.echo(json.dumps(envelope.model_dump(mode="json"), indent=2, default=str))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def build_artifact_paths(storage, snapshot_id: str | None) -> dict[str, str] | None:
|
|
99
|
+
"""Return key artifact paths for a snapshot, if available."""
|
|
100
|
+
try:
|
|
101
|
+
# Resolve the identifier to a scan_id first
|
|
102
|
+
resolved_id = storage.resolve_scan_id(snapshot_id)
|
|
103
|
+
if not resolved_id:
|
|
104
|
+
return None
|
|
105
|
+
scan_dir = storage.get_scan_path(resolved_id)
|
|
106
|
+
except Exception:
|
|
107
|
+
return None
|
|
108
|
+
if not scan_dir:
|
|
109
|
+
return None
|
|
110
|
+
return {
|
|
111
|
+
"snapshot_dir": str(scan_dir),
|
|
112
|
+
"snapshot": str(scan_dir / "snapshot.json"),
|
|
113
|
+
"assets": str(scan_dir / "assets.json"),
|
|
114
|
+
"relationships": str(scan_dir / "relationships.json"),
|
|
115
|
+
"attack_paths": str(scan_dir / "attack_paths.json"),
|
|
116
|
+
"findings": str(scan_dir / "findings.json"),
|
|
117
|
+
}
|