agentsentinel-cli 0.7.3__tar.gz → 0.7.5__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.3 → agentsentinel_cli-0.7.5}/PKG-INFO +1 -1
  2. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/cli.py +64 -0
  3. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/discover.py +33 -10
  4. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/pyproject.toml +1 -1
  5. agentsentinel_cli-0.7.5/tmp/note.md +16 -0
  6. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/.gitignore +0 -0
  7. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/DOCUMENTATION.md +0 -0
  8. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/LICENSE +0 -0
  9. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/README.md +0 -0
  10. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/__init__.py +0 -0
  11. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/a2a_report.py +0 -0
  12. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/a2a_rules.py +0 -0
  13. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/a2a_scanner.py +0 -0
  14. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/agent_mode.py +0 -0
  15. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/agent_mode_report.py +0 -0
  16. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/ai_probe.py +0 -0
  17. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/attacks/__init__.py +0 -0
  18. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/attacks/library.py +0 -0
  19. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/discover_report.py +0 -0
  20. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/fingerprint.py +0 -0
  21. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/frameworks.py +0 -0
  22. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/inspect.py +0 -0
  23. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/inspect_report.py +0 -0
  24. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/mcp_client.py +0 -0
  25. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/mcp_report.py +0 -0
  26. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/mcp_rules.py +0 -0
  27. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/probe.py +0 -0
  28. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/probe_report.py +0 -0
  29. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/report.py +0 -0
  30. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/rules.py +0 -0
  31. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/scanner.py +0 -0
  32. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/secrets.py +0 -0
  33. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/secrets_report.py +0 -0
  34. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/secrets_rules.py +0 -0
  35. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/supply_chain_ai.py +0 -0
  36. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/supply_chain_report.py +0 -0
  37. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/supply_chain_rules.py +0 -0
  38. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/suppress.py +0 -0
  39. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/agentsentinel_cli/target.py +0 -0
  40. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/tmp/test-mcp-agent/README.md +0 -0
  41. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/tmp/test-mcp-agent/langchain_agent.py +0 -0
  42. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/tmp/test-mcp-agent/mcp_server.py +0 -0
  43. {agentsentinel_cli-0.7.3 → agentsentinel_cli-0.7.5}/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.3
3
+ Version: 0.7.5
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
@@ -119,6 +119,60 @@ def _enrich_from_platform(agents, scores_map, connect_url, api_key):
119
119
  console.print(f" [dim yellow]Warning: could not connect to AgentSentinel: {exc}[/dim yellow]")
120
120
 
121
121
 
122
+ # ── sentinel discover helpers ────────────────────────────────────────────────
123
+
124
+ def _deep_scan_agents(agents: list, extra_headers: dict | None) -> None:
125
+ """Run mcp scan rules on every confirmed network MCP server from a discover run."""
126
+ from agentsentinel_cli.discover import DiscoveredAgent
127
+ from agentsentinel_cli.mcp_client import scan_http, McpAuthRequired, McpError
128
+ from agentsentinel_cli.mcp_rules import McpContext, run_mcp_rules, mcp_posture_score
129
+ from agentsentinel_cli.mcp_report import print_mcp_result
130
+ from agentsentinel_cli import suppress as _suppress
131
+
132
+ network_agents = [a for a in agents if a.source == "network"]
133
+ if not network_agents:
134
+ return
135
+
136
+ console.print()
137
+ console.rule("[bold bright_blue]DEEP SCAN[/bold bright_blue]", style="bright_blue")
138
+
139
+ sup_rules = _suppress.load_ignore_file(Path.cwd())
140
+
141
+ for agent in network_agents:
142
+ base = f"http://{agent.location}"
143
+ scan_url = f"{base}/sse" if agent.transport == "sse" else base
144
+
145
+ # Auth-required servers need credentials — skip silently if none provided
146
+ if not agent.tools and not extra_headers:
147
+ console.print(
148
+ f"\n [dim]Skipping {agent.location} — auth required, "
149
+ f"use --auth-header to deep scan[/dim]"
150
+ )
151
+ continue
152
+
153
+ try:
154
+ server = scan_http(scan_url, extra_headers=extra_headers, timeout=15)
155
+ except McpAuthRequired:
156
+ console.print(
157
+ f"\n [dim]Skipping {agent.location} — credentials rejected[/dim]"
158
+ )
159
+ continue
160
+ except (McpError, Exception):
161
+ console.print(
162
+ f"\n [dim]Skipping {agent.location} — could not reconnect[/dim]"
163
+ )
164
+ continue
165
+
166
+ # auth_required: True when the server actually enforces auth (risk LOW/MEDIUM)
167
+ auth_required = agent.risk in ("LOW", "MEDIUM")
168
+ ctx = McpContext(server=server, auth_required=auth_required)
169
+ findings = run_mcp_rules(ctx)
170
+ findings, _ = _suppress.apply(findings, sup_rules)
171
+ score = mcp_posture_score(findings)
172
+
173
+ print_mcp_result(ctx, findings, score, scan_url)
174
+
175
+
122
176
  # ── sentinel discover ─────────────────────────────────────────────────────────
