@aporthq/aport-agent-guardrails 1.0.8
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.
- package/LICENSE +217 -0
- package/README.md +481 -0
- package/bin/agent-guardrails +133 -0
- package/bin/aport-create-passport.sh +444 -0
- package/bin/aport-cursor-hook.sh +90 -0
- package/bin/aport-guardrail-api.sh +108 -0
- package/bin/aport-guardrail-bash.sh +394 -0
- package/bin/aport-guardrail-v2.sh +5 -0
- package/bin/aport-guardrail.sh +5 -0
- package/bin/aport-resolve-paths.sh +71 -0
- package/bin/aport-status.sh +276 -0
- package/bin/frameworks/crewai.sh +49 -0
- package/bin/frameworks/cursor.sh +95 -0
- package/bin/frameworks/langchain.sh +48 -0
- package/bin/frameworks/n8n.sh +36 -0
- package/bin/frameworks/openclaw.sh +19 -0
- package/bin/lib/allowlist.sh +18 -0
- package/bin/lib/common.sh +28 -0
- package/bin/lib/config.sh +46 -0
- package/bin/lib/constants.sh +232 -0
- package/bin/lib/detect.sh +65 -0
- package/bin/lib/error.sh +269 -0
- package/bin/lib/passport.sh +19 -0
- package/bin/lib/templates/.gitkeep +1 -0
- package/bin/lib/templates/config.yaml +6 -0
- package/bin/lib/validation.sh +206 -0
- package/bin/openclaw +660 -0
- package/docs/ADDING_A_FRAMEWORK.md +87 -0
- package/docs/AGENTS.md.example +40 -0
- package/docs/CODE_REVIEW.md +192 -0
- package/docs/DEPLOYMENT_READINESS.md +81 -0
- package/docs/FAQ_SECURITY_SCANNERS.md +373 -0
- package/docs/FRAMEWORK_ROADMAP.md +41 -0
- package/docs/HOSTED_PASSPORT_SETUP.md +362 -0
- package/docs/IMPLEMENTING_YOUR_OWN_EVALUATOR.md +433 -0
- package/docs/OPENCLAW_COMPATIBILITY.md +73 -0
- package/docs/OPENCLAW_LOCAL_INTEGRATION.md +596 -0
- package/docs/OPENCLAW_TOOLS_AND_POLICIES.md +54 -0
- package/docs/QUICKSTART.md +470 -0
- package/docs/QUICKSTART_OPENCLAW_PLUGIN.md +470 -0
- package/docs/README.md +28 -0
- package/docs/RELEASE.md +87 -0
- package/docs/REPO_LAYOUT.md +47 -0
- package/docs/SKILLS_ECOSYSTEM_ANALYSIS_FEB17.md +1260 -0
- package/docs/TOOL_POLICY_MAPPING.md +46 -0
- package/docs/UPGRADE.md +46 -0
- package/docs/VERIFICATION_METHODS.md +97 -0
- package/docs/assets/README.md +8 -0
- package/docs/assets/porter.svg +54 -0
- package/docs/development/ERROR_CODES.md +616 -0
- package/docs/frameworks/GITHUB_ISSUE_PROPOSALS.md +1105 -0
- package/docs/frameworks/crewai.md +114 -0
- package/docs/frameworks/cursor.md +159 -0
- package/docs/frameworks/langchain.md +72 -0
- package/docs/frameworks/n8n.md +40 -0
- package/docs/frameworks/openclaw.md +40 -0
- package/docs/launch/ADD_APORT_AWESOME_LISTS_INSTRUCTIONS.md +146 -0
- package/docs/launch/ANNOUNCEMENT_GUIDE.md +266 -0
- package/docs/launch/AWESOME_REPOS.md +53 -0
- package/docs/launch/CURSOR_VSCODE_HOOKS_RESEARCH.md +77 -0
- package/docs/launch/DEMO_TERMINAL_OUTPUT.txt +48 -0
- package/docs/launch/DRY_AND_PLAN_CHECKLIST.md +47 -0
- package/docs/launch/EVIDENCE_README.md +61 -0
- package/docs/launch/EVIDENCE_TERMINAL_CAPTURE.txt +10 -0
- package/docs/launch/FRAMEWORK_SUPPORT_PLAN.md +1640 -0
- package/docs/launch/LAUNCH_READINESS_CHECKLIST.md +237 -0
- package/docs/launch/LAUNCH_STRATEGY_SUMMARY.md +464 -0
- package/docs/launch/OPENCLAW_FEEDBACK_AND_FIXES.md +85 -0
- package/docs/launch/POST_1_VALENTINE_IMPROVED.md +233 -0
- package/docs/launch/POST_2_GUARDRAIL_IMPROVED.md +369 -0
- package/docs/launch/PRE_LAUNCH_FIXES.md +766 -0
- package/docs/launch/QUICK_LAUNCH_CHECKLIST.md +400 -0
- package/docs/launch/READINESS_SUMMARY.md +262 -0
- package/docs/launch/README.md +68 -0
- package/docs/launch/USER_STORIES.md +327 -0
- package/docs/launch/scripts/add-aport-awesome-pr.sh +69 -0
- package/docs/operations/MONITORING.md +588 -0
- package/docs/reviews/2026-02-18-staff-review.md +268 -0
- package/extensions/openclaw-aport/README.md +415 -0
- package/extensions/openclaw-aport/index.js +625 -0
- package/extensions/openclaw-aport/openclaw-aport.js +7 -0
- package/extensions/openclaw-aport/openclaw.plugin.json +46 -0
- package/extensions/openclaw-aport/package.json +36 -0
- package/extensions/openclaw-aport/test.js +307 -0
- package/external/aport-policies/README.md +363 -0
- package/external/aport-policies/agent.session.create.v1/README.md +345 -0
- package/external/aport-policies/agent.session.create.v1/policy.json +162 -0
- package/external/aport-policies/agent.tool.register.v1/README.md +361 -0
- package/external/aport-policies/agent.tool.register.v1/policy.json +172 -0
- package/external/aport-policies/code.release.publish.v1/README.md +51 -0
- package/external/aport-policies/code.release.publish.v1/policy.json +121 -0
- package/external/aport-policies/code.repository.merge.v1/README.md +287 -0
- package/external/aport-policies/code.repository.merge.v1/express.example.js +332 -0
- package/external/aport-policies/code.repository.merge.v1/fastapi.example.py +370 -0
- package/external/aport-policies/code.repository.merge.v1/policy.json +162 -0
- package/external/aport-policies/data.export.create.v1/README.md +226 -0
- package/external/aport-policies/data.export.create.v1/express.example.js +172 -0
- package/external/aport-policies/data.export.create.v1/fastapi.example.py +165 -0
- package/external/aport-policies/data.export.create.v1/policy.json +133 -0
- package/external/aport-policies/data.report.ingest.v1/README.md +134 -0
- package/external/aport-policies/data.report.ingest.v1/express.example.js +105 -0
- package/external/aport-policies/data.report.ingest.v1/minimal-example.js +68 -0
- package/external/aport-policies/data.report.ingest.v1/policy.json +174 -0
- package/external/aport-policies/finance.crypto.trade.v1/README.md +146 -0
- package/external/aport-policies/finance.crypto.trade.v1/express.example.js +109 -0
- package/external/aport-policies/finance.crypto.trade.v1/minimal-example.js +65 -0
- package/external/aport-policies/finance.crypto.trade.v1/policy.json +176 -0
- package/external/aport-policies/finance.payment.charge.v1/README.md +326 -0
- package/external/aport-policies/finance.payment.charge.v1/express.example.js +250 -0
- package/external/aport-policies/finance.payment.charge.v1/fastapi.example.py +227 -0
- package/external/aport-policies/finance.payment.charge.v1/minimal-example.js +64 -0
- package/external/aport-policies/finance.payment.charge.v1/policy.json +224 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.template.json +40 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/payments-charge-policy.test.js +817 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/test_payments_charge_policy.py +486 -0
- package/external/aport-policies/finance.payment.payout.v1/README.md +78 -0
- package/external/aport-policies/finance.payment.payout.v1/policy.json +181 -0
- package/external/aport-policies/finance.payment.refund.v1/README.md +275 -0
- package/external/aport-policies/finance.payment.refund.v1/express.example.js +167 -0
- package/external/aport-policies/finance.payment.refund.v1/fastapi.example.py +136 -0
- package/external/aport-policies/finance.payment.refund.v1/minimal-example.js +183 -0
- package/external/aport-policies/finance.payment.refund.v1/policy.json +216 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/refunds-policy.test.js +924 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/test_refunds_policy.py +778 -0
- package/external/aport-policies/finance.transaction.execute.v1/README.md +309 -0
- package/external/aport-policies/finance.transaction.execute.v1/express.example.js +261 -0
- package/external/aport-policies/finance.transaction.execute.v1/fastapi.example.py +231 -0
- package/external/aport-policies/finance.transaction.execute.v1/minimal-example.js +78 -0
- package/external/aport-policies/finance.transaction.execute.v1/policy.json +189 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.template.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/test_transactions_policy.py +214 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/transactions-policy.test.js +306 -0
- package/external/aport-policies/governance.data.access.v1/README.md +292 -0
- package/external/aport-policies/governance.data.access.v1/express.example.js +321 -0
- package/external/aport-policies/governance.data.access.v1/fastapi.example.py +279 -0
- package/external/aport-policies/governance.data.access.v1/minimal-example.js +65 -0
- package/external/aport-policies/governance.data.access.v1/policy.json +208 -0
- package/external/aport-policies/governance.data.access.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/data-access-policy.test.js +308 -0
- package/external/aport-policies/governance.data.access.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.instance.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.template.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/test_data_access_policy.py +214 -0
- package/external/aport-policies/legal.contract.review.v1/README.md +109 -0
- package/external/aport-policies/legal.contract.review.v1/policy.json +378 -0
- package/external/aport-policies/legal.contract.review.v1/tests/legal-contract-review-policy.test.js +609 -0
- package/external/aport-policies/legal.contract.review.v1/tests/passport.template.json +49 -0
- package/external/aport-policies/mcp.tool.execute.v1/README.md +301 -0
- package/external/aport-policies/mcp.tool.execute.v1/policy.json +141 -0
- package/external/aport-policies/messaging.message.send.v1/README.md +230 -0
- package/external/aport-policies/messaging.message.send.v1/express.example.js +183 -0
- package/external/aport-policies/messaging.message.send.v1/fastapi.example.py +193 -0
- package/external/aport-policies/messaging.message.send.v1/policy.json +144 -0
- package/external/aport-policies/policy-template.json +107 -0
- package/external/aport-policies/system.command.execute.v1/README.md +275 -0
- package/external/aport-policies/system.command.execute.v1/policy.json +146 -0
- package/external/aport-spec/CONTRIBUTING.md +273 -0
- package/external/aport-spec/LICENSE +21 -0
- package/external/aport-spec/README.md +168 -0
- package/external/aport-spec/conformance/README.md +294 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/allow_users.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/deny_pii.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/allow_users.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/deny_pii.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/passports/template.json +29 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/allow_50usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_150usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_currency.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/allow_50usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_150usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_currency.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/passports/template.json +42 -0
- package/external/aport-spec/conformance/package.json +44 -0
- package/external/aport-spec/conformance/pnpm-lock.yaml +642 -0
- package/external/aport-spec/conformance/src/cases.ts +371 -0
- package/external/aport-spec/conformance/src/ed25519.ts +167 -0
- package/external/aport-spec/conformance/src/jcs.ts +85 -0
- package/external/aport-spec/conformance/src/runner.ts +533 -0
- package/external/aport-spec/conformance/src/validators.ts +185 -0
- package/external/aport-spec/conformance/test-runner.js +315 -0
- package/external/aport-spec/conformance/tsconfig.json +21 -0
- package/external/aport-spec/error-schema.json +192 -0
- package/external/aport-spec/index.json +12 -0
- package/external/aport-spec/integrations/clawmoat/README.md +12 -0
- package/external/aport-spec/integrations/shield/README.md +245 -0
- package/external/aport-spec/integrations/shield/adapters/index.js +116 -0
- package/external/aport-spec/integrations/shield/adapters/system-command-execute.js +133 -0
- package/external/aport-spec/integrations/shield/test/README.md +58 -0
- package/external/aport-spec/integrations/shield/test/shield.md +40 -0
- package/external/aport-spec/integrations/shield/test/test-shield-to-verify.js +274 -0
- package/external/aport-spec/metrics-schema.json +504 -0
- package/external/aport-spec/oap/CHANGELOG.md +54 -0
- package/external/aport-spec/oap/VERSION.md +40 -0
- package/external/aport-spec/oap/capability-registry.md +229 -0
- package/external/aport-spec/oap/conformance.md +257 -0
- package/external/aport-spec/oap/decision-schema.json +114 -0
- package/external/aport-spec/oap/examples/context.refund.usd.50.json +9 -0
- package/external/aport-spec/oap/examples/decision.allow.sample.json +20 -0
- package/external/aport-spec/oap/examples/decision.deny.sample.json +23 -0
- package/external/aport-spec/oap/examples/passport.instance.v1.json +50 -0
- package/external/aport-spec/oap/examples/passport.template.v1.json +71 -0
- package/external/aport-spec/oap/oap-spec.md +426 -0
- package/external/aport-spec/oap/passport-schema.json +396 -0
- package/external/aport-spec/oap/security.md +213 -0
- package/external/aport-spec/oap/vc/context-oap-v1.jsonld +137 -0
- package/external/aport-spec/oap/vc/examples/oap-decision-vc.json +37 -0
- package/external/aport-spec/oap/vc/examples/oap-passport-vc.json +68 -0
- package/external/aport-spec/oap/vc/tools/INTEGRATION.md +375 -0
- package/external/aport-spec/oap/vc/tools/README.md +278 -0
- package/external/aport-spec/oap/vc/tools/examples/decision-to-vc.js +66 -0
- package/external/aport-spec/oap/vc/tools/examples/passport-to-vc.js +83 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-decision.js +77 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-passport.js +94 -0
- package/external/aport-spec/oap/vc/tools/package.json +38 -0
- package/external/aport-spec/oap/vc/tools/pnpm-lock.yaml +472 -0
- package/external/aport-spec/oap/vc/tools/src/cli.ts +226 -0
- package/external/aport-spec/oap/vc/tools/src/crypto-utils.ts +427 -0
- package/external/aport-spec/oap/vc/tools/src/index.ts +653 -0
- package/external/aport-spec/oap/vc/tools/src/test.ts +148 -0
- package/external/aport-spec/oap/vc/tools/src/vp.ts +382 -0
- package/external/aport-spec/oap/vc/tools/test-simple.js +214 -0
- package/external/aport-spec/oap/vc/tools/tsconfig.json +19 -0
- package/external/aport-spec/oap/vc/vc-mapping.md +443 -0
- package/external/aport-spec/passport-schema.json +586 -0
- package/external/aport-spec/rate-limiting.md +136 -0
- package/external/aport-spec/transport-profile.md +325 -0
- package/external/aport-spec/webhook-spec.md +314 -0
- package/package.json +70 -0
- package/skills/aport-agent-guardrail/SKILL.md +314 -0
- package/src/evaluator.js +252 -0
- package/src/server/index.js +72 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# SHIELD Integration (Draft)
|
|
2
|
+
|
|
3
|
+
_Last updated: 2026-02-17_
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
**SHIELD.md** ([canonical spec](https://nova-hunting.github.io/shield.md/)) defines a structured threat feed for AI agents: threat entries with id, fingerprint, category, severity, confidence, action, and `recommendation_agent` matching logic. Enforcement is exactly one of `log`, `require_approval`, or `block`. Version 0 is context-loaded (advisory); [v1 keeps the same threat shape and moves enforcement outside the LLM](https://nova-hunting.github.io/shield.md/#upgrade).
|
|
8
|
+
|
|
9
|
+
**Open Agent Passport (OAP) v1.0** ([spec](../../oap/oap-spec.md)) provides the runtime authorization layer SHIELD v1 envisions:
|
|
10
|
+
- Passports (W3C VC/DID-aligned) describe agent capabilities and limits.
|
|
11
|
+
- Policy packs define evaluation rules; **limits** (e.g. allowlists, blocked patterns, threat data) live in the **passport** under `limits.{capability}`.
|
|
12
|
+
- APort guardrails evaluate policy packs against passport + context and enforce deterministically (e.g. `before_tool_call` in OpenClaw).
|
|
13
|
+
|
|
14
|
+
This integration treats **SHIELD threat intelligence as an input to OAP**: SHIELD content (whether from community feeds or user-defined `shield.md` rules) is imported into OAP policy packs and passport limits so the same deterministic, signed enforcement applies.
|
|
15
|
+
|
|
16
|
+
**OAP is the authorization standard. SHIELD is threat intelligence.**
|
|
17
|
+
|
|
18
|
+
- **SHIELD:** A format for threat definitions. Content can be **feeds** (community- or vendor-curated lists you import) or **user-defined rules** (a `shield.md` your team authors). Either way it’s easy for security teams to maintain.
|
|
19
|
+
- **OAP:** W3C-compliant authorization (crypto signatures, audit trails)
|
|
20
|
+
- **This adapter:** Translates SHIELD → OAP for deterministic enforcement
|
|
21
|
+
|
|
22
|
+
**Analogy:** SHIELD is like a CVE database (or your own rule set in that format); OAP is the firewall that enforces rules based on that intel. This adapter is the import layer.
|
|
23
|
+
|
|
24
|
+
See [_plan/execution/openclaw/SHIELD_POWER_ANALYSIS_CORRECTED.md](../../../_plan/execution/openclaw/SHIELD_POWER_ANALYSIS_CORRECTED.md).
|
|
25
|
+
|
|
26
|
+
## Goals
|
|
27
|
+
|
|
28
|
+
- **Spec alignment:** Map the [canonical SHIELD v0/v1](https://nova-hunting.github.io/shield.md/) threat model onto OAP: passport `limits.{capability}.shield` and policy pack `evaluation_rules`.
|
|
29
|
+
- **Importer tooling:** CLI to ingest `shield.md` and emit (1) a limits fragment for passports and (2) policy pack updates (e.g. a SHIELD evaluation rule) so guardrails can enforce without hand-editing JSON.
|
|
30
|
+
- **Confidence semantics:** Preserve SHIELD’s rule: ≥0.85 confidence → enforce as-is; <0.85 → treat as `require_approval` unless action is `block` and severity is `critical`.
|
|
31
|
+
- **Multi-source feeds:** SHIELD alongside ClawMoat, CVE, and custom rules, all under the same OAP schema and enforcement.
|
|
32
|
+
|
|
33
|
+
## SHIELD v0 Reference (canonical)
|
|
34
|
+
|
|
35
|
+
- **Scopes:** `prompt`, `skill.install`, `skill.execute`, `tool.call`, `network.egress`, `secrets.read`, `mcp`
|
|
36
|
+
- **Threat categories:** `prompt`, `tool`, `mcp`, `memory`, `supply_chain`, `vulnerability`, `fraud`, `policy_bypass`, `anomaly`, `skill`, `other`
|
|
37
|
+
- **Actions:** `log` | `require_approval` | `block` (only these three)
|
|
38
|
+
- **Eligibility:** threat is eligible only if `revoked` is false, `revoked_at` is null, and current time is before `expires_at`
|
|
39
|
+
- **Decision block:** Before gated events, emit a decision with `action`, `scope`, `threat_id`, `fingerprint`, `matched_on`, `match_value`, `reason`
|
|
40
|
+
- **Block response (exact):** `Blocked. Threat matched: <threat_id>. Match: <matched_on>=<match_value>.`
|
|
41
|
+
- **recommendation_agent (v0):** Case-sensitive directives `BLOCK:`, `APPROVE:`, `LOG:` with conditions: skill name equals/contains, outbound request to domain/url, secrets read path equals, file path equals; operator OR. Normalization: domains lowercase, no trailing dot; URLs prefix match.
|
|
42
|
+
|
|
43
|
+
## Scope → OAP capability mapping
|
|
44
|
+
|
|
45
|
+
| SHIELD scope | OAP capability / policy pack (example) | Implemented in adapter |
|
|
46
|
+
|---------------------|----------------------------------------|-------------------------|
|
|
47
|
+
| `prompt` | e.g. `prompt.guardrail` | No (OAP supports when policy pack exists) |
|
|
48
|
+
| `skill.install` | Skill policy | No |
|
|
49
|
+
| `skill.execute` | Skill policy | No |
|
|
50
|
+
| `tool.call` | `system.command.execute` ([policy](../../../policies/system.command.execute.v1/policy.json)) | **Yes** |
|
|
51
|
+
| `network.egress` | Network policy | No |
|
|
52
|
+
| `secrets.read` | Secrets policy | No |
|
|
53
|
+
| `mcp` | `mcp.tool.execute` ([policy](../../../policies/mcp.tool.execute.v1/policy.json)) | No (OAP supports; adapter can be extended) |
|
|
54
|
+
|
|
55
|
+
When importing, `threat.category` and SHIELD scope determine which OAP capability (and thus which policy pack and `limits.{capability}`) the threat is attached to. Each capability already has its own policy pack (e.g. `system.command.execute.v1` → `policies/system.command.execute.v1/policy.json`). We do not create new policy files per scope; **within each existing policy pack, we embed SHIELD threats under `limits.{capability}.shield`** (passport limits for that capability).
|
|
56
|
+
|
|
57
|
+
## Strategy for multiple SHIELD scopes
|
|
58
|
+
|
|
59
|
+
**Why doesn’t the adapter support every scope today?** Each SHIELD scope has different **context** and **recommendation_agent** conditions:
|
|
60
|
+
|
|
61
|
+
- `tool.call` → context: command, args; conditions: “command contains X” → we map to `blocked_patterns` and an OAP policy for `system.command.execute`.
|
|
62
|
+
- `mcp` → context: server, tool; conditions: domain/url, skill name, etc. → would need a different limits shape and policy pack (e.g. `mcp.tool.execute`).
|
|
63
|
+
- `skill.execute` → context: skill name, etc.; conditions: “skill name equals/contains” → would need a skill policy pack and condition parser.
|
|
64
|
+
- `network.egress`, `secrets.read`, `prompt` → each has its own context and conditions in the [SHIELD spec](https://nova-hunting.github.io/shield.md/).
|
|
65
|
+
|
|
66
|
+
**Do we have to implement each scope manually?** Yes. Adding a scope means: (1) **scope → OAP capability** (and policy pack shape), (2) **parsing that scope’s recommendation_agent conditions** (e.g. “skill name contains X” → limits or conditions), (3) **outputting the right policy + limits** for that capability. The adapter is a reference implementation; each scope is a small, well-defined addition (new template + condition parser).
|
|
67
|
+
|
|
68
|
+
**Generated on the fly vs stored in policies/?** The adapter **generates** policy and limits on the fly from `shield.md`; nothing is hardcoded in the repo as the only source. For scopes that already have an APort policy pack (e.g. `policies/system.command.execute.v1/`, `policies/mcp.tool.execute.v1/`), we reuse that **shape** (required_context, evaluation_rules) in the adapter so the output is verifier-compatible. Each capability already maps to its policy pack; we embed SHIELD threats inside those packs under limits.{capability}.shield. So: **each SHIELD scope becomes an OAP policy (capability + rules); that policy is generated by the adapter from the feed, not stored as a static file.** The existing `policies/*` in the repo are canonical shapes the adapter can mirror; they are not required to exist for the adapter to emit a valid pack for a new scope.
|
|
69
|
+
|
|
70
|
+
## Field mapping (SHIELD → OAP)
|
|
71
|
+
|
|
72
|
+
Each OAP capability already has its own policy pack (e.g. `policies/system.command.execute.v1/policy.json`). SHIELD threat fields are stored under **passport limits** at `limits.{capability}.shield.threats[]` for that capability - i.e. within each existing policy pack’s limits, we embed SHIELD under `limits.{capability}.shield`; we do not create new policy files per scope. The **policy pack** adds an evaluation rule (e.g. type `shield` or custom_validator `validateShieldThreats`) that reads this structure and enforces SHIELD semantics.
|
|
73
|
+
|
|
74
|
+
| SHIELD field (v0) | OAP placement (passport limits) | Notes |
|
|
75
|
+
|----------------------|-----------------------------------------------------|-------|
|
|
76
|
+
| `id` | `limits.{capability}.shield.threats[].id` | Prefixed `shield_` to avoid collisions (e.g. `shield_T001`). |
|
|
77
|
+
| `fingerprint` | `limits.{capability}.shield.threats[].fingerprint` | Optional; dedup and audit. |
|
|
78
|
+
| `category` | `limits.{capability}.shield.threats[].category` | Must align with SHIELD categories; used for scope matching. |
|
|
79
|
+
| `severity` | `limits.{capability}.shield.threats[].severity` | Drives priority when multiple threats match. |
|
|
80
|
+
| `confidence` | `limits.{capability}.shield.threats[].confidence` | ≥0.85 → enforce; <0.85 → require_approval unless block+critical. |
|
|
81
|
+
| `action` | `limits.{capability}.shield.threats[].action` | `log` \| `require_approval` \| `block`. |
|
|
82
|
+
| `title` | `limits.{capability}.shield.threats[].title` | Short label; optional `description` for body. |
|
|
83
|
+
| `recommendation_agent` | `limits.{capability}.shield.threats[].recommendation_agent` or parsed into `conditions` | BLOCK/APPROVE/LOG + conditions; see below. |
|
|
84
|
+
| `expires_at` | `limits.{capability}.shield.threats[].expires_at` | Omit or mark inactive when expired. |
|
|
85
|
+
| `revoked` / `revoked_at` | `limits.{capability}.shield.threats[].revoked` (and optional `revoked_at`) | Exclude revoked threats from evaluation. |
|
|
86
|
+
|
|
87
|
+
### recommendation_agent → conditions
|
|
88
|
+
|
|
89
|
+
SHIELD v0 conditions map to structured conditions used by the guardrail:
|
|
90
|
+
|
|
91
|
+
- **skill name equals <value>** → e.g. `conditions.skill.equals` or `conditions.skill.name`
|
|
92
|
+
- **skill name contains <value>** → e.g. `conditions.skill.contains`
|
|
93
|
+
- **outbound request to <domain>** → e.g. `conditions.domain` (normalized: lowercase, no trailing dot)
|
|
94
|
+
- **outbound request to <url_prefix>** → e.g. `conditions.url_prefix`
|
|
95
|
+
- **secrets read path equals <value>** → e.g. `conditions.secret.path`
|
|
96
|
+
- **file path equals <value>** → e.g. `conditions.file.path`
|
|
97
|
+
|
|
98
|
+
BLOCK → enforce as block; APPROVE → require_approval; LOG → log. Multiple conditions may be combined with OR as in the spec.
|
|
99
|
+
|
|
100
|
+
### Resulting passport limits shape (per capability)
|
|
101
|
+
|
|
102
|
+
Imported SHIELD threats for a given capability live under the passport’s limits, e.g. for `system.command.execute`:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"limits": {
|
|
107
|
+
"system.command.execute": {
|
|
108
|
+
"allowed_commands": ["npm", "node", "git"],
|
|
109
|
+
"blocked_patterns": ["rm -rf", "sudo"],
|
|
110
|
+
"max_execution_time": 300,
|
|
111
|
+
"shield": {
|
|
112
|
+
"threats": [
|
|
113
|
+
{
|
|
114
|
+
"id": "shield_T001",
|
|
115
|
+
"fingerprint": "optional-hash",
|
|
116
|
+
"category": "tool",
|
|
117
|
+
"severity": "critical",
|
|
118
|
+
"confidence": 0.97,
|
|
119
|
+
"action": "block",
|
|
120
|
+
"title": "Destructive shell commands",
|
|
121
|
+
"recommendation_agent": "BLOCK: command contains rm -rf OR command contains sudo",
|
|
122
|
+
"conditions": {
|
|
123
|
+
"patterns": { "blocked": ["rm -rf", "sudo"] }
|
|
124
|
+
},
|
|
125
|
+
"description": "Block destructive shell commands",
|
|
126
|
+
"expires_at": null,
|
|
127
|
+
"revoked": false
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Policy packs that support SHIELD add an evaluation rule that:
|
|
137
|
+
1. Reads `limits.{capability}.shield.threats` (for the capability the policy governs).
|
|
138
|
+
2. Filters to eligible threats (not revoked, not expired).
|
|
139
|
+
3. Matches using `recommendation_agent` / conditions first, then explicit string match; applies confidence downgrade rule; resolves multiple matches as block > require_approval > log.
|
|
140
|
+
4. For deny: returns OAP deny with a reason that follows SHIELD’s block format where applicable: `Blocked. Threat matched: <id>. Match: <matched_on>=<match_value>.`
|
|
141
|
+
|
|
142
|
+
Example evaluation rule in the **policy pack** (conceptual; OAP today supports `expression` and `custom_validator` - either add `type: "shield"` or implement as a custom_validator such as `validateShieldThreats`):
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"name": "shield_enforcement",
|
|
147
|
+
"type": "shield",
|
|
148
|
+
"shield_ref": "limits.system.command.execute.shield",
|
|
149
|
+
"deny_code": "oap.shield_blocked",
|
|
150
|
+
"description": "Evaluate SHIELD threats for this capability"
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Expression rules in OAP have access to `passport`, `context`, and `limits`; a SHIELD rule would use `limits.system.command.execute.shield` (or the capability implied by the policy).
|
|
155
|
+
|
|
156
|
+
### Confidence downgrade logic
|
|
157
|
+
|
|
158
|
+
Same as SHIELD v0:
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
const shouldEnforce = threat.confidence >= 0.85 || (threat.action === "block" && threat.severity === "critical");
|
|
162
|
+
const effectiveAction = shouldEnforce ? threat.action : "require_approval";
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Implementation vs canonical SHIELD spec
|
|
166
|
+
|
|
167
|
+
The [canonical SHIELD v0 spec](https://nova-hunting.github.io/shield.md/) defines scopes, threat fields, enforcement states, decision block format, and `recommendation_agent` syntax. This integration implements the following:
|
|
168
|
+
|
|
169
|
+
| Spec element | Implemented | Notes |
|
|
170
|
+
|--------------|-------------|--------|
|
|
171
|
+
| **Scope** | `tool.call` only | Adapter outputs policy + limits for `system.command.execute`. Other scopes (prompt, skill.install, skill.execute, network.egress, secrets.read, mcp) are not yet implemented in the adapter; OAP supports them when corresponding policy packs exist. |
|
|
172
|
+
| **Active threats (compressed)** | Yes | Parsed: id, fingerprint, category, severity, confidence, action, title, recommendation_agent, expires_at, revoked. |
|
|
173
|
+
| **Match eligibility** | Partially | Revoked threats excluded in adapter. Evaluator can filter by expires_at and apply confidence threshold. |
|
|
174
|
+
| **Confidence threshold** | In evaluator | ≥0.85 enforce; <0.85 → require_approval unless block+critical. Can be applied when evaluating `limits.{capability}.shield.threats`. |
|
|
175
|
+
| **Enforcement states** | Yes | log, require_approval, block; BLOCK/APPROVE/LOG in recommendation_agent. |
|
|
176
|
+
| **recommendation_agent** | Subset | **Implemented:** BLOCK/APPROVE with “command contains X” (and OR) for tool scope. **Not yet:** skill name equals/contains, outbound request to domain/url, secrets read path, file path. |
|
|
177
|
+
| **Block response format** | Optional | Spec requires exact: `Blocked. Threat matched: <threat_id>. Match: <matched_on>=<match_value>.` OAP deny reason can follow this when using a SHIELD-specific evaluator. |
|
|
178
|
+
| **Decision block** | Via OAP | SHIELD’s DECISION block (action, scope, threat_id, …) is reflected in OAP’s allow/deny decision + reasons; scope maps to capability. |
|
|
179
|
+
|
|
180
|
+
## Alignment with OpenClaw and APort spec
|
|
181
|
+
|
|
182
|
+
- **[OpenClaw #12385](https://github.com/openclaw/openclaw/issues/12385)** – Adds an optional SHIELD policy layer that runs **before** sensitive events (skill install/execute, tool call, MCP, network, secrets). Our adapter produces the OAP policy pack and limits fragment that a guardrail uses to enforce that layer; the “before_tool_call” (and other gates) call the same OAP verifier. So: **this repo = SHIELD → OAP import; OpenClaw = where that OAP policy is enforced at runtime** (e.g. before_tool_call). **To point the thread here:** post the drafted reply in [ISSUE_REPLY_DRAFT.md](ISSUE_REPLY_DRAFT.md) as a comment on the issue.
|
|
183
|
+
- **[aporthq/aport-spec discussion #19](https://github.com/aporthq/aport-spec/discussions/19)** – Proposes SHIELD threat intelligence import into OAP policy packs, CLI `shield-to-oap`, and multi-source threat intel. This adapter is the reference implementation of that import path; field mapping and `limits.{capability}.shield` shape align with the discussion. **Next steps:** post the summary in [DISCUSSION_SUMMARY.md](DISCUSSION_SUMMARY.md) to the discussion, gather feedback, and link the public discussion from this README once it's live.
|
|
184
|
+
|
|
185
|
+
APort **supports** policy-in-body verify and the full OAP stack today; the “future” piece is only optional **packaging** of the adapter as `npx @aporthq/agent-guardrails shield import shield.md` for distribution.
|
|
186
|
+
|
|
187
|
+
## Reference adapter
|
|
188
|
+
|
|
189
|
+
**CLI status:** The packaged CLI (`npx @aporthq/agent-guardrails shield import shield.md`) is **not yet released - CLI implementation in progress.** Use the in-repo script below until it ships.
|
|
190
|
+
|
|
191
|
+
The reference implementation lives in **`adapters/`**:
|
|
192
|
+
|
|
193
|
+
- **[adapters/index.js](adapters/index.js)** – Imports each scope adapter, exposes `convert(shieldPath)`, and doubles as a script: `node adapters/index.js [shield.md]` outputs JSON (policy, limitsFragment, threats). Default input: `test/shield.md`.
|
|
194
|
+
- **Current scope:** **tool.call** → [adapters/system-command-execute.js](adapters/system-command-execute.js) (OAP `system.command.execute`). Add more scopes by adding an adapter file and registering it in `index.js`.
|
|
195
|
+
- **`test/`** – Test folder: fixture [test/shield.md](test/shield.md) and integration test [test/test-shield-to-verify.js](test/test-shield-to-verify.js) (conversion, APort verify, response shape).
|
|
196
|
+
- **Demo:** A **<2 min demo GIF** showing “malicious tool blocked” (e.g. Cisco scenario). Placeholder until recorded.
|
|
197
|
+
|
|
198
|
+
See [test/README.md](test/README.md) for run instructions and test coverage.
|
|
199
|
+
|
|
200
|
+
## Workstreams
|
|
201
|
+
|
|
202
|
+
1. **Spec (this doc):** Finalize mapping, scope→capability table, and `limits.{capability}.shield` shape; document in [aport-spec discussion #19](https://github.com/aporthq/aport-spec/discussions/19).
|
|
203
|
+
2. **Importer:** [adapters/index.js](adapters/index.js) is the reference implementation (run as CLI: `node adapters/index.js [shield.md]`). Optional packaging as `npx @aporthq/agent-guardrails shield import shield.md` for distribution. It parses `shield.md` (threat blocks between ---), maps to `limits.{capability}.shield.threats[]` and an OAP policy pack (e.g. tool.call → system.command.execute).
|
|
204
|
+
3. **Demo and docs:** Example `shield.md`, resulting limits fragment, policy pack snippet, guardrail log excerpt; **<2 min demo GIF** (malicious tool blocked, e.g. Cisco scenario).
|
|
205
|
+
|
|
206
|
+
## Roadmap
|
|
207
|
+
|
|
208
|
+
| Phase | Target | Deliverables |
|
|
209
|
+
|----------------|--------|--------------|
|
|
210
|
+
| Spec alignment | Feb 20 | Final mapping (this doc), scope→capability table, publish summary and open questions in [discussion #19](https://github.com/aporthq/aport-spec/discussions/19). |
|
|
211
|
+
| Importer + tests | Feb 28 | **CLI implementation in progress.** Target: packaged `shield-to-oap` CLI, fixtures, README + demo, guardrail integration tests. In-repo: `node adapters/index.js [shield.md]` works today. |
|
|
212
|
+
| Launch & outreach | Mar 5 | OpenClaw PR/docs, Discord, contribution guide for SHIELD feeds, joint messaging with ClawMoat. |
|
|
213
|
+
|
|
214
|
+
**Publishable state:** Once the discussion summary is posted to [discussion #19](https://github.com/aporthq/aport-spec/discussions/19), the drafted issue reply is posted to [OpenClaw #12385](https://github.com/openclaw/openclaw/issues/12385), and an initial ClawMoat schema is added, the spec folder is in a **publishable state** even before the packaged CLI ships.
|
|
215
|
+
|
|
216
|
+
## Open questions (for discussion #19)
|
|
217
|
+
|
|
218
|
+
1. Store SHIELD threats under `limits.{capability}.shield` (as above) or introduce a top-level `threat_feeds` section?
|
|
219
|
+
2. Support bidirectional export (OAP limits + rules → SHIELD markdown) for round-trip editing?
|
|
220
|
+
3. Conflict resolution when multiple feeds define the same capability/condition: last-write-wins vs explicit priorities?
|
|
221
|
+
4. How to represent `recommendation_agent` natural-language fragments that don’t map to explicit strings (heuristics vs structured conditions only)?
|
|
222
|
+
|
|
223
|
+
## Compatibility with APort
|
|
224
|
+
|
|
225
|
+
This integration is compatible with **APort’s policy verifier** and the OAP authorization stack: policy packs that include SHIELD threat data (via the mapping above) are evaluated by the same verifier that enforces all OAP policies - deterministic evaluation, cryptographically signed allow/deny decisions, compliance-grade audit trail, and sub-100ms decisions. SHIELD threat intelligence is one input to that standard; the authorization standard remains OAP. This spec (and the broader `spec/integrations` direction) sets the standard for how threat feeds plug into OAP and positions APort as the authorization layer, consistent with [discussion #19](https://github.com/aporthq/aport-spec/discussions/19).
|
|
226
|
+
|
|
227
|
+
## Open Questions for Discussion #19
|
|
228
|
+
1. Should we support bidirectional export (OAP policy pack → SHIELD markdown) for round-trip editing?
|
|
229
|
+
2. Conflict resolution: when multiple feeds define the same capability/condition, should latest
|
|
230
|
+
entry win, or should we require explicit priorities?
|
|
231
|
+
1. How should `recommendation_agent` directives that rely on natural-language descriptions be
|
|
232
|
+
represented (e.g., heuristics vs explicit strings)?
|
|
233
|
+
|
|
234
|
+
## Test and adapters
|
|
235
|
+
|
|
236
|
+
The [test/](test/) folder contains the test fixture (`shield.md`) and integration test (`test-shield-to-verify.js`). The [adapters/](adapters/) folder contains one adapter per scope: [index.js](adapters/index.js) imports them (currently [system-command-execute.js](adapters/system-command-execute.js) for tool.call). Flow: **shield.md → adapters → policy + limits → verify**. See [test/README.md](test/README.md) for run instructions.
|
|
237
|
+
|
|
238
|
+
## References
|
|
239
|
+
|
|
240
|
+
- **SHIELD spec (canonical):** [shield.md](https://nova-hunting.github.io/shield.md/) and [Upgrade path to v1](https://nova-hunting.github.io/shield.md/#upgrade)
|
|
241
|
+
- **OAP spec:** [spec/oap/oap-spec.md](../../oap/oap-spec.md)
|
|
242
|
+
- **Policy packs:** [policies/README.md](../../../policies/README.md), e.g. [system.command.execute.v1](../../../policies/system.command.execute.v1/policy.json), [mcp.tool.execute.v1](../../../policies/mcp.tool.execute.v1/policy.json)
|
|
243
|
+
- **OpenClaw issue (SHIELD policy layer):** [openclaw/openclaw#12385](https://github.com/openclaw/openclaw/issues/12385) - draft reply: [ISSUE_REPLY_DRAFT.md](ISSUE_REPLY_DRAFT.md)
|
|
244
|
+
- **OAP spec discussion (SHIELD import):** [aporthq/aport-spec discussions #19](https://github.com/aporthq/aport-spec/discussions/19) - draft summary: [DISCUSSION_SUMMARY.md](DISCUSSION_SUMMARY.md)
|
|
245
|
+
- **Positioning (OAP = standard, SHIELD = threat intel):** [_plan/execution/openclaw/SHIELD_POWER_ANALYSIS_CORRECTED.md](../../../_plan/execution/openclaw/SHIELD_POWER_ANALYSIS_CORRECTED.md)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SHIELD → OAP adapters: one per scope.
|
|
5
|
+
* index.js imports each scope adapter and exposes convert(shieldPath).
|
|
6
|
+
* When run as CLI: node adapters/index.js [shield.md] → JSON (policy, limitsFragment, threats).
|
|
7
|
+
*
|
|
8
|
+
* @see https://nova-hunting.github.io/shield.md/
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const path = require("path");
|
|
13
|
+
|
|
14
|
+
const DEFAULT_INPUT = path.join(__dirname, "..", "test", "shield.md");
|
|
15
|
+
|
|
16
|
+
const systemCommandExecute = require("./system-command-execute.js");
|
|
17
|
+
|
|
18
|
+
const SCOPE_ADAPTERS = {
|
|
19
|
+
"tool.call": systemCommandExecute,
|
|
20
|
+
// future: "mcp": mcpToolExecute, "skill.execute": skillExecute, ...
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parse SHIELD markdown: extract threat blocks (YAML between ---).
|
|
25
|
+
* Returns array of threat objects (Active threats compressed fields).
|
|
26
|
+
*/
|
|
27
|
+
function parseShieldMd(content) {
|
|
28
|
+
const threats = [];
|
|
29
|
+
const blockRe = /^---\s*\n([\s\S]*?)\n---/gm;
|
|
30
|
+
let m;
|
|
31
|
+
while ((m = blockRe.exec(content)) !== null) {
|
|
32
|
+
const yaml = m[1].trim();
|
|
33
|
+
if (!yaml) continue;
|
|
34
|
+
const entry = {};
|
|
35
|
+
for (const line of yaml.split("\n")) {
|
|
36
|
+
const colon = line.indexOf(":");
|
|
37
|
+
if (colon === -1) continue;
|
|
38
|
+
const key = line.slice(0, colon).trim();
|
|
39
|
+
const value = line
|
|
40
|
+
.slice(colon + 1)
|
|
41
|
+
.trim()
|
|
42
|
+
.replace(/^["']|["']$/g, "");
|
|
43
|
+
entry[key] = value;
|
|
44
|
+
}
|
|
45
|
+
if (entry.id) threats.push(entry);
|
|
46
|
+
}
|
|
47
|
+
return threats;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Map threat.category to SHIELD scope for adapter selection.
|
|
52
|
+
*/
|
|
53
|
+
function categoryToScope(category) {
|
|
54
|
+
const map = {
|
|
55
|
+
tool: "tool.call",
|
|
56
|
+
mcp: "mcp",
|
|
57
|
+
prompt: "prompt",
|
|
58
|
+
skill: "skill.execute",
|
|
59
|
+
memory: "memory",
|
|
60
|
+
supply_chain: "supply_chain",
|
|
61
|
+
vulnerability: "vulnerability",
|
|
62
|
+
fraud: "fraud",
|
|
63
|
+
policy_bypass: "policy_bypass",
|
|
64
|
+
anomaly: "anomaly",
|
|
65
|
+
other: "other",
|
|
66
|
+
};
|
|
67
|
+
return map[category] || "tool.call";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert shield.md to OAP policy + limits using scope adapters.
|
|
72
|
+
* Currently only tool.call (system.command.execute) is implemented;
|
|
73
|
+
* threats with category "tool" are passed to that adapter. Other categories
|
|
74
|
+
* are ignored until adapters are added.
|
|
75
|
+
*/
|
|
76
|
+
function convert(shieldPath) {
|
|
77
|
+
const content = fs.readFileSync(shieldPath, "utf8");
|
|
78
|
+
const threats = parseShieldMd(content);
|
|
79
|
+
const toolThreats = threats.filter((t) => categoryToScope(t.category || "tool") === "tool.call");
|
|
80
|
+
const adapter = SCOPE_ADAPTERS["tool.call"];
|
|
81
|
+
const { policy, limitsFragment } = adapter.convert(toolThreats);
|
|
82
|
+
return { policy, limitsFragment, threats };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function main() {
|
|
86
|
+
const inputPath = process.argv[2] || DEFAULT_INPUT;
|
|
87
|
+
if (!fs.existsSync(inputPath)) {
|
|
88
|
+
console.error("File not found:", inputPath);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
const { policy, limitsFragment, threats } = convert(inputPath);
|
|
92
|
+
if (process.env.SHIELD_OUTPUT === "limits") {
|
|
93
|
+
console.log(JSON.stringify(limitsFragment, null, 2));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (process.env.SHIELD_OUTPUT === "policy") {
|
|
97
|
+
console.log(JSON.stringify(policy, null, 2));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
console.log(JSON.stringify({ policy, limitsFragment, threats }, null, 2));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (require.main === module) {
|
|
104
|
+
main();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
convert,
|
|
109
|
+
parseShieldMd,
|
|
110
|
+
getAdapterForScope(scope) {
|
|
111
|
+
return SCOPE_ADAPTERS[scope] || null;
|
|
112
|
+
},
|
|
113
|
+
adapters: {
|
|
114
|
+
systemCommandExecute,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHIELD scope adapter: tool.call → OAP system.command.execute
|
|
3
|
+
*
|
|
4
|
+
* Parses recommendation_agent for "command contains X" (BLOCK/APPROVE).
|
|
5
|
+
* Outputs OAP policy pack + limits fragment for system.command.execute.
|
|
6
|
+
*
|
|
7
|
+
* @see https://nova-hunting.github.io/shield.md/
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// OAP policy pack shape for system.command.execute (SHIELD scope: tool.call)
|
|
11
|
+
const POLICY = {
|
|
12
|
+
id: "shield.system.command.execute.v1",
|
|
13
|
+
name: "System Command Execution (from SHIELD)",
|
|
14
|
+
description: "OAP policy pack produced from SHIELD threat feed.",
|
|
15
|
+
version: "1.0.0",
|
|
16
|
+
status: "active",
|
|
17
|
+
requires_capabilities: ["system.command.execute"],
|
|
18
|
+
min_assurance: "L0",
|
|
19
|
+
limits_required: ["allowed_commands", "max_execution_time"],
|
|
20
|
+
required_fields: ["command"],
|
|
21
|
+
optional_fields: ["args", "cwd", "env", "timeout", "shell", "user"],
|
|
22
|
+
enforcement: {
|
|
23
|
+
command_allowlist_enforced: true,
|
|
24
|
+
blocked_patterns_enforced: true,
|
|
25
|
+
execution_time_enforced: true,
|
|
26
|
+
},
|
|
27
|
+
required_context: {
|
|
28
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
29
|
+
type: "object",
|
|
30
|
+
required: ["command"],
|
|
31
|
+
properties: {
|
|
32
|
+
command: { type: "string", minLength: 1, maxLength: 10000 },
|
|
33
|
+
args: { type: "array", items: { type: "string" }, maxItems: 100 },
|
|
34
|
+
cwd: { type: "string", maxLength: 4096 },
|
|
35
|
+
env: { type: "object", additionalProperties: { type: "string" } },
|
|
36
|
+
timeout: { type: "integer", minimum: 1, maximum: 3600 },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
evaluation_rules_version: "1.0",
|
|
40
|
+
evaluation_rules: [
|
|
41
|
+
{
|
|
42
|
+
name: "command_allowlist",
|
|
43
|
+
type: "expression",
|
|
44
|
+
condition:
|
|
45
|
+
"limits.allowed_commands.includes('*') || limits.allowed_commands.includes(context.command) || limits.allowed_commands.some(c => context.command.startsWith(c))",
|
|
46
|
+
deny_code: "oap.command_not_allowed",
|
|
47
|
+
description: "Command must be in allowed list",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "blocked_patterns",
|
|
51
|
+
type: "custom_validator",
|
|
52
|
+
validator: "validateBlockedPatterns",
|
|
53
|
+
deny_code: "oap.blocked_pattern",
|
|
54
|
+
description: "Command must not contain blocked patterns (SHIELD-aligned)",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "execution_time_limit",
|
|
58
|
+
type: "expression",
|
|
59
|
+
condition:
|
|
60
|
+
"!context.timeout || context.timeout <= limits.max_execution_time",
|
|
61
|
+
deny_code: "oap.limit_exceeded",
|
|
62
|
+
description: "Execution time must not exceed limit",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function recommendationToPatterns(recommendation_agent) {
|
|
68
|
+
const block = [];
|
|
69
|
+
const approve = [];
|
|
70
|
+
if (!recommendation_agent || typeof recommendation_agent !== "string")
|
|
71
|
+
return { block, approve };
|
|
72
|
+
const blockMatch = recommendation_agent.match(
|
|
73
|
+
/BLOCK:\s*command contains\s+(.+?)(?:\s+OR\s+command contains\s+(.+))?$/i,
|
|
74
|
+
);
|
|
75
|
+
const approveMatch = recommendation_agent.match(
|
|
76
|
+
/APPROVE:\s*command contains\s+(.+?)(?:\s+OR\s+command contains\s+(.+))?$/i,
|
|
77
|
+
);
|
|
78
|
+
if (blockMatch) {
|
|
79
|
+
block.push(blockMatch[1].trim());
|
|
80
|
+
if (blockMatch[2]) block.push(blockMatch[2].trim());
|
|
81
|
+
}
|
|
82
|
+
if (approveMatch) {
|
|
83
|
+
approve.push(approveMatch[1].trim());
|
|
84
|
+
if (approveMatch[2]) approve.push(approveMatch[2].trim());
|
|
85
|
+
}
|
|
86
|
+
return { block, approve };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Convert threats (tool scope) to OAP limits fragment for system.command.execute.
|
|
91
|
+
*/
|
|
92
|
+
function threatsToLimitsFragment(threats) {
|
|
93
|
+
const blocked_patterns = [];
|
|
94
|
+
const shieldThreats = [];
|
|
95
|
+
for (const t of threats) {
|
|
96
|
+
if (t.revoked === "true") continue;
|
|
97
|
+
const { block, approve } = recommendationToPatterns(t.recommendation_agent);
|
|
98
|
+
blocked_patterns.push(...block, ...approve);
|
|
99
|
+
shieldThreats.push({
|
|
100
|
+
id: `shield_${t.id}`,
|
|
101
|
+
fingerprint: t.fingerprint || null,
|
|
102
|
+
category: t.category || "tool",
|
|
103
|
+
severity: t.severity || "high",
|
|
104
|
+
confidence: parseFloat(t.confidence) || 0.9,
|
|
105
|
+
action: t.action || "block",
|
|
106
|
+
title: t.title || t.id,
|
|
107
|
+
recommendation_agent: t.recommendation_agent || null,
|
|
108
|
+
expires_at: t.expires_at === "null" ? null : t.expires_at,
|
|
109
|
+
revoked: t.revoked === "true",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
allowed_commands: ["npm", "git", "node", "python"],
|
|
114
|
+
blocked_patterns: [...new Set(blocked_patterns)],
|
|
115
|
+
max_execution_time: 300,
|
|
116
|
+
shield: { threats: shieldThreats },
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Convert threats for tool.call scope to { policy, limitsFragment }.
|
|
122
|
+
*/
|
|
123
|
+
function convert(threats) {
|
|
124
|
+
const limitsFragment = threatsToLimitsFragment(threats);
|
|
125
|
+
return { policy: { ...POLICY }, limitsFragment };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
scope: "tool.call",
|
|
130
|
+
capability: "system.command.execute",
|
|
131
|
+
convert,
|
|
132
|
+
threatsToLimitsFragment,
|
|
133
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# SHIELD integration test folder
|
|
2
|
+
|
|
3
|
+
This folder contains the **test fixture** (`shield.md`) and **integration test** that run conversion and verify. The **scope adapters** live in [../adapters/](../adapters/): [index.js](../adapters/index.js) imports each scope adapter (e.g. [system-command-execute.js](../adapters/system-command-execute.js) for `tool.call` → `system.command.execute`) and is also the CLI entry point when run as a script.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
| File | Description |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `shield.md` | Example [SHIELD v0](https://nova-hunting.github.io/shield.md/) threat feed (tool scope: block `rm -rf`, require approval for `sudo`). Policy is always generated from this file via the adapters. |
|
|
10
|
+
| `test-shield-to-verify.js` | Integration tests: (1) conversion via adapters, (2) APort verify with policy-in-body, (3) response shape. |
|
|
11
|
+
|
|
12
|
+
## Flow
|
|
13
|
+
|
|
14
|
+
1. **SHIELD** (`shield.md`) defines threats.
|
|
15
|
+
2. **Adapters** ([../adapters/index.js](../adapters/index.js)) parse the feed and delegate by scope; [system-command-execute.js](../adapters/system-command-execute.js) handles `tool.call` → OAP `system.command.execute` policy + limits.
|
|
16
|
+
3. **Verify:** `POST /api/verify/policy/IN_BODY` with that policy and passport.
|
|
17
|
+
4. **Response** fits OAP; deny can follow SHIELD block format.
|
|
18
|
+
|
|
19
|
+
## Run the adapter (CLI)
|
|
20
|
+
|
|
21
|
+
Run [../adapters/index.js](../adapters/index.js) as a script:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# From repo root (default: test/shield.md)
|
|
25
|
+
node spec/integrations/shield/adapters/index.js
|
|
26
|
+
# Explicit path
|
|
27
|
+
node spec/integrations/shield/adapters/index.js spec/integrations/shield/test/shield.md
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Output: JSON with `policy`, `limitsFragment`, and `threats`. For policy-only or limits-only:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
SHIELD_OUTPUT=policy node spec/integrations/shield/adapters/index.js spec/integrations/shield/test/shield.md
|
|
34
|
+
SHIELD_OUTPUT=limits node spec/integrations/shield/adapters/index.js spec/integrations/shield/test/shield.md
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Run the tests
|
|
38
|
+
|
|
39
|
+
**API base URL:**
|
|
40
|
+
|
|
41
|
+
- **Not local (default, e.g. CI):** Uses live API `https://api.aport.io`. No env needed.
|
|
42
|
+
- **Local:** Set `LOCAL=1` (or `RUN_LOCAL=1` or `APORT_VERIFY_LOCAL=1`) and start the dev server (e.g. `npm run dev` or `wrangler dev`) at `http://localhost:8787`.
|
|
43
|
+
- **Override:** `APORT_API_BASE_URL` overrides the default (e.g. `APORT_API_BASE_URL=http://localhost:8787`).
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Use live API (default when not local)
|
|
47
|
+
node spec/integrations/shield/test/test-shield-to-verify.js
|
|
48
|
+
|
|
49
|
+
# Use local dev server
|
|
50
|
+
LOCAL=1 node spec/integrations/shield/test/test-shield-to-verify.js
|
|
51
|
+
|
|
52
|
+
# Explicit base URL
|
|
53
|
+
APORT_API_BASE_URL=https://api.aport.io node spec/integrations/shield/test/test-shield-to-verify.js
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Test coverage:** Conversion (shield.md → policy + limits via adapters), APort verify (allow/deny), response shape (deny code and message).
|
|
57
|
+
|
|
58
|
+
See [../README.md](../README.md) for SHIELD↔OAP mapping and adapter strategy.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: shield.md
|
|
3
|
+
description: Context-based runtime threat feed policy. Uses structured threat entries to decide log, require_approval, or block.
|
|
4
|
+
version: "0.1"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# shield-v0.md (example)
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
Example threat feed for OAP integration demo. Converts to OAP policy pack and is verified via POST /api/verify/policy/IN_BODY.
|
|
11
|
+
|
|
12
|
+
## Active threats (compressed)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
id: T001
|
|
16
|
+
fingerprint: example-destructive-cmd
|
|
17
|
+
category: tool
|
|
18
|
+
severity: critical
|
|
19
|
+
confidence: 0.95
|
|
20
|
+
action: block
|
|
21
|
+
title: Destructive shell commands
|
|
22
|
+
recommendation_agent: BLOCK: command contains rm -rf
|
|
23
|
+
expires_at: null
|
|
24
|
+
revoked: false
|
|
25
|
+
---
|
|
26
|
+
Block commands containing `rm -rf`.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
id: T002
|
|
30
|
+
fingerprint: example-sudo
|
|
31
|
+
category: tool
|
|
32
|
+
severity: high
|
|
33
|
+
confidence: 0.9
|
|
34
|
+
action: require_approval
|
|
35
|
+
title: Privilege escalation
|
|
36
|
+
recommendation_agent: APPROVE: command contains sudo
|
|
37
|
+
expires_at: null
|
|
38
|
+
revoked: false
|
|
39
|
+
---
|
|
40
|
+
Require approval for commands containing `sudo`.
|