agentdiscover 2.9.2__tar.gz → 2.9.4__tar.gz

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 (151) hide show
  1. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/PKG-INFO +1 -1
  2. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/sample-repo/agents/crewai_agent.py +4 -1
  3. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/sample-repo/agents/langchain_agent.py +3 -1
  4. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/pyproject.toml +1 -1
  5. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/aibom.py +46 -0
  6. agentdiscover-2.9.4/src/agent_discover_scanner/audit_reports.py +170 -0
  7. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/cli.py +3 -1
  8. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/correlator.py +159 -15
  9. agentdiscover-2.9.4/src/agent_discover_scanner/generation_defaults.py +250 -0
  10. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/network_monitor.py +9 -0
  11. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/platform.py +23 -1
  12. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/process_introspection.py +77 -0
  13. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/sarif_output.py +6 -0
  14. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/scan_runner.py +3 -0
  15. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/signatures.py +264 -1
  16. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/visitor.py +22 -0
  17. agentdiscover-2.9.4/tests/__init__.py +0 -0
  18. agentdiscover-2.9.4/tests/test_declared_model.py +322 -0
  19. agentdiscover-2.9.4/tests/test_generation_params.py +421 -0
  20. agentdiscover-2.9.2/src/agent_discover_scanner/audit_reports.py +0 -83
  21. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/.claude/commands/grow-scanner.md +0 -0
  22. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/.github/workflows/aibom.yml +0 -0
  23. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/.github/workflows/ci.yml +0 -0
  24. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/.github/workflows/scan.yml +0 -0
  25. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/.gitignore +0 -0
  26. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/.python-version +0 -0
  27. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/CHANGELOG.md +0 -0
  28. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/CLAUDE.md +0 -0
  29. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/COMMIT_NOTES.md +0 -0
  30. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/CONTRIBUTING.md +0 -0
  31. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/Dockerfile +0 -0
  32. /agentdiscover-2.9.2/src/agent_discover_scanner/detectors/__init__.py → /agentdiscover-2.9.4/FETCH_HEAD +0 -0
  33. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/LICENSE +0 -0
  34. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/README.md +0 -0
  35. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/README_BADGES.txt +0 -0
  36. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/RELEASE_NOTES.md +0 -0
  37. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/ROADMAP.md +0 -0
  38. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/SECURITY.md +0 -0
  39. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/action.yml +0 -0
  40. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/k8s/agents-configmap.yaml +0 -0
  41. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/k8s/crewai-agent.yaml +0 -0
  42. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/k8s/langchain-agent.yaml +0 -0
  43. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/k8s/shadow-agent.yaml +0 -0
  44. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/mcp-governance/DESIGN.md +0 -0
  45. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/mcp-governance/README.md +0 -0
  46. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/mcp-governance/sample-policy.yaml +0 -0
  47. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/run-demo.sh +0 -0
  48. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/sample-repo/agents/autogen_agent.py +0 -0
  49. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/sample-repo/agents/direct_client.py +0 -0
  50. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/sample-repo/requirements.txt +0 -0
  51. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/demo/setup.sh +0 -0
  52. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/deployment/kubernetes/tracing-policy.yaml +0 -0
  53. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/deployment/systemd/README.md +0 -0
  54. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/deployment/systemd/agent-discover-scanner.service +0 -0
  55. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docker-compose.yml +0 -0
  56. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docs/EXAMPLES.md +0 -0
  57. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docs/TETRAGON_SETUP.md +0 -0
  58. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docs/architecture.svg +0 -0
  59. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docs/comparisons/vs-grep.md +0 -0
  60. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docs/comparisons/vs-nudge-security.md +0 -0
  61. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docs/ghost-detection.svg +0 -0
  62. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docs/integrations/elastic.md +0 -0
  63. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docs/integrations/github-actions.md +0 -0
  64. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docs/integrations/kubernetes-daemonset.md +0 -0
  65. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docs/integrations/splunk.md +0 -0
  66. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docs/known-apps-example.txt +0 -0
  67. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docs/layer4-setup.md +0 -0
  68. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/docs/nist-mapping.md +0 -0
  69. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/examples/k8s/test-workload.yaml +0 -0
  70. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/examples/tetragon-sample-events.json +0 -0
  71. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/install.sh +0 -0
  72. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/__init__.py +0 -0
  73. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/behavioral_patterns.py +0 -0
  74. {agentdiscover-2.9.2/src/agent_discover_scanner/exporters → agentdiscover-2.9.4/src/agent_discover_scanner/detectors}/__init__.py +0 -0
  75. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/detectors/cloud_audit/__init__.py +0 -0
  76. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/detectors/cloud_audit/aws_cloudtrail.py +0 -0
  77. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/detectors/cloud_audit/azure_monitor.py +0 -0
  78. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/detectors/cloud_audit/base.py +0 -0
  79. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/detectors/cloud_audit/gcp_audit.py +0 -0
  80. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/detectors/cloudtrail.py +0 -0
  81. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/detectors/sse_proxy/__init__.py +0 -0
  82. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/detectors/sse_proxy/base.py +0 -0
  83. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/detectors/sse_proxy/netskope.py +0 -0
  84. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/detectors/sse_proxy/prisma_access.py +0 -0
  85. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/detectors/sse_proxy/zscaler_zia.py +0 -0
  86. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/errors.py +0 -0
  87. {agentdiscover-2.9.2/src/agent_discover_scanner/interceptors/sse → agentdiscover-2.9.4/src/agent_discover_scanner/exporters}/__init__.py +0 -0
  88. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/exporters/mcpfw_policy.py +0 -0
  89. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/git_scanner.py +0 -0
  90. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/high_risk_agents.py +0 -0
  91. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/interceptors/__init__.py +0 -0
  92. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/interceptors/base.py +0 -0
  93. {agentdiscover-2.9.2/src/agent_discover_scanner/layer4 → agentdiscover-2.9.4/src/agent_discover_scanner/interceptors/sse}/__init__.py +0 -0
  94. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/interceptors/sse/netskope.py +0 -0
  95. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/interceptors/sse/prisma_access.py +0 -0
  96. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/interceptors/sse/umbrella.py +0 -0
  97. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/interceptors/sse/zscaler.py +0 -0
  98. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/js_signatures.py +0 -0
  99. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/known_apps.py +0 -0
  100. {agentdiscover-2.9.2/tests → agentdiscover-2.9.4/src/agent_discover_scanner/layer4}/__init__.py +0 -0
  101. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/layer4/osquery_executor.py +0 -0
  102. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/layer4/osquery_queries.py +0 -0
  103. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/layer4/result_parser.py +0 -0
  104. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/macos_detector.py +0 -0
  105. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/mcp_detector.py +0 -0
  106. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/models/endpoint_discovery.py +0 -0
  107. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/monitors/__init__.py +0 -0
  108. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/monitors/json_output.py +0 -0
  109. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/monitors/k8s_monitor.py +0 -0
  110. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/monitors/tetragon_events.py +0 -0
  111. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/monitors/tetragon_monitor.py +0 -0
  112. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/monitors/vendor_mapping.py +0 -0
  113. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/reports/layer4_report.py +0 -0
  114. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/saas_detector.py +0 -0
  115. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/sbom_analyzer.py +0 -0
  116. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/scanner.py +0 -0
  117. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/src/agent_discover_scanner/windows_detector.py +0 -0
  118. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/autogen_safe.py +0 -0
  119. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/autogen_unsafe.py +0 -0
  120. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/bedrock_safe.py +0 -0
  121. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/bedrock_unsafe.py +0 -0
  122. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/clean_code.py +0 -0
  123. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/crewai_safe.py +0 -0
  124. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/crewai_unsafe.py +0 -0
  125. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/langchain_agents.py +0 -0
  126. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/langchain_js_agent.js +0 -0
  127. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/langgraph_workflow.py +0 -0
  128. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/safe_with_gateway.py +0 -0
  129. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/shadow_anthropic.py +0 -0
  130. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/shadow_openai.py +0 -0
  131. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/fixtures/shadow_openai_js.js +0 -0
  132. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_aibom.py +0 -0
  133. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_audit_bundle.py +0 -0
  134. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_bedrock_signatures.py +0 -0
  135. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_behavioral_patterns.py +0 -0
  136. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_cloud_audit_layer5.py +0 -0
  137. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_cloudtrail.py +0 -0
  138. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_cloudtrail_wiring.py +0 -0
  139. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_correlator.py +0 -0
  140. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_correlator_layer5.py +0 -0
  141. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_forward_dns.py +0 -0
  142. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_git_scanner.py +0 -0
  143. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_install.sh +0 -0
  144. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_interceptors.py +0 -0
  145. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_l2_process_introspection.py +0 -0
  146. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_mcpfw_export.py +0 -0
  147. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_network_monitor.py +0 -0
  148. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_platform_enrichment.py +0 -0
  149. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_scanner.py +0 -0
  150. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/tests/test_sse_proxy.py +0 -0
  151. {agentdiscover-2.9.2 → agentdiscover-2.9.4}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentdiscover
