agentsentinel-cli 0.7.5__tar.gz → 0.7.6__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.5 → agentsentinel_cli-0.7.6}/PKG-INFO +1 -1
  2. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/a2a_rules.py +0 -1
  3. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/mcp_rules.py +42 -37
  4. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/rules.py +10 -20
  5. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/pyproject.toml +1 -1
  6. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/.gitignore +0 -0
  7. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/DOCUMENTATION.md +0 -0
  8. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/LICENSE +0 -0
  9. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/README.md +0 -0
  10. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/__init__.py +0 -0
  11. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/a2a_report.py +0 -0
  12. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/a2a_scanner.py +0 -0
  13. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/agent_mode.py +0 -0
  14. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/agent_mode_report.py +0 -0
  15. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/ai_probe.py +0 -0
  16. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/attacks/__init__.py +0 -0
  17. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/attacks/library.py +0 -0
  18. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/cli.py +0 -0
  19. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/discover.py +0 -0
  20. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/discover_report.py +0 -0
  21. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/fingerprint.py +0 -0
  22. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/frameworks.py +0 -0
  23. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/inspect.py +0 -0
  24. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/inspect_report.py +0 -0
  25. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/mcp_client.py +0 -0
  26. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/mcp_report.py +0 -0
  27. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/probe.py +0 -0
  28. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/probe_report.py +0 -0
  29. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/report.py +0 -0
  30. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/scanner.py +0 -0
  31. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/secrets.py +0 -0
  32. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/secrets_report.py +0 -0
  33. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/secrets_rules.py +0 -0
  34. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/supply_chain_ai.py +0 -0
  35. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/supply_chain_report.py +0 -0
  36. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/supply_chain_rules.py +0 -0
  37. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/suppress.py +0 -0
  38. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/agentsentinel_cli/target.py +0 -0
  39. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/tmp/note.md +0 -0
  40. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/tmp/test-mcp-agent/README.md +0 -0
  41. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/tmp/test-mcp-agent/langchain_agent.py +0 -0
  42. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/tmp/test-mcp-agent/mcp_server.py +0 -0
  43. {agentsentinel_cli-0.7.5 → agentsentinel_cli-0.7.6}/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.5
3
+ Version: 0.7.6
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
@@ -269,7 +269,6 @@ _ALL_RULES = [
269
269
  _rule_prompt_passthrough,
270
270
  _rule_implicit_trust_with_code_exec,
271
271
  _rule_unscoped_delegation,
272
- _rule_unverified_orchestrator,
273
272
  ]
274
273
 
275
274
  _SEVERITY_WEIGHT = {"CRITICAL": 40, "HIGH": 20, "MEDIUM": 10, "LOW": 5}
@@ -114,15 +114,26 @@ def _rule_code_execution(ctx: McpContext) -> McpFinding | None:
114
114
 
115
115
 
116
116
  def _rule_unbounded_input(ctx: McpContext) -> McpFinding | None:
117
- """HIGH: unconstrained string inputs increase injection payload surface. (OWASP LLM01)"""
117
+ """HIGH: dangerous string parameters have no input constraints. (OWASP LLM01)
118
+
119
+ Only flags parameters whose names suggest they feed dangerous operations —
120
+ shell commands, SQL queries, file paths, URLs, code. Generic string fields
121
+ (name, title, message, body) are not flagged.
122
+ """
123
+ _DANGEROUS_PARAMS = frozenset({
124
+ "command", "cmd", "shell", "shell_command",
125
+ "query", "sql", "sql_query", "expression",
126
+ "path", "file_path", "filepath", "filename", "directory", "dir",
127
+ "url", "uri", "endpoint", "webhook", "target",
128
+ "code", "script", "template", "prompt",
129
+ })
130
+
118
131
  unvalidated: list[str] = []
119
132
  for tool in ctx.server.tools:
120
- schema = tool.input_schema
121
- props = schema.get("properties", {})
122
- if not props and schema.get("type") == "object":
123
- unvalidated.append(f"{tool.name} (no schema)")
124
- continue
133
+ props = tool.input_schema.get("properties", {})
125
134
  for prop_name, prop_def in props.items():
135
+ if prop_name.lower() not in _DANGEROUS_PARAMS:
136
+ continue
126
137
  if (
127
138
  prop_def.get("type") == "string"
128
139
  and "maxLength" not in prop_def
@@ -130,7 +141,6 @@ def _rule_unbounded_input(ctx: McpContext) -> McpFinding | None:
130
141
  and "pattern" not in prop_def
131
142
  ):
