From 720e92e426dcd9aefb7a3916a3b8ed7a7efa5fc6 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Wed, 15 Apr 2026 14:15:25 -0700 Subject: [PATCH] feat(plugin): split compliance-posture into 3 plugins (#256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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-"}. Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/molecule-audit/plugin.yaml | 16 +++ .../skills/ai-act-audit-log/SKILL.md | 133 ++++++++++++++++++ plugins/molecule-compliance/plugin.yaml | 17 +++ .../skills/owasp-agentic/SKILL.md | 91 ++++++++++++ plugins/molecule-security-scan/plugin.yaml | 16 +++ .../skills/skill-cve-gate/SKILL.md | 128 +++++++++++++++++ 6 files changed, 401 insertions(+) create mode 100644 plugins/molecule-audit/plugin.yaml create mode 100644 plugins/molecule-audit/skills/ai-act-audit-log/SKILL.md create mode 100644 plugins/molecule-compliance/plugin.yaml create mode 100644 plugins/molecule-compliance/skills/owasp-agentic/SKILL.md create mode 100644 plugins/molecule-security-scan/plugin.yaml create mode 100644 plugins/molecule-security-scan/skills/skill-cve-gate/SKILL.md diff --git a/plugins/molecule-audit/plugin.yaml b/plugins/molecule-audit/plugin.yaml new file mode 100644 index 00000000..04675afa --- /dev/null +++ b/plugins/molecule-audit/plugin.yaml @@ -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 diff --git a/plugins/molecule-audit/skills/ai-act-audit-log/SKILL.md b/plugins/molecule-audit/skills/ai-act-audit-log/SKILL.md new file mode 100644 index 00000000..ba48088d --- /dev/null +++ b/plugins/molecule-audit/skills/ai-act-audit-log/SKILL.md @@ -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 diff --git a/plugins/molecule-compliance/plugin.yaml b/plugins/molecule-compliance/plugin.yaml new file mode 100644 index 00000000..c6bbe12c --- /dev/null +++ b/plugins/molecule-compliance/plugin.yaml @@ -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 diff --git a/plugins/molecule-compliance/skills/owasp-agentic/SKILL.md b/plugins/molecule-compliance/skills/owasp-agentic/SKILL.md new file mode 100644 index 00000000..eb37a244 --- /dev/null +++ b/plugins/molecule-compliance/skills/owasp-agentic/SKILL.md @@ -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 (``, `<|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 diff --git a/plugins/molecule-security-scan/plugin.yaml b/plugins/molecule-security-scan/plugin.yaml new file mode 100644 index 00000000..6521cf2d --- /dev/null +++ b/plugins/molecule-security-scan/plugin.yaml @@ -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 diff --git a/plugins/molecule-security-scan/skills/skill-cve-gate/SKILL.md b/plugins/molecule-security-scan/skills/skill-cve-gate/SKILL.md new file mode 100644 index 00000000..7cdb5955 --- /dev/null +++ b/plugins/molecule-security-scan/skills/skill-cve-gate/SKILL.md @@ -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