3
- Version: 2.9.2
3
+ Version: 2.9.4
4
4
  Summary: Discover every AI agent in your infrastructure. 5-layer detection: static analysis, network monitoring, eBPF/Kubernetes runtime, endpoint, and cloud audit (CloudTrail). Company-level correlation. AIBOM export. MCP server detection.
5
5
  Project-URL: Homepage, https://defendai.ai
6
6
  Project-URL: Documentation, https://github.com/Defend-AI-Tech-Inc/agent-discover-scanner/blob/main/README.md
@@ -1,10 +1,13 @@
1
1
  from crewai import Agent, Task, Crew
2
2
 
3
+ MODEL_NAME = "gpt-4-turbo"
4
+
3
5
  researcher = Agent(
4
6
  role="Researcher",
5
7
  goal="Research topics thoroughly",
6
8
  backstory="Expert researcher",
7
- allow_code_execution=False
9
+ allow_code_execution=False,
10
+ model=MODEL_NAME,
8
11
  )
9
12
 
10
13
  task = Task(description="Research AI security", agent=researcher)
@@ -3,7 +3,9 @@ from langchain.llms import OpenAI
3
3
 
4
4
  llm = OpenAI(temperature=0)
5
5
  tools = [Tool(name="search", func=lambda x: x, description="search")]