132
143
  unvalidated.append(f"{tool.name}.{prop_name}")
133
- break
134
144
 
135
145
  if unvalidated:
136
146
  sample = unvalidated[:5]
@@ -139,24 +149,29 @@ def _rule_unbounded_input(ctx: McpContext) -> McpFinding | None:
139
149
  severity="HIGH",
140
150
  rule_id="UNBOUNDED_INPUT",
141
151
  message=(
142
- "Tools accept unconstrained string inputs with no maxLength, enum, or pattern. "
143
- "Injection payloads can be passed directly through tool arguments."
152
+ "Dangerous parameters (command, path, query, url, code) accept unconstrained "
153
+ "string input. No maxLength, enum, or pattern — injection payloads pass through directly."
144
154
  ),
145
- detail=f"Unconstrained inputs: {', '.join(sample)}{suffix}",
155
+ detail=f"Unconstrained dangerous inputs: {', '.join(sample)}{suffix}",
146
156
  )
147
157
  return None
148
158
 
149
159
 
150
160
  def _rule_tool_sprawl(ctx: McpContext) -> McpFinding | None:
151
- """MEDIUM: excessive tool count increases blast radius. (OWASP LLM06)"""
161
+ """MEDIUM: high tool count across many categories increases blast radius. (OWASP LLM06)
162
+
163
+ Requires BOTH high count AND diverse categories. A server with 14 file-system
164
+ tools is a focused file manager. A server with 12 tools spanning code execution,
165
+ email, database, and web is a broad attack surface.
166
+ """
152
167
  categories = {t.category for t in ctx.server.tools} - {"other"}
153
- if len(ctx.server.tools) > 10 or len(categories) >= 5:
168
+ if len(ctx.server.tools) > 10 and len(categories) >= 5:
154
169
  return McpFinding(
155
170
  severity="MEDIUM",
156
171
  rule_id="TOOL_SPRAWL",
157
172
  message=(
158
- f"Server exposes {len(ctx.server.tools)} tools across {len(categories)} categories. "
159
- "Every tool is an attack surface reduce to the minimum required."
173
+ f"Server exposes {len(ctx.server.tools)} tools across {len(categories)} "
174
+ "distinct categories. High cross-category diversity increases blast radius."
160
175
  ),
161
176
  detail=f"Categories: {', '.join(sorted(categories))}",
162
177
  )
@@ -164,33 +179,25 @@ def _rule_tool_sprawl(ctx: McpContext) -> McpFinding | None:
164
179
 
165
180
 
166
181
  def _rule_vague_descriptions(ctx: McpContext) -> McpFinding | None:
167
- """MEDIUM: thin tool descriptions expand prompt injection surface. (OWASP LLM01)"""
168
- vague = [t.name for t in ctx.server.tools if len(t.description.strip()) < 20]
182
+ """MEDIUM: missing or single-word tool descriptions expand prompt injection surface. (OWASP LLM01)
183
+
184
+ Flags descriptions with fewer than 3 words — empty, one-word, or two-word
185
+ descriptions give the LLM no context about what the tool does or what it
186
+ should NOT do.
187
+ """
188
+ vague = [
189
+ t.name for t in ctx.server.tools
190
+ if len(t.description.strip().split()) < 3
191
+ ]
169
192
  if len(vague) >= 2:
170
193
  return McpFinding(
171
194
  severity="MEDIUM",
172
195
  rule_id="VAGUE_TOOL_DESCRIPTIONS",
173
196
  message=(
174
- "Multiple tools have short or missing descriptions. "
175
- "Vague descriptions make it easier for prompt injection to misdirect tool use."
176
- ),
177
- detail=f"Thin descriptions on: {', '.join(vague[:5])}{'…' if len(vague) > 5 else ''}",
178
- )
179
- return None
180
-
181
-
182
- def _rule_missing_rate_limit(ctx: McpContext) -> McpFinding | None:
183
- """LOW: MCP protocol has no built-in rate limiting. (OWASP LLM06)"""
184
- dangerous = [t.name for t in ctx.server.tools if t.is_dangerous]
185
- if dangerous:
186
- return McpFinding(
187
- severity="LOW",
188
- rule_id="MISSING_RATE_LIMIT",
189
- message=(
190
- "Dangerous tools detected. MCP has no built-in rate limiting — "
191
- "ensure the server layer enforces per-client call limits."
197
+ "Multiple tools have absent or near-absent descriptions (fewer than 3 words). "
198
+ "Without clear descriptions the LLM cannot reason about safe tool use."
192
199
  ),
193
- detail=f"Verify limits on: {', '.join(dangerous)}",
200
+ detail=f"Absent descriptions: {', '.join(vague[:5])}{'…' if len(vague) > 5 else ''}",
194
201
  )
195
202
  return None
196
203
 
@@ -206,8 +213,6 @@ _ALL_RULES = [
206
213
  # MEDIUM
207
214
  _rule_tool_sprawl,
208
215
  _rule_vague_descriptions,
209
- # LOW
210
- _rule_missing_rate_limit,
211
216
  ]
212
217
 
213
218
  _SEVERITY_WEIGHT = {"CRITICAL": 40, "HIGH": 20, "MEDIUM": 10, "LOW": 5}
@@ -145,27 +145,32 @@ def _rule_privilege_excess(agent: AgentInfo) -> Finding | None:
145
145
 
146
146
 
147
147
  def _rule_dangerous_grants(agent: AgentInfo) -> Finding | None:
148
- dangerous = [t.name for t in agent.tools if t.is_dangerous]
148
+ # Exclude code_execution tools CODE_EXECUTION_GRANT already covers them at CRITICAL.
149
+ # This rule catches dangerous tools outside that category (e.g. send_email, delete_record).
150
+ dangerous = [
151
+ t.name for t in agent.tools
152
+ if t.is_dangerous and t.category not in _CODE_EXEC_CATEGORIES
153
+ ]
149
154
  if dangerous:
150
155
  return Finding(
151
156
  severity="HIGH",
152
157
  rule_id="DANGEROUS_GRANTS",
153
- message="Agent holds dangerous tool grants. Verify intent and add rate limits.",
158
+ message="Agent holds dangerous tool grants outside code execution. Verify intent.",
154
159
  detail=f"Dangerous tools: {', '.join(dangerous)}",
155
160
  )
156
161
  return None
157
162
 
158
163
 
159
164
  def _rule_tool_sprawl(agent: AgentInfo) -> Finding | None:
160
- """MEDIUM: agent holds too many tools across too many categories — blast radius scales with sprawl."""
165
+ """MEDIUM: high tool count across many categories — blast radius scales with diversity."""
161
166
  categories = {t.category for t in agent.tools if t.category != "other"}
162
- if len(agent.tools) > 10 or len(categories) >= 5:
167
+ if len(agent.tools) > 10 and len(categories) >= 5:
163
168
  return Finding(
164
169
  severity="MEDIUM",
165
170
  rule_id="TOOL_SPRAWL",
166
171
  message=(
167
172
  f"Agent holds {len(agent.tools)} tools across {len(categories)} categories. "
168
- "Reduce grants to the minimum required for each task."
173
+ "High cross-category diversity increases blast radius."
169
174
  ),
170
175
  detail=f"Categories present: {', '.join(sorted(categories))}",
171
176
  )
@@ -187,19 +192,6 @@ def _rule_write_without_description(agent: AgentInfo) -> Finding | None:
187
192
  return None
188
193
 
189
194
 
190
- def _rule_missing_rate_limit(agent: AgentInfo) -> Finding | None:
191
- """Flag dangerous tools — rate limits aren't visible in static analysis."""
192
- dangerous = [t.name for t in agent.tools if t.is_dangerous]
193
- if dangerous:
194
- return Finding(
195
- severity="LOW",
196
- rule_id="MISSING_RATE_LIMIT",
197
- message="Dangerous grants detected. Ensure rate limits are configured at runtime.",
198
- detail=f"Tools to check: {', '.join(dangerous)}",
199
- )
200
- return None
201
-
202
-
203
195
  _ALL_RULES = [
204
196
  # CRITICAL
205
197
  _rule_exfiltration_path,
@@ -215,8 +207,6 @@ _ALL_RULES = [
215
207
  # MEDIUM
216
208
  _rule_tool_sprawl,
217
209
  _rule_write_without_description,
218
- # LOW
219
- _rule_missing_rate_limit,
220
210
  ]
221
211
 
222
212
  _SEVERITY_WEIGHT = {"CRITICAL": 40, "HIGH": 20, "MEDIUM": 10, "LOW": 5}
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agentsentinel-cli"
7
- version = "0.7.5"
7
+ version = "0.7.6"
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"