agentsentinel-cli 0.7.2__tar.gz → 0.7.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 (43) hide show
  1. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/PKG-INFO +1 -1
  2. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/cli.py +24 -4
  3. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/discover.py +33 -10
  4. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/pyproject.toml +1 -1
  5. agentsentinel_cli-0.7.4/tmp/note.md +4 -0
  6. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/.gitignore +0 -0
  7. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/DOCUMENTATION.md +0 -0
  8. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/LICENSE +0 -0
  9. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/README.md +0 -0
  10. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/__init__.py +0 -0
  11. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/a2a_report.py +0 -0
  12. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/a2a_rules.py +0 -0
  13. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/a2a_scanner.py +0 -0
  14. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/agent_mode.py +0 -0
  15. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/agent_mode_report.py +0 -0
  16. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/ai_probe.py +0 -0
  17. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/attacks/__init__.py +0 -0
  18. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/attacks/library.py +0 -0
  19. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/discover_report.py +0 -0
  20. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/fingerprint.py +0 -0
  21. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/frameworks.py +0 -0
  22. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/inspect.py +0 -0
  23. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/inspect_report.py +0 -0
  24. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/mcp_client.py +0 -0
  25. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/mcp_report.py +0 -0
  26. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/mcp_rules.py +0 -0
  27. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/probe.py +0 -0
  28. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/probe_report.py +0 -0
  29. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/report.py +0 -0
  30. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/rules.py +0 -0
  31. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/scanner.py +0 -0
  32. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/secrets.py +0 -0
  33. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/secrets_report.py +0 -0
  34. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/secrets_rules.py +0 -0
  35. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/supply_chain_ai.py +0 -0
  36. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/supply_chain_report.py +0 -0
  37. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/supply_chain_rules.py +0 -0
  38. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/suppress.py +0 -0
  39. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/agentsentinel_cli/target.py +0 -0
  40. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/tmp/test-mcp-agent/README.md +0 -0
  41. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/tmp/test-mcp-agent/langchain_agent.py +0 -0
  42. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/tmp/test-mcp-agent/mcp_server.py +0 -0
  43. {agentsentinel_cli-0.7.2 → agentsentinel_cli-0.7.4}/tmp/test-mcp-agent/requirements.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentsentinel-cli
3
- Version: 0.7.2
3
+ Version: 0.7.4
4
4
  Summary: Agentic security CLI — AI analyst with memory, supply chain audit, MCP audit, red-team probing, and agent discovery
5
5
  Project-URL: Homepage, https://github.com/jaydenaung/agentsentinel-cli
6
6
  Project-URL: Repository, https://github.com/jaydenaung/agentsentinel-cli
@@ -128,6 +128,8 @@ def _enrich_from_platform(agents, scores_map, connect_url, api_key):
128
128
  help="Probe local ports — confirmed via MCP protocol handshake.")
129
129
  @click.option("--docker/--no-docker", default=False, show_default=True,
130
130
  help="Inspect running Docker containers for MCP/agent patterns.")
131
+ @click.option("--host", default=None, metavar="IP",
132
+ help="Scan a single host, e.g. 10.0.1.45.")
131
133
  @click.option("--subnet", default=None, metavar="CIDR",
132
134
  help="Scan a CIDR subnet for MCP servers, e.g. 10.0.0.0/24.")