6
- agent = initialize_agent(tools, llm, agent="zero-shot-react-description")
6
+ agent = initialize_agent(
7
+ tools, llm, agent="zero-shot-react-description", model="gpt-4"
8
+ )
7
9
 
8
10
 
9
11
  def run(query: str):
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agentdiscover"
7
- version = "2.9.2"
7
+ version = "2.9.4"
8
8
  description = "Discover every AI agent in your infrastructure. 5-layer detection: static analysis, network monitoring, eBPF/Kubernetes runtime, endpoint, and cloud audit (CloudTrail). Company-level correlation. AIBOM export. MCP server detection."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -52,6 +52,52 @@ def generate_aibom(inventory_json: Path, output_path: Path) -> dict[str, Any]:
52
52
  "value": ",".join(str(x) for x in layers),
53
53
  }
54
54
  )
55
+ if agent.get("declared_model"):
56
+ comp["properties"].append(
57
+ {
58
+ "name": "agent-discover:declared_model",
59
+ "value": str(agent["declared_model"]),
60
+ }
61
+ )
62
+ if agent.get("declared_model_source"):
63
+ comp["properties"].append(
64
+ {
65
+ "name": "agent-discover:declared_model_source",
66
+ "value": str(agent["declared_model_source"]),
67
+ }
68
+ )
69
+ if agent.get("runtime_env_model"):
70
+ comp["properties"].append(
71
+ {
72
+ "name": "agent-discover:runtime_env_model",
73
+ "value": str(agent["runtime_env_model"]),
74
+ }
75
+ )
76
+ # Generation/sampling parameters (facts only — provenance-tagged raw
77
+ # values, same conditional-append pattern as declared_model above).
78
+ # Threshold flags (HIGH_TEMPERATURE/UNBOUNDED_MAX_TOKENS/ZERO_TEMPERATURE)
79
+ # are deliberately NEVER written here — a formal BOM artifact shouldn't
80
+ # carry subjective/informational flags; see audit_reports.py instead.
81
+ generation_params = agent.get("generation_params")
82
+ if isinstance(generation_params, dict):
83
+ for param_name, param_data in generation_params.items():
84
+ if not isinstance(param_data, dict):
85
+ continue
86
+ source = param_data.get("source")
87
+ if source:
88
+ comp["properties"].append(
89
+ {
90
+ "name": f"agent-discover:declared_{param_name}_source",
91
+ "value": str(source),
92
+ }
93
+ )
94
+ if "value" in param_data and param_data["value"] is not None:
95
+ comp["properties"].append(
96
+ {
97
+ "name": f"agent-discover:declared_{param_name}",
98
+ "value": str(param_data["value"]),
99
+ }
100
+ )
55
101
  components.append(comp)
56
102
 
57
103
  try:
