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.
Files changed (65) hide show
  1. cyntrisec/__init__.py +3 -0
  2. cyntrisec/__main__.py +6 -0
  3. cyntrisec/aws/__init__.py +6 -0
  4. cyntrisec/aws/collectors/__init__.py +17 -0
  5. cyntrisec/aws/collectors/ec2.py +30 -0
  6. cyntrisec/aws/collectors/iam.py +116 -0
  7. cyntrisec/aws/collectors/lambda_.py +45 -0
  8. cyntrisec/aws/collectors/network.py +70 -0
  9. cyntrisec/aws/collectors/rds.py +38 -0
  10. cyntrisec/aws/collectors/s3.py +68 -0
  11. cyntrisec/aws/collectors/usage.py +188 -0
  12. cyntrisec/aws/credentials.py +153 -0
  13. cyntrisec/aws/normalizers/__init__.py +17 -0
  14. cyntrisec/aws/normalizers/ec2.py +115 -0
  15. cyntrisec/aws/normalizers/iam.py +182 -0
  16. cyntrisec/aws/normalizers/lambda_.py +83 -0
  17. cyntrisec/aws/normalizers/network.py +225 -0
  18. cyntrisec/aws/normalizers/rds.py +130 -0
  19. cyntrisec/aws/normalizers/s3.py +184 -0
  20. cyntrisec/aws/relationship_builder.py +1359 -0
  21. cyntrisec/aws/scanner.py +303 -0
  22. cyntrisec/cli/__init__.py +5 -0
  23. cyntrisec/cli/analyze.py +747 -0
  24. cyntrisec/cli/ask.py +412 -0
  25. cyntrisec/cli/can.py +307 -0
  26. cyntrisec/cli/comply.py +226 -0
  27. cyntrisec/cli/cuts.py +231 -0
  28. cyntrisec/cli/diff.py +332 -0
  29. cyntrisec/cli/errors.py +105 -0
  30. cyntrisec/cli/explain.py +348 -0
  31. cyntrisec/cli/main.py +114 -0
  32. cyntrisec/cli/manifest.py +893 -0
  33. cyntrisec/cli/output.py +117 -0
  34. cyntrisec/cli/remediate.py +643 -0
  35. cyntrisec/cli/report.py +462 -0
  36. cyntrisec/cli/scan.py +207 -0
  37. cyntrisec/cli/schemas.py +391 -0
  38. cyntrisec/cli/serve.py +164 -0
  39. cyntrisec/cli/setup.py +260 -0
  40. cyntrisec/cli/validate.py +101 -0
  41. cyntrisec/cli/waste.py +323 -0
  42. cyntrisec/core/__init__.py +31 -0
  43. cyntrisec/core/business_config.py +110 -0
  44. cyntrisec/core/business_logic.py +131 -0
  45. cyntrisec/core/compliance.py +437 -0
  46. cyntrisec/core/cost_estimator.py +301 -0
  47. cyntrisec/core/cuts.py +360 -0
  48. cyntrisec/core/diff.py +361 -0
  49. cyntrisec/core/graph.py +202 -0
  50. cyntrisec/core/paths.py +830 -0
  51. cyntrisec/core/schema.py +317 -0
  52. cyntrisec/core/simulator.py +371 -0
  53. cyntrisec/core/waste.py +309 -0
  54. cyntrisec/mcp/__init__.py +5 -0
  55. cyntrisec/mcp/server.py +862 -0
  56. cyntrisec/storage/__init__.py +7 -0
  57. cyntrisec/storage/filesystem.py +344 -0
  58. cyntrisec/storage/memory.py +113 -0
  59. cyntrisec/storage/protocol.py +92 -0
  60. cyntrisec-0.1.7.dist-info/METADATA +672 -0
  61. cyntrisec-0.1.7.dist-info/RECORD +65 -0
  62. cyntrisec-0.1.7.dist-info/WHEEL +4 -0
  63. cyntrisec-0.1.7.dist-info/entry_points.txt +2 -0
  64. cyntrisec-0.1.7.dist-info/licenses/LICENSE +190 -0
  65. cyntrisec-0.1.7.dist-info/licenses/NOTICE +5 -0
@@ -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
+ }