forked from molecule-ai/molecule-core
feat(plugin): split compliance-posture into 3 plugins (#256)
Closes #256. Per CEO direction, shipping three separate opt-in plugins instead of one bundled "compliance-posture" — keeps installs granular so a workspace that only wants CVE scanning doesn't carry OWASP policy or append-only audit retention. - plugins/molecule-compliance/ — wraps compliance.py (OWASP OA-01 prompt injection + OA-03 excessive agency). Skill: owasp-agentic. - plugins/molecule-audit/ — wraps audit.py (EU AI Act Art. 12/13/17 append-only JSONL log, SIEM-friendly). Skill: ai-act-audit-log. - plugins/molecule-security-scan/ — wraps security_scan.py (Snyk or pip-audit CVE gate on skill requirements.txt). Skill: skill-cve-gate. Each plugin ships a manifest + one SKILL.md with: - When to install / when to skip - Configuration shape (config.yaml blocks) - Anti-patterns to avoid - Cross-references to the other two plugins so an operator can reason about the full compliance surface All three wrap code that already exists in workspace-template/builtin_tools/ — no Python changes. Install per workspace via POST /workspaces/:id/plugins {"source":"builtin://molecule-<name>"}. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2aa901882f
commit
45e4eb0be3
16
plugins/molecule-audit/plugin.yaml
Normal file
16
plugins/molecule-audit/plugin.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
name: molecule-audit
|
||||
version: 1.0.0
|
||||
description: >
|
||||
Immutable append-only audit log for EU AI Act compliance (Articles 12/13/17).
|
||||
Wraps builtin_tools/audit.py — JSON Lines format, SIEM-friendly, write-only.
|
||||
Opt-in per workspace; usually paired with molecule-compliance.
|
||||
author: Molecule AI
|
||||
tags: [audit, compliance, eu-ai-act, logging, siem]
|
||||
|
||||
runtimes:
|
||||
- langgraph
|
||||
- claude_code
|
||||
- deepagents
|
||||
|
||||
skills:
|
||||
- ai-act-audit-log
|
||||
133
plugins/molecule-audit/skills/ai-act-audit-log/SKILL.md
Normal file
133
plugins/molecule-audit/skills/ai-act-audit-log/SKILL.md
Normal file
@ -0,0 +1,133 @@
|
||||
---
|
||||
name: ai-act-audit-log
|
||||
description: "Emit immutable audit events for EU AI Act compliance. Use when a workspace performs any action that needs to be legally reconstructable: delegations, approvals, RBAC decisions, memory read/write. JSON Lines, append-only, SIEM-friendly."
|
||||
---
|
||||
|
||||
# EU AI Act Audit Log
|
||||
|
||||
Opt-in plugin that activates `builtin_tools/audit.py` — an append-only
|
||||
JSON Lines log satisfying the record-keeping and transparency obligations
|
||||
of the EU AI Act (Articles 12, 13, 17) for high-risk AI systems.
|
||||
|
||||
## When to install
|
||||
|
||||
Install on any workspace that:
|
||||
- Must satisfy EU AI Act conformity assessment
|
||||
- Needs a tamper-evident trail of agent decisions for a legal discovery
|
||||
- Pairs with `molecule-compliance` to record OWASP OA-01 detections and
|
||||
OA-03 terminations
|
||||
|
||||
Skip on disposable dev workspaces — the log fills disk over time and
|
||||
isn't useful for throwaway agents.
|
||||
|
||||
## Event schema
|
||||
|
||||
Every line is one JSON object:
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-04-15T21:30:00.123Z",
|
||||
"event_type": "delegation",
|
||||
"workspace_id": "ws-acme-pm-a1b2c3d4",
|
||||
"actor": "ws-acme-pm-a1b2c3d4",
|
||||
"action": "delegate",
|
||||
"resource": "ws-acme-dev-lead-e5f6g7h8",
|
||||
"outcome": "allowed",
|
||||
"trace_id": "5e8b2f3c-9a1d-4e7b-8c6f-1234567890ab"
|
||||
}
|
||||
```
|
||||
|
||||
Required fields:
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| `timestamp` | ISO-8601 UTC with offset — sort key + freshness indicator |
|
||||
| `event_type` | `delegation` / `approval` / `memory` / `rbac` |
|
||||
| `workspace_id` | Who generated the event |
|
||||
| `actor` | Who triggered the action (defaults to workspace_id for automated events; human identity for approval decisions) |
|
||||
| `action` | Verb: `delegate`, `approve`, `memory.read`, `memory.write`, `rbac.deny` |
|
||||
| `resource` | Target of the action: another workspace id, memory scope, approval action string |
|
||||
| `outcome` | `allowed` / `denied` / `success` / `failure` / `timeout` / `requested` / `granted` |
|
||||
| `trace_id` | UUID v4 correlating related events across workspaces |
|
||||
|
||||
## Usage
|
||||
|
||||
Call `audit.log_event` from any tool or handler:
|
||||
|
||||
```python
|
||||
from builtin_tools.audit import log_event
|
||||
|
||||
log_event(
|
||||
event_type="delegation",
|
||||
workspace_id=self.workspace_id,
|
||||
actor=self.workspace_id,
|
||||
action="delegate",
|
||||
resource=target_workspace_id,
|
||||
outcome="allowed",
|
||||
trace_id=ctx.trace_id,
|
||||
)
|
||||
```
|
||||
|
||||
The function is synchronous and fire-and-forget — it opens the log file
|
||||
in append mode, writes one line, closes. No buffering, no retry. If the
|
||||
disk is full the call raises `IOError`; the caller decides whether to
|
||||
surface that (usually yes — an audit gap is a compliance event itself).
|
||||
|
||||
## Configuration
|
||||
|
||||
Add to `config.yaml`:
|
||||
|
||||
```yaml
|
||||
audit:
|
||||
enabled: true
|
||||
log_path: /var/log/molecule/audit.jsonl
|
||||
max_size_mb: 100 # informational only; rotation is EXTERNAL
|
||||
retention_days: 365 # informational only; the module never deletes
|
||||
```
|
||||
|
||||
## Rotation (external)
|
||||
|
||||
This module is **write-only by design**. It does not rotate, compress,
|
||||
or delete log lines. Use the host's `logrotate` (Linux) or equivalent:
|
||||
|
||||
```
|
||||
/var/log/molecule/audit.jsonl {
|
||||
daily
|
||||
rotate 365
|
||||
compress
|
||||
copytruncate # NOT truncate — copytruncate leaves the file open
|
||||
missingok
|
||||
notifempty
|
||||
}
|
||||
```
|
||||
|
||||
`copytruncate` is load-bearing — the Python side holds the file
|
||||
descriptor open for append, so a rename-based rotation would orphan the
|
||||
new file and writes would continue to the rotated-away path.
|
||||
|
||||
## SIEM ingestion
|
||||
|
||||
The JSON Lines format is directly consumable by:
|
||||
- Splunk (ingest via Universal Forwarder)
|
||||
- Elastic (Filebeat + JSON decoder)
|
||||
- Datadog (Agent in JSON mode)
|
||||
- Self-hosted Loki
|
||||
|
||||
One ingestion pipeline per workspace volume. No post-processing needed.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- **Don't** write to the same log path from multiple workspaces on the
|
||||
same host — races corrupt the JSONL newlines. Use per-workspace paths.
|
||||
- **Don't** truncate or edit the log. Tamper-evidence is the whole point.
|
||||
- **Don't** log raw PII or secrets in the `resource` or `outcome` fields.
|
||||
Use IDs or hashes; the audit story and the GDPR story have to coexist.
|
||||
- **Don't** skip this on OA-01/OA-03 detections — they're exactly the
|
||||
events an auditor wants to see.
|
||||
|
||||
## Related
|
||||
|
||||
- `builtin_tools/audit.py` — the implementation
|
||||
- `molecule-compliance` — emits OWASP OA-01 / OA-03 events into this log
|
||||
- `molecule-security-scan` — emits CVE-scan results into this log
|
||||
- Issue #256 — the proposal that led to this plugin split
|
||||
17
plugins/molecule-compliance/plugin.yaml
Normal file
17
plugins/molecule-compliance/plugin.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
name: molecule-compliance
|
||||
version: 1.0.0
|
||||
description: >
|
||||
OWASP Top 10 for Agentic Applications (Dec 2025) compliance enforcement.
|
||||
Wraps builtin_tools/compliance.py — prompt-injection detection/blocking,
|
||||
excessive-agency limits (max tool calls + task duration). Opt-in per
|
||||
workspace via config.yaml compliance block.
|
||||
author: Molecule AI
|
||||
tags: [compliance, owasp, security, prompt-injection]
|
||||
|
||||
runtimes:
|
||||
- langgraph
|
||||
- claude_code
|
||||
- deepagents
|
||||
|
||||
skills:
|
||||
- owasp-agentic
|
||||
91
plugins/molecule-compliance/skills/owasp-agentic/SKILL.md
Normal file
91
plugins/molecule-compliance/skills/owasp-agentic/SKILL.md
Normal file
@ -0,0 +1,91 @@
|
||||
---
|
||||
name: owasp-agentic
|
||||
description: "Enforce OWASP Top 10 for Agentic Applications. Use when a workspace handles untrusted input (user messages, scraped web content, file uploads) or when it would be catastrophic if the agent ran away with unlimited tool calls. Gates prompt injection + excessive agency."
|
||||
---
|
||||
|
||||
# OWASP Agentic Compliance
|
||||
|
||||
Opt-in compliance layer that wraps `builtin_tools/compliance.py`. The
|
||||
Python primitives exist in every runtime image — installing this plugin
|
||||
activates them via config and documents the policy.
|
||||
|
||||
## Coverage
|
||||
|
||||
| OWASP ID | Name | Primitive | Default mode |
|
||||
|---|---|---|---|
|
||||
| **OA-01** | Prompt Injection | `sanitize_input(text)` | `detect` |
|
||||
| **OA-03** | Excessive Agency | `check_agency_limits(task_ctx)` | 50 calls / 300s |
|
||||
|
||||
## When to install
|
||||
|
||||
Install this plugin on any workspace that:
|
||||
- Accepts free-form user input (chat interfaces, A2A message bodies)
|
||||
- Scrapes or ingests untrusted web content
|
||||
- Runs long-horizon tasks where a stuck loop could burn LLM budget
|
||||
- Must satisfy compliance reviews that cite OWASP Top 10 for AI
|
||||
|
||||
## Configuration
|
||||
|
||||
Add to `config.yaml`:
|
||||
|
||||
```yaml
|
||||
compliance:
|
||||
mode: owasp_agentic
|
||||
prompt_injection: detect # detect → log+pass, block → raise PromptInjectionError
|
||||
max_tool_calls_per_task: 50 # OA-03 ceiling
|
||||
max_task_duration_seconds: 300 # OA-03 wall-clock ceiling
|
||||
```
|
||||
|
||||
Modes explained:
|
||||
|
||||
- **`detect`** (default) — logs an audit event via `audit.log_event` when a
|
||||
trigger pattern is found, returns the original text. The agent still
|
||||
processes the input. Good for rollout: you see what triggers before
|
||||
committing to blocking.
|
||||
- **`block`** — raises `PromptInjectionError` before the agent sees the
|
||||
text. The caller (typically `a2a_executor.py`) catches it and returns a
|
||||
400-shaped error to the sender.
|
||||
|
||||
## Trigger patterns (OA-01)
|
||||
|
||||
`sanitize_input` scans for:
|
||||
- Instruction-override phrases ("ignore previous instructions", "new system prompt")
|
||||
- Role-hijacking attempts ("you are now", "act as")
|
||||
- System-prompt delimiter injection (`</s>`, `<|im_start|>`)
|
||||
- Known jailbreak keywords (rotating list; update via compliance.py)
|
||||
|
||||
False positives on legitimate content are expected in `detect` mode —
|
||||
that's why it's the default. Only flip to `block` after you've reviewed
|
||||
audit logs for a week and confirmed the hit rate is low.
|
||||
|
||||
## Agency limits (OA-03)
|
||||
|
||||
Tracks per-task:
|
||||
- Number of tool calls (`tool_call_count`)
|
||||
- Elapsed wall-clock time (`started_at → now`)
|
||||
|
||||
When either exceeds the configured ceiling, `check_agency_limits` raises
|
||||
`ExcessiveAgencyError`. The task terminates gracefully — the caller sees
|
||||
a final message + `status=failed`.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- **Don't** install on workspaces that only process trusted internal
|
||||
input — the overhead isn't worth it.
|
||||
- **Don't** set `max_tool_calls_per_task` below 20. Many legitimate
|
||||
multi-step tasks need 15-30 tool calls; ceilings that low cause false
|
||||
terminations.
|
||||
- **Don't** flip `prompt_injection` to `block` without a rollout period.
|
||||
- **Don't** rely on this as your only defense — it's a cheap policy
|
||||
layer, not a substitute for proper sandboxing of the agent's
|
||||
filesystem + network access.
|
||||
|
||||
## Related
|
||||
|
||||
- `builtin_tools/compliance.py` — the implementation
|
||||
- `molecule-audit` — audit-log retention for the events this plugin
|
||||
generates (OA-01 detections, OA-03 terminations). Install both to get
|
||||
a coherent compliance story.
|
||||
- `molecule-security-scan` — pre-load CVE gate for skill dependencies
|
||||
(complements this runtime policy with supply-chain policy).
|
||||
- Issue #256 — the proposal that led to this plugin split
|
||||
16
plugins/molecule-security-scan/plugin.yaml
Normal file
16
plugins/molecule-security-scan/plugin.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
name: molecule-security-scan
|
||||
version: 1.0.0
|
||||
description: >
|
||||
Supply-chain CVE gate for skill dependencies. Wraps builtin_tools/security_scan.py —
|
||||
runs Snyk or pip-audit against a skill's requirements.txt before the skill
|
||||
loads, blocking or warning on critical/high CVEs. Opt-in per workspace.
|
||||
author: Molecule AI
|
||||
tags: [security, cve, supply-chain, snyk, pip-audit]
|
||||
|
||||
runtimes:
|
||||
- langgraph
|
||||
- claude_code
|
||||
- deepagents
|
||||
|
||||
skills:
|
||||
- skill-cve-gate
|
||||
128
plugins/molecule-security-scan/skills/skill-cve-gate/SKILL.md
Normal file
128
plugins/molecule-security-scan/skills/skill-cve-gate/SKILL.md
Normal file
@ -0,0 +1,128 @@
|
||||
---
|
||||
name: skill-cve-gate
|
||||
description: "Block or warn on CVE-vulnerable dependencies before a skill loads into the workspace. Use when a workspace installs skills from third-party sources (user-uploaded, marketplace, agentskills.io). Prevents known-bad transitive deps from running in the agent's process."
|
||||
---
|
||||
|
||||
# Skill CVE Gate
|
||||
|
||||
Supply-chain risk management for skill dependencies. Wraps
|
||||
`builtin_tools/security_scan.py`. When a skill is about to load, the
|
||||
gate runs a CVE scanner against its `requirements.txt` and either
|
||||
blocks, warns, or skips depending on mode.
|
||||
|
||||
## Scanners (auto-selected)
|
||||
|
||||
| Scanner | Requires | When selected |
|
||||
|---|---|---|
|
||||
| **Snyk CLI** | `snyk` binary in PATH + `SNYK_TOKEN` env | Available — preferred (richer DB + license coverage) |
|
||||
| **pip-audit** | `pip-audit` binary in PATH | Fallback when Snyk isn't installed |
|
||||
| **(none)** | — | Neither available → skip with log line |
|
||||
|
||||
Selection happens at scan time, per skill. No config flag needed.
|
||||
|
||||
## Modes
|
||||
|
||||
Configure in `config.yaml`:
|
||||
|
||||
```yaml
|
||||
security_scan:
|
||||
mode: warn # off | warn | block
|
||||
```
|
||||
|
||||
- **`off`** — skip scanning entirely. Useful in air-gapped CI that has
|
||||
no network access to CVE databases, or dev loops where you know the
|
||||
deps are vetted.
|
||||
- **`warn`** (default) — run the scanner, log a WARNING + audit event
|
||||
on any critical/high finding, but load the skill anyway. Good for
|
||||
rollout phase: you see the risk surface without breaking users.
|
||||
- **`block`** — raise `SkillSecurityError` when critical/high CVEs are
|
||||
found. Skill does not load; agent falls back to built-in tools only.
|
||||
Use once warn-phase is clean.
|
||||
|
||||
## When to install
|
||||
|
||||
Install on any workspace that:
|
||||
- Installs skills from third-party sources (marketplace, agentskills.io,
|
||||
user uploads)
|
||||
- Runs in a production tenant context where agent compromise is
|
||||
meaningful
|
||||
- Must satisfy a supply-chain audit (SOC 2, ISO 27001 control A.8.28)
|
||||
|
||||
Skip on workspaces that only use first-party plugins from
|
||||
`plugins/molecule-*` — those are vetted at commit time in monorepo CI.
|
||||
|
||||
## Audit trail
|
||||
|
||||
Every scan writes to the audit log via `audit.log_event`:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "supply_chain",
|
||||
"action": "cve_scan",
|
||||
"resource": "skill-name:version",
|
||||
"outcome": "pass",
|
||||
"detail": {
|
||||
"scanner": "snyk",
|
||||
"critical": 0,
|
||||
"high": 0,
|
||||
"medium": 2,
|
||||
"low": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Failures (mode=block) log `outcome: "denied"` + the blocking CVE id.
|
||||
Pair with `molecule-audit` to get the full JSONL trail.
|
||||
|
||||
## SNYK_TOKEN
|
||||
|
||||
Set via workspace secret (not config.yaml):
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/workspaces/$WS_ID/secrets \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key":"SNYK_TOKEN","value":"..."}'
|
||||
```
|
||||
|
||||
Snyk authenticates via env var; the token is injected at container
|
||||
start. Without it Snyk runs in unauthenticated mode (fewer CVE sources
|
||||
available) and the fallback to pip-audit is more attractive.
|
||||
|
||||
## Configuration — full
|
||||
|
||||
```yaml
|
||||
security_scan:
|
||||
mode: warn
|
||||
# Override the auto-selected scanner:
|
||||
# scanner: pip-audit # force pip-audit even when snyk is available
|
||||
severity_threshold: high # critical | high | medium | low
|
||||
fail_open_if_no_scanner: true # skip silently when neither tool present
|
||||
```
|
||||
|
||||
`severity_threshold` — only findings at or above this severity trigger
|
||||
the mode behavior (warn or block). Medium and low are always logged at
|
||||
INFO but never block.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- **Don't** set `mode: block` during initial rollout — you'll strand
|
||||
legitimate skills that have medium-severity transitive deps. Start
|
||||
in `warn`, measure, then block.
|
||||
- **Don't** install without also installing `molecule-audit` — the
|
||||
compliance value of scanning disappears if the events aren't in a
|
||||
durable log.
|
||||
- **Don't** scan the monorepo's first-party plugins. They're vetted at
|
||||
PR-review time. Repeat scanning wastes time + may trip on false
|
||||
positives.
|
||||
- **Don't** rely on this as your only supply-chain defense. It catches
|
||||
known CVEs; it does NOT catch typosquatting, malicious package
|
||||
updates, or signed-but-compromised releases. Complement with
|
||||
deterministic lockfiles + registry allowlists.
|
||||
|
||||
## Related
|
||||
|
||||
- `builtin_tools/security_scan.py` — the implementation
|
||||
- `molecule-compliance` — runtime OWASP policy; this is its supply-
|
||||
chain counterpart
|
||||
- `molecule-audit` — event retention for scan results
|
||||
- Issue #256 — the proposal that led to this plugin split
|
||||
Loading…
Reference in New Issue
Block a user