@@ -0,0 +1,170 @@
1
+ """Markdown reports for the audit command."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+
9
+ def write_ghost_agents_markdown(inventory_json: Path, dest: Path) -> None:
10
+ data = json.loads(inventory_json.read_text(encoding="utf-8"))
11
+ ghosts = (data.get("inventory") or {}).get("ghost") or []
12
+ lines = [
13
+ "# Ghost agents",
14
+ "",
15
+ "Runtime signals without matching source inventory (from latest correlation).",
16
+ "",
17
+ ]
18
+ if not ghosts:
19
+ lines.append("_No ghost agents in this run._")
20
+ else:
21
+ for g in ghosts:
22
+ lines.append(f"## {g.get('agent_id', 'unknown')}")
23
+ lines.append("")
24
+ lines.append(f"- **Risk:** {g.get('risk_level', '')}")
25
+ lines.append(f"- **Provider / framework:** {g.get('network_provider') or g.get('framework') or '—'}")
26
+ lines.append(f"- **Process:** {g.get('process_name') or '—'}")
27
+ lines.append(f"- **Last seen:** {g.get('last_seen') or '—'}")
28
+ lines.append("")
29
+ dest.parent.mkdir(parents=True, exist_ok=True)
30
+ dest.write_text("\n".join(lines) + "\n", encoding="utf-8")
31
+
32
+
33
+ def write_mcp_markdown(mcp_result: dict[str, Any], dest: Path) -> None:
34
+ servers = mcp_result.get("servers") or []
35
+ lines = [
36
+ "# MCP servers",
37
+ "",
38
+ "Detected from configuration, process, registry, and optional network/endpoint context.",
39
+ "",
40
+ "[MCP] Enforce policies on detected servers → pip install mcpfw-defendai | mcpfw.dev",
41
+ "",
42
+ ]
43
+ if not servers:
44
+ lines.append("_No MCP servers detected in this run._")
45
+ else:
46
+ for s in servers:
47
+ name = s.get("server_name") or s.get("name") or "unknown"
48
+ lines.append(f"## {name}")
49
+ lines.append("")
50
+ lines.append(f"- **Vendor:** {s.get('vendor', '—')}")
51
+ lines.append(f"- **Publisher verified:** {s.get('publisher_verified', False)}")
52
+ lines.append(f"- **Local script:** {s.get('is_local_script', False)}")
53
+ lines.append("")
54
+ dest.parent.mkdir(parents=True, exist_ok=True)
55
+ dest.write_text("\n".join(lines) + "\n", encoding="utf-8")
56
+
57
+
58
+ _GENERATION_PARAM_ORDER = (
59
+ "temperature",
60
+ "top_p",
61
+ "top_k",
62
+ "max_tokens",
63
+ "frequency_penalty",
64
+ "presence_penalty",
65
+ "stop",
66
+ "seed",
67
+ "n",
68
+ )
69
+
70
+
71
+ def write_generation_params_markdown(inventory_json: Path, dest: Path) -> None:
72
+ """
73
+ Write generation/sampling parameter facts + informational threshold flags.
74
+
75
+ This report is intentionally separate from ghost-agents.md / the SARIF
76
+ security findings: HIGH_TEMPERATURE / UNBOUNDED_MAX_TOKENS / ZERO_TEMPERATURE
77
+ are opinions about configuration, not security findings, and must never
78
+ influence risk_level or agent classification (see correlator.py /
79
+ generation_defaults.py). This file is the only place they are surfaced.
80
+ """
81
+ data = json.loads(inventory_json.read_text(encoding="utf-8"))
82
+ inventory = data.get("inventory") or {}
83
+
84
+ agents_with_params = []
85
+ for classification, agents in inventory.items():
86
+ if not isinstance(agents, list):
87
+ continue
88
+ for agent in agents:
89
+ if isinstance(agent, dict) and agent.get("generation_params"):
90
+ agents_with_params.append((classification, agent))
91
+
92
+ lines = [
93
+ "# Generation / sampling parameters",
94
+ "",
95
+ "Raw detected values for temperature, top_p, top_k, max_tokens, "
96
+ "frequency_penalty, presence_penalty, stop, seed, and n, with honest "
97
+ "provenance per parameter — explicit (found in code), framework_default "
98
+ "(backfilled from a maintained provider/wrapper table), not_applicable "
99
+ "(fixed/rejected by the model family), or unknown (no reliable default "
100
+ "applies).",
101
+ "",
102
+ "**This report is informational only.** Flags below (HIGH_TEMPERATURE, "
103
+ "UNBOUNDED_MAX_TOKENS, ZERO_TEMPERATURE) are configuration observations, "
104
+ "not security findings — they do NOT affect risk_level, agent "
105
+ "classification, or `aibom.json`. No prescriptive \"recommended value\" "
106
+ "is given; that is a deliberately separate, out-of-scope decision.",
107
+ "",
108
+ ]
109
+
110
+ if not agents_with_params:
111
+ lines.append("_No generation/sampling parameters detected in this run._")
112
+ else:
113
+ for classification, agent in agents_with_params:
114
+ lines.append(f"## {agent.get('agent_id', 'unknown')}")
115
+ lines.append("")
116
+ lines.append(f"- **Classification:** {classification}")
117
+ lines.append(f"- **Framework:** {agent.get('framework') or '—'}")
118
+ if agent.get("declared_model"):
119
+ lines.append(f"- **Declared model:** {agent['declared_model']}")
120
+ lines.append("")
121
+ lines.append("| Parameter | Value | Provenance |")
122
+ lines.append("|---|---|---|")
123
+ params = agent.get("generation_params") or {}
124
+ for param_name in _GENERATION_PARAM_ORDER:
125
+ param_data = params.get(param_name)
126
+ if not isinstance(param_data, dict):
127
+ continue
128
+ source = param_data.get("source", "unknown")
129
+ value = param_data.get("value", "—")
130
+ lines.append(f"| `{param_name}` | {value} | {source} |")
131
+ lines.append("")
132
+ flags = agent.get("generation_param_flags") or []
133
+ if flags:
134
+ lines.append("**Notes:**")
135
+ lines.append("")
136
+ for flag in flags:
137
+ lines.append(f"- `{flag.get('flag')}`: {flag.get('message')}")
138
+ lines.append("")
139
+
140
+ dest.parent.mkdir(parents=True, exist_ok=True)
141
+ dest.write_text("\n".join(lines) + "\n", encoding="utf-8")
142
+
143
+
144
+ def write_audit_summary(report: dict[str, Any], dest: Path, raw_dir: Path) -> None:
145
+ s = report.get("summary") or {}
146
+ lines = [
147
+ "# Audit summary",
148
+ "",
149
+ f"**Generated:** {report.get('generated_at', '')}",
150
+ "",
151
+ "## Counts",
152
+ "",
153
+ f"- Confirmed: {s.get('confirmed', 0)}",
154
+ f"- Unknown: {s.get('unknown', 0)}",
155
+ f"- Ghost: {s.get('ghost', 0)}",
156
+ f"- Zombie: {s.get('zombie', 0)}",
157
+ f"- Shadow AI usage: {s.get('shadow_ai_usage', 0)}",
158
+ "",
159
+ "## Artifacts",
160
+ "",
161
+ f"- Raw scan directory: `{raw_dir}`",
162
+ "- `aibom.json` — CycloneDX-oriented AIBOM",
163
+ "- `ghost-agents.md` — Ghost agent detail",
164
+ "- `mcp-report.md` — MCP inventory",
165
+ "- `generation-params.md` — Generation/sampling parameter facts (informational)",
166
+ "- `summary.md` — This file",
167
+ "",
168
+ ]
169
+ dest.parent.mkdir(parents=True, exist_ok=True)
170
+ dest.write_text("\n".join(lines) + "\n", encoding="utf-8")
@@ -1012,11 +1012,12 @@ def audit(
1012
1012
  ):
1013
1013
  """