123
177
 
124
178
  @main.command()
@@ -136,6 +190,8 @@ def _enrich_from_platform(agents, scores_map, connect_url, api_key):
136
190
  help="Custom port range, e.g. 8000-9001. Defaults to common MCP/agent ports.")
137
191
  @click.option("--auth-header", "auth_header", default=None, metavar="HEADER",
138
192
  help="HTTP auth header for MCP handshakes, e.g. 'Authorization: Bearer token'.")
193
+ @click.option("--scan", "do_scan", is_flag=True, default=False,
194
+ help="Deep-scan every confirmed MCP server with sentinel mcp scan rules.")
139
195
  @click.option("--format", "fmt", type=click.Choice(["text", "json"]), default="text",
140
196
  help="Output format.")
141
197
  @click.option("--verbose", "-v", is_flag=True, default=False,
@@ -148,17 +204,20 @@ def discover(
148
204
  subnet: str | None,
149
205
  ports: str | None,
150
206
  auth_header: str | None,
207
+ do_scan: bool,
151
208
  fmt: str,
152
209
  verbose: bool,
153
210
  ) -> None:
154
211
  """Find MCP servers and AI agent processes in your environment.
155
212
 
156
213
  Confirms MCP servers via protocol handshake — not just open ports.
214
+ Add --scan to deep-audit every confirmed server in the same run.
157
215
 
158
216
  \b
159
217
  Examples:
160
218
  sentinel discover local processes + ports
161
219
  sentinel discover --host 10.0.1.45 single remote host
220
+ sentinel discover --host 10.0.1.45 --scan discover + deep audit
162
221
  sentinel discover --subnet 10.0.0.0/24 full subnet scan
163
222
  sentinel discover --subnet 10.0.0.0/24 \\
164
223
  --auth-header 'Authorization: Bearer token' scan with credentials
@@ -195,6 +254,8 @@ def discover(
195
254
  click.echo(discover_json(agents))
196
255
  return
197
256
  print_discover_result(agents, vectors=[f"host ({host})"], verbose=verbose)
257
+ if do_scan:
258
+ _deep_scan_agents(agents, extra_headers or None)
198
259
  if any(a.risk == "CRITICAL" for a in agents):
199
260
  sys.exit(1)
200
261
  return
@@ -237,6 +298,9 @@ def discover(
237
298
 
238
299
  print_discover_result(agents, vectors=vectors, verbose=verbose, subnet_stats=subnet_stats)
239
300
 
301
+ if do_scan:
302
+ _deep_scan_agents(agents, extra_headers or None)
303
+
240
304
  # Exit 1 if any CRITICAL agents found (useful for CI)
241
305
  if any(a.risk == "CRITICAL" for a in agents):
242
306
  sys.exit(1)
@@ -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.3"
7
+ version = "0.7.5"
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,16 @@
1
+ # Notes
2
+ ls dist/
3
+
4
+ python3.11 -m build
5
+ twine upload dist/agentsentinel_cli-0.7.3* --username __token__ --password $PYPI_TOKEN
6
+
7
+ lsof -i :8000
8
+ lsof -ti :8000
9
+
10
+ kill $(lsof -ti :8000)
11
+
12
+ kill -9 $(lsof -ti :8000)
13
+
14
+ nsenter -t <PID> -m -u -i -n -p -- /bin/bash
15
+
16
+ ps -p <PID> -o pid,ppid,command