133
135
  @click.option("--ports", default=None, metavar="RANGE",
@@ -142,6 +144,7 @@ def discover(
142
144
  process: bool,
143
145
  network: bool,
144
146
  docker: bool,
147
+ host: str | None,
145
148
  subnet: str | None,
146
149
  ports: str | None,
147
150
  auth_header: str | None,
@@ -151,12 +154,12 @@ def discover(
151
154
  """Find MCP servers and AI agent processes in your environment.
152
155
 
153
156
  Confirms MCP servers via protocol handshake — not just open ports.
154
- Use --subnet to scan across a network range.
155
157
 
156
158
  \b
157
159
  Examples:
158
160
  sentinel discover local processes + ports
159
- sentinel discover --subnet 10.0.0.0/24 scan internal network
161
+ sentinel discover --host 10.0.1.45 single remote host
162
+ sentinel discover --subnet 10.0.0.0/24 full subnet scan
160
163
  sentinel discover --subnet 10.0.0.0/24 \\
161
164
  --auth-header 'Authorization: Bearer token' scan with credentials
162
165
  sentinel discover --no-process network only
@@ -164,7 +167,7 @@ def discover(
164
167
  sentinel discover --ports 8000-9001 custom port range
165
168
  sentinel discover --format json machine-readable output
166
169
  """
167
- from agentsentinel_cli.discover import run_discovery, as_json as discover_json
170
+ from agentsentinel_cli.discover import run_discovery, scan_network, as_json as discover_json
168
171
  from agentsentinel_cli.discover_report import print_discover_result, print_subnet_progress
169
172
 
170
173
  # Parse port range
@@ -179,6 +182,23 @@ def discover(
179
182
  key, _, val = auth_header.partition(":")
180
183
  extra_headers[key.strip()] = val.strip()
181
184
 
185
+ # --host: single-host scan — bypass run_discovery and call scan_network directly
186
+ if host:
187
+ if fmt == "text":
188
+ _warn_missing_deps(False, True)
189
+ agents = scan_network(
190
+ host=host,
191
+ ports=port_list,
192
+ extra_headers=extra_headers or None,
193
+ )
194
+ if fmt == "json":
195
+ click.echo(discover_json(agents))
196
+ return
197
+ print_discover_result(agents, vectors=[f"host ({host})"], verbose=verbose)
198
+ if any(a.risk == "CRITICAL" for a in agents):
199
+ sys.exit(1)
200
+ return
201
+
182
202
  # Collect active scan vectors for the header
183
203
  vectors = []
184
204
  if process:
@@ -192,7 +212,7 @@ def discover(
192
212
 
193
213
  if not vectors:
194
214
  console.print("[yellow]No scan vectors selected — use at least one of: "
195
- "--process, --network, --subnet, --docker[/yellow]")
215
+ "--process, --network, --host, --subnet, --docker[/yellow]")
196
216
  sys.exit(1)
197
217
 
198
218
  if fmt == "text":
@@ -379,6 +379,23 @@ def scan_subnet(
379
379
 
380
380
  # ── MCP protocol prober ───────────────────────────────────────────────────────
381
381
 
382
+ def _auth_is_enforced(base: str, timeout: float) -> bool:
383
+ """Return True only if the server actively rejects unauthenticated requests.
384
+
385
+ Probes without credentials. McpAuthRequired (401/403) → enforced.
386
+ Successful handshake → not enforced (server accepts anyone).
387
+ Any other error → assume not enforced (conservative — don't hide risk).
388
+ """
389
+ from agentsentinel_cli.mcp_client import scan_http, McpAuthRequired, McpError
390
+ try:
391
+ scan_http(base, extra_headers=None, timeout=timeout)
392
+ return False
393
+ except McpAuthRequired:
394
+ return True
395
+ except (McpError, Exception):
396
+ return False
397
+
398
+
382
399
  def _probe_mcp(
383
400
  host: str,
384
401
  port: int,
@@ -423,28 +440,34 @@ def _probe_mcp(
423
440
  except Exception:
424
441
  return None
425
442
 
426
- # Handshake succeeded — assess risk based on actual tool content
443
+ # Handshake succeeded — assess risk based on actual tool content and whether
444
+ # the server actually enforces authentication.
427
445
  tool_names = [t.name for t in server.tools]
428
446
  has_dangerous = any(t.is_dangerous for t in server.tools)
429
447
  has_write = any(t.scope == "write" for t in server.tools)
430
- auth_present = bool(extra_headers)
431
448
 
432
- if not auth_present:
449
+ # When credentials were provided, verify the server actually requires them.
450
+ # If it accepts a probe WITHOUT credentials too, auth is not enforced — the
451
+ # server is still open to anyone and the risk doesn't change.
452
+ auth_enforced = False
453
+ if extra_headers:
454
+ auth_enforced = _auth_is_enforced(base, timeout)
455
+
456
+ if not extra_headers or not auth_enforced:
433
457
  if has_dangerous or has_write:
434
458
  risk = "CRITICAL"
435
- risk_reason = (
436
- f"Unauthenticated MCP server with dangerous/write tools: "
437
- f"{', '.join(t.name for t in server.tools if t.is_dangerous or t.scope == 'write')}"
438
- )
459
+ bad = ", ".join(t.name for t in server.tools if t.is_dangerous or t.scope == "write")
460
+ risk_reason = f"Unauthenticated MCP server with dangerous/write tools: {bad}"
439
461
  else:
462
+ n = len(server.tools)
440
463
  risk = "HIGH"
441
464
  risk_reason = (
442
- f"Unauthenticated MCP server — {len(server.tools)} tool"
443
- f"{'s' if len(server.tools) != 1 else ''} publicly accessible"
465
+ f"Unauthenticated MCP server — {n} tool{'s' if n != 1 else ''} publicly accessible"
444
466
  )
445
467
  else:
468
+ n = len(server.tools)
446
469
  risk = "LOW"
447
- risk_reason = f"MCP server (authenticated) — {len(server.tools)} tool{'s' if len(server.tools) != 1 else ''} enumerated"
470
+ risk_reason = f"MCP server (auth enforced) — {n} tool{'s' if n != 1 else ''} enumerated"
448
471
 
449
472
  scan_url = f"{base}/sse" if server.transport == "sse" else base
450
473
  auth_flag = (
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agentsentinel-cli"
7
- version = "0.7.2"
7
+ version = "0.7.4"
8
8
  description = "Agentic security CLI — AI analyst with memory, supply chain audit, MCP audit, red-team probing, and agent discovery"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,4 @@
1
+ # Notes
2
+
3
+ python3.11 -m build
4
+ twine upload dist/agentsentinel_cli-0.7.3* --username __token__ --password $PYPI_TOKEN