1014
1014
  Run the full scan-all pipeline into output/raw/, then write AIBOM JSON and Markdown reports
1015
- (ghost-agents.md, mcp-report.md, summary.md) beside raw/.
1015
+ (ghost-agents.md, mcp-report.md, generation-params.md, summary.md) beside raw/.
1016
1016
  """
1017
1017
  from agent_discover_scanner.aibom import generate_aibom
1018
1018
  from agent_discover_scanner.audit_reports import (
1019
1019
  write_audit_summary,
1020
+ write_generation_params_markdown,
1020
1021
  write_ghost_agents_markdown,
1021
1022
  write_mcp_markdown,
1022
1023
  )
@@ -1068,6 +1069,7 @@ def audit(
1068
1069
 
1069
1070
  generate_aibom(inv_path, output / "aibom.json")
1070
1071
  write_ghost_agents_markdown(inv_path, output / "ghost-agents.md")
1072
+ write_generation_params_markdown(inv_path, output / "generation-params.md")
1071
1073
  write_audit_summary(report, output / "summary.md", raw_dir)
1072
1074
 
1073
1075
  nf: list = []
@@ -13,6 +13,12 @@ from typing import Any, Dict, List, Optional, Tuple
13
13
 
14
14
  import os
15
15
 
16
+ from agent_discover_scanner.generation_defaults import (
17
+ classify_wrapper_key,
18
+ compute_threshold_flags,
19
+ has_any_signal,
20
+ resolve_generation_params,
21
+ )
16
22
  from agent_discover_scanner.known_apps import build_known_apps, is_known_desktop_app
17
23
 
18
24
 
@@ -131,6 +137,45 @@ class AgentInventoryItem:
131
137
  saas_connections: Dict[str, Any] = field(default_factory=dict)
132
138
  risk_flags: List[str] = field(default_factory=list)
133
139
 
140
+ # Model identification (v2.7.1+) — local, free-pipeline model identity tiers.
141
+ # Distinct from (and never merged with) Layer 5's invocation-confirmed
142
+ # model_id / metadata.models_called, which remains the highest-confidence tier.
143
+ #
144
+ # declared_model / declared_model_source: Layer 1 static analysis — a model
145
+ # identifier found via source (see signatures.py: extract_declared_model).
146
+ # declared_model_source is "literal" (string constant kwarg) or
147
+ # "resolved_constant" (kwarg was a Name resolved to a same-file constant).
148
+ declared_model: Optional[str] = None
149
+ declared_model_source: Optional[str] = None
150
+ # runtime_env_model / runtime_env_source: Layer 2 live process introspection —
151
+ # read from the actual environment of the running process (higher confidence
152
+ # than declared_model since it reflects live process memory, not source code).
153
+ # runtime_env_source is the matched env var name (e.g. "MODEL_NAME").
154
+ runtime_env_model: Optional[str] = None
155
+ runtime_env_source: Optional[str] = None
156
+
157
+ # Generation/sampling parameters (v2.10.0+) — Layer 1 static analysis.
158
+ # Facts only: raw values with honest provenance, one entry per tracked
159
+ # parameter (see generation_defaults.ALL_GENERATION_PARAMS), e.g.
160
+ # {"temperature": {"value": 0.7, "source": "framework_default"},
161
+ # "max_tokens": {"source": "unknown"}, ...}
162
+ # `source` is one of "literal" / "resolved_constant" (both mean: found
163
+ # explicitly in code — the "explicit" tier), "framework_default"
164
+ # (backfilled from the maintained defaults table), "not_applicable"
165
+ # (fixed/rejected by this provider+wrapper/model family — never has a
166
+ # `value`), or "unknown" (not found, no reliable default — never has a
167
+ # `value`). Only populated when there's real signal (a resolvable
168
+ # provider+wrapper OR at least one explicit value) — see
169
+ # generation_defaults.has_any_signal.
170
+ generation_params: Optional[Dict[str, Dict[str, Any]]] = None
171
+ # Threshold flags derived from generation_params (HIGH_TEMPERATURE,
172
+ # UNBOUNDED_MAX_TOKENS, ZERO_TEMPERATURE) — INFORMATIONAL ONLY.
173
+ # Deliberately never merged into risk_level/risk_flags/classification, and
174
+ # deliberately excluded from aibom.json (a formal BOM artifact shouldn't
175
+ # carry subjective flags). Consumed only by audit_reports.py's
176
+ # generation-params.md.
177
+ generation_param_flags: List[Dict[str, str]] = field(default_factory=list)
178
+
134
179
  def __post_init__(self):
135
180
  if self.discovered_at is None:
136
181
  self.discovered_at = datetime.now().isoformat()
@@ -211,12 +256,24 @@ def _populate_saas_and_risk_flags(
211
256
 
212
257
  risk_flags = []
213
258
  detected = saas.get("detected", [])
259
+ is_shadow = item.classification == "shadow_ai_usage"
260
+
214
261
  if item.classification == "ghost" and detected:
215
262
  risk_flags.append("critical_ghost")
216
- if saas.get("has_cloud_provider"):
263
+
264
+ # cloud_credentials_present and database_credentials_present require evidence of
265
+ # actual credentials (API keys, env vars, .env files) — not just network connections
266
+ # to cloud services. Shadow AI agents are browsers / desktop apps: their network
267
+ # traffic to Azure/GCP/AWS is normal browsing, not credential usage. Only flag
268
+ # when credential_files_found exist, or when the agent is code-backed (non-shadow).
269
+ has_actual_cred_evidence = bool(
270
+ saas.get("credential_files_found") or (not is_shadow)
271
+ )
272
+ if saas.get("has_cloud_provider") and has_actual_cred_evidence:
217
273
  risk_flags.append("cloud_credentials_present")
218
- if saas.get("has_database_access"):
274
+ if saas.get("has_database_access") and has_actual_cred_evidence:
219
275
  risk_flags.append("database_credentials_present")
276
+
220
277
  if saas.get("has_external_api_calls") and item.classification == "unknown":
221
278
  risk_flags.append("unverified_saas_access")
222
279
  item.risk_flags = risk_flags
@@ -246,19 +303,23 @@ class CorrelationEngine:
246
303
  findings = []
247
304
  for run in sarif.get("runs", []):
248
305
  for result in run.get("results", []):
249
- findings.append(
250
- {
251
- "rule_id": result.get("ruleId"),
252
- "file_path": result["locations"][0]["physicalLocation"][
253
- "artifactLocation"
254
- ]["uri"],
255
- "line": result["locations"][0]["physicalLocation"]["region"][
256
- "startLine"
257
- ],
258
- "message": result["message"]["text"],
259
- "level": result.get("level", "warning"),
260
- }
261
- )
306
+ finding = {
307
+ "rule_id": result.get("ruleId"),
308
+ "file_path": result["locations"][0]["physicalLocation"][
309
+ "artifactLocation"
310
+ ]["uri"],
311
+ "line": result["locations"][0]["physicalLocation"]["region"][
312
+ "startLine"
313
+ ],
314
+ "message": result["message"]["text"],
315
+ "level": result.get("level", "warning"),
316
+ }
317
+ # Structured data (e.g. declared_model tier) carried via SARIF's
318
+ # standard properties bag — see sarif_output.py / signatures.py.
319
+ properties = result.get("properties")
320
+ if properties:
321
+ finding["extracted"] = properties
322
+ findings.append(finding)
262
323
 
263
324
  return findings
264
325
  except (json.JSONDecodeError, KeyError):
@@ -692,6 +753,12 @@ class CorrelationEngine:
692
753
  "framework_confidence": nf.get("framework_confidence"), # "high"/"medium"/None
693
754
  "entry_script": nf.get("entry_script"), # resolved script path
694
755
  "detected_host": nf.get("detected_host"), # DNS-correlated hostname
756
+ # runtime_env_model tier — read live from the process's own
757
+ # environment via psutil (process_introspection.py). Highest
758
+ # local-scan confidence: reflects what the process actually
759
+ # has in memory, not what's hardcoded in source.
760
+ "runtime_env_model": nf.get("runtime_env_model"),
761
+ "runtime_env_source": nf.get("runtime_env_source"),
695
762
  }
696
763
 
697
764
  # Index Layer 3 by provider (first match per provider for filling k8s fields)
@@ -711,8 +778,44 @@ class CorrelationEngine:
711
778
  # Track which Layer 3 findings were matched to a code finding (for GHOST: unmatched = ghost)
712
779
  matched_l3_keys: set = set() # (namespace, pod) tuples
713
780
 
781
+ # DAI008 (declared_model) findings are a metadata-enrichment signal, not an
782
+ # independent agent signal — they never become their own inventory item.
783
+ # Instead, index them by (file_path, line) so their `extracted` payload can
784
+ # be merged onto a co-located framework/provider finding (DAI001-DAI007 at
785
+ # the same call site) below.
786
+ model_by_location: Dict[Tuple[str, int], Dict] = {}
787
+ for cf in code_findings:
788
+ if cf.get("rule_id") == "DAI008" and cf.get("extracted", {}).get("declared_model"):
789
+ model_by_location[(cf.get("file_path"), cf.get("line"))] = cf["extracted"]
790
+
791
+ # DAI009 (generation params) findings are likewise metadata-enrichment,
792
+ # never an independent agent signal. Unlike DAI008 (matched by exact
793
+ # file+line), generation params are commonly set on a *different* call
794
+ # than the one that anchors the primary framework finding (e.g. the
795
+ # LLM wrapper constructor `ChatOpenAI(temperature=0.7)` vs. the
796
+ # `initialize_agent(...)` call it's passed into) — so these are
797
+ # indexed by file only and merged (union across all DAI009 findings
798
+ # in that file) onto every primary finding in the same file below.
799
+ genparams_by_file: Dict[str, Dict[str, Dict]] = {}
800
+ wrapper_hint_by_file: Dict[str, str] = {}
801
+ for cf in code_findings:
802
+ if cf.get("rule_id") not in ("DAI008", "DAI009"):
803
+ continue
804
+ extracted = cf.get("extracted") or {}
805
+ file_path = cf.get("file_path")
806
+ if not file_path:
807
+ continue
808
+ if extracted.get("wrapper_hint"):
809
+ wrapper_hint_by_file[file_path] = extracted["wrapper_hint"]
810
+ if cf.get("rule_id") == "DAI009":
811
+ found = extracted.get("generation_params") or {}
812
+ genparams_by_file.setdefault(file_path, {}).update(found)
813
+
714
814
  # Process code findings (Layer 1)
715
815
  for cf in code_findings:
816
+ if cf.get("rule_id") in ("DAI008", "DAI009"):
817
+ continue # metadata-only; merged into same-file findings above
818
+
716
819
  agent_id = f"{cf['file_path']}:{cf['line']}"
717
820
  framework = cls.extract_framework_from_rule(cf["rule_id"])
718
821
 
@@ -730,6 +833,31 @@ class CorrelationEngine:
730
833
 
731
834
  possible = cls._code_finding_providers(cf)
732
835
 
836
+ # declared_model tier (Layer 1, static): either this finding directly
837
+ # carries `extracted` (e.g. Bedrock model-ID string literal), or a
838
+ # co-located DAI008 finding at the same file+line does.
839
+ _model_extracted = cf.get("extracted") or model_by_location.get(
840
+ (cf.get("file_path"), cf.get("line"))
841
+ )
842
+ declared_model = (_model_extracted or {}).get("declared_model")
843
+ declared_model_source = (_model_extracted or {}).get("declared_model_source")
844
+
845
+ # Generation/sampling parameters (facts, informational flags are separate
846
+ # — see module docstring on AgentInventoryItem.generation_param_flags).
847
+ _file_path_key = cf.get("file_path")
848
+ _explicit_genparams = genparams_by_file.get(_file_path_key, {})
849
+ _wrapper_hint = wrapper_hint_by_file.get(_file_path_key)
850
+ generation_params = None
851
+ generation_param_flags: List[Dict[str, str]] = []
852
+ _wrapper_key = classify_wrapper_key(
853
+ cf.get("rule_id"), cf.get("message"), _wrapper_hint, declared_model
854
+ )
855
+ if has_any_signal(_wrapper_key, _explicit_genparams):
856
+ generation_params = resolve_generation_params(
857
+ _wrapper_key, declared_model, _explicit_genparams
858
+ )
859
+ generation_param_flags = compute_threshold_flags(generation_params)
860
+
733
861
  # Layer 5 enrichment locals — filled by supplemental blocks below.
734
862
  model_id = None
735
863
  l5_framework = None
@@ -812,6 +940,8 @@ class CorrelationEngine:
812
940
  l2_entry_script: str | None = None
813
941
  l2_detected_host: str | None = None
814
942
  l2_framework_confidence: str | None = None
943
+ l2_runtime_env_model: str | None = None
944
+ l2_runtime_env_source: str | None = None
815
945
 
816
946
  if match_provider:
817
947
  if match_provider in active_providers:
@@ -822,6 +952,8 @@ class CorrelationEngine:
822
952
  l2_entry_script = info.get("entry_script")
823
953
  l2_detected_host = info.get("detected_host")
824
954
  l2_framework_confidence = info.get("framework_confidence")
955
+ l2_runtime_env_model = info.get("runtime_env_model")
956
+ l2_runtime_env_source = info.get("runtime_env_source")
825
957
  network_provider = match_provider
826
958
  if matched_l3 is not None:
827
959
  k8s_pod = matched_l3.get("pod")
@@ -910,6 +1042,12 @@ class CorrelationEngine:
910
1042
  detected_hosts=[l2_detected_host] if l2_detected_host else [],
911
1043
  framework_confidence=l2_framework_confidence,
912
1044
  evidence=_build_evidence_tags(detection_layers, l2_entry_script, l2_detected_host),
1045
+ declared_model=declared_model,
1046
+ declared_model_source=declared_model_source,
1047
+ runtime_env_model=l2_runtime_env_model,
1048
+ runtime_env_source=l2_runtime_env_source,
1049
+ generation_params=generation_params,
1050
+ generation_param_flags=generation_param_flags,
913
1051
  )
914
1052
  try:
915
1053
  _file_path = (cf.get("file_path") or "") or ""
@@ -943,6 +1081,8 @@ class CorrelationEngine:
943
1081
  entry_script = info.get("entry_script") # resolved script path
944
1082
  detected_host = info.get("detected_host") # DNS-correlated hostname
945
1083
  l2_fw_confidence = info.get("framework_confidence") # "high"/"medium"/None
1084
+ l2_rt_env_model = info.get("runtime_env_model") # runtime_env_model tier
1085
+ l2_rt_env_source = info.get("runtime_env_source")
946
1086
 
947
1087
  if is_known_desktop_app(process_name, _known_apps):
948
1088
  # Known consumer AI tool (Cursor, Claude Desktop, etc.) → Shadow AI
@@ -985,6 +1125,8 @@ class CorrelationEngine:
985
1125
  detected_hosts=[detected_host] if detected_host else [],
986
1126
  framework_confidence=l2_fw_confidence,
987
1127
  evidence=_build_evidence_tags(["layer2"], entry_script, detected_host),
1128
+ runtime_env_model=l2_rt_env_model,
1129
+ runtime_env_source=l2_rt_env_source,
988
1130
  )
989
1131
  try:
990
1132
  _populate_saas_and_risk_flags(
@@ -1010,6 +1152,8 @@ class CorrelationEngine:
1010
1152
  detection_layers=["layer2"],
1011
1153
  detected_hosts=[detected_host] if detected_host else [],
1012
1154
  evidence=_build_evidence_tags(["layer2"], None, detected_host),
1155
+ runtime_env_model=l2_rt_env_model,
1156
+ runtime_env_source=l2_rt_env_source,
1013
1157
  )
1014
1158
  try:
1015
1159
  _populate_saas_and_risk_flags(