security: supply chain hardening — CI pinning, dep pinning, and code fixes (#9801)

CI/CD Hardening:
- Pin all 12 GitHub Actions to full commit SHAs (was mutable @vN tags)
- Add explicit permissions: {contents: read} to 4 workflows
- Pin CI pip installs to exact versions (pyyaml==6.0.2, httpx==0.28.1)
- Extend supply-chain-audit.yml to scan workflow, Dockerfile, dependency
  manifest, and Actions version changes

Dependency Pinning:
- Pin git-based Python deps to commit SHAs (atroposlib, tinker, yc-bench)
- Pin WhatsApp Baileys from mutable branch to commit SHA

Tool Registry:
- Reject tool name shadowing from different tool families (plugins/MCP
  cannot overwrite built-in tools). MCP-to-MCP overwrites still allowed.

MCP Security:
- Add tool description content scanning for prompt injection patterns
- Log detailed change diff on dynamic tool refresh at WARNING level

Skill Manager:
- Fix dangerous verdict bug: agent-created skills with dangerous
  findings were silently allowed (ask->None->allow). Now blocked.
This commit is contained in:
Teknium
2026-04-14 14:23:37 -07:00
committed by GitHub
parent 9bbf7659e9
commit eed891f1bb
14 changed files with 214 additions and 53 deletions

View File

@@ -117,11 +117,27 @@ class ToolRegistry:
with self._lock:
existing = self._tools.get(name)
if existing and existing.toolset != toolset:
logger.warning(
"Tool name collision: '%s' (toolset '%s') is being "
"overwritten by toolset '%s'",
name, existing.toolset, toolset,
# Allow MCP-to-MCP overwrites (legitimate: server refresh,
# or two MCP servers with overlapping tool names).
both_mcp = (
existing.toolset.startswith("mcp-")
and toolset.startswith("mcp-")
)
if both_mcp:
logger.debug(
"Tool '%s': MCP toolset '%s' overwriting MCP toolset '%s'",
name, toolset, existing.toolset,
)
else:
# Reject shadowing — prevent plugins/MCP from overwriting
# built-in tools or vice versa.
logger.error(
"Tool registration REJECTED: '%s' (toolset '%s') would "
"shadow existing tool from toolset '%s'. Deregister the "
"existing tool first if this is intentional.",
name, toolset, existing.toolset,
)
return
self._tools[name] = ToolEntry(
name=name,
toolset=toolset,