Skip to content

chore(plugins): move cedarpolicyplugin to plugins/external/cedar - #50

Open
reginaldalfret wants to merge 2 commits into
IBM:mainfrom
reginaldalfret:reginaldalfret/issue-21-move-cedar-plugin
Open

reginaldalfret wants to merge 2 commits into
IBM:mainfrom
reginaldalfret:reginaldalfret/issue-21-move-cedar-plugin

Conversation

@reginaldalfret

Copy link
Copy Markdown

Description

Moves the cedarpolicyplugin external plugin from the IBM/mcp-context-forge monorepo into this repository at plugins/external/cedar/, as part of the plugin ecosystem separation tracked in IBM/mcp-context-forge#2100.

Includes:

  • Plugin implementation in cedarpolicyplugin/ (__init__.py, plugin.py, schema.py, plugin-manifest.yaml)
  • Configuration examples in examples/
  • Resource configurations in resources/
  • Test suite in tests/
  • pyproject.toml, Containerfile, Makefile, run-server.sh, and README.md

Fixes #21.

Move the cedarpolicyplugin external plugin from the mcp-context-forge
monorepo to plugins/external/cedar/ as part of the plugin ecosystem
separation.

Fixes IBM#21.

Signed-off-by: reginaldalfret <reginaldalfret@gmail.com>

@jonpspri jonpspri left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: request changes

2 of 5 acceptance criteria from #21 fail (one proven by execution). The copy is faithful to upstream (IBM/mcp-context-forge@main), sign-off is present, and the code passes its own tests and lint in a working environment — but it does not install as merged.

Acceptance criteria scorecard

#21 criterion Status
Plugin lands at plugins/external/cedar/ ✅ all 20 files, matches issue layout
pip install -e plugins/external/cedar ❌ ResolutionImpossible (proven below)
Tests pass ✅ 12/12 passed, 90% coverage — but only after hand-fixing deps
CI covers plugin (lint + test) ❌ no workflow added; .github/workflows/ untouched
README reflects new home ❌ all URLs/instructions still point to the monorepo

Blocking

1. Declared dependencies are mutually unsatisfiable — pyproject.toml

cedarpolicyplugin 0.1.0 depends on mcp<2 and >=1.28.1
mcp-contextforge-gateway 1.0.11 depends on mcp>=2.0.0

Gateway 1.0.8–1.0.10 require mcp<2,>=1.28.1; 1.0.11 flipped to mcp>=2.0.0 (verified via PyPI metadata). Issue #21 specified >=1.0.8; this PR took upstream's newer >=1.0.11 pin, which is exactly the version that breaks resolution. (Upstream has the same broken pin — the defect was copied, not introduced — but the acceptance criterion is explicit and the PR fails it today.)

Verified fix: with mcp-contextforge-gateway>=1.0.8,<1.0.11 the resolver picks 1.0.10, which pulls cpex 0.1.3 transitively, and pytest tests/ → 12 passed, ruff check → clean. Alternative: drop the mcp<2 pin and migrate to gateway 1.0.11 / cpex 0.1.4 (mcp 2.x) — larger, untested; since #21 says dependency review is tracked separately, the <1.0.11 pin is the minimal honest fix.

Note: cpex is imported everywhere in plugin.py/tests but never declared in dependencies — it only arrives transitively via the gateway. Works today; fragile. Worth declaring explicitly.

2. No CI — criterion 4. No workflow exists for lint+test of this plugin. Existing per-component workflows (e.g. build-fast-time-server.yml) are the established pattern to copy.

Non-blocking but should fix

3. Shipped example configs contain policies that can never match or parse:

  • examples/config-cedar.yaml and resources/plugins/config.yaml use Prompts::"judge_prompts" — the plugin generates Prompt::"..." (CedarResourceTemplates.PROMPT in plugin.py). Prompt policies silently never match. The README uses the correct Prompt::.
  • examples/config-cedar.yaml (and the README's full-config blob) use Resource::""https://example.com/data"" — doubled quotes; invalid Cedar entity syntax, will raise at evaluation.
  • resources/plugins/config.yaml registers only 4 hooks (prompt_*, tool_*) yet defines Resource/Prompt policies; resource hooks are never registered.

4. tool_post_invoke fails open where sibling hooks fail closed — plugin.py: when the tool action is Allowed but both view_full and view_redacted are Deny, it returns continue_processing=True with the unredacted result. prompt_post_fetch/resource_post_fetch return a DENY violation in the identical state. Inconsistent and security-relevant; untested path.

5. plugin-manifest.yaml is stale: lists prompt_pre_hook/tool_pre_hook-style names (actual hooks: prompt_pre_fetch, tool_pre_invoke, …), omits resource hooks, and default_configs: is null. Repo convention (plugins/python/pii_filter/plugin-manifest.yaml) uses correct names and populated defaults.

6. README defects: references .env.example (file is .env.template); references resources/config.yaml (actual: resources/plugins/config.yaml); "opa plugin" copy-paste remnants; versionaaaaa typo; "server fast-time that exposes git tools" (registers mcp-server-git); broken step numbering. Plus criterion 5: nothing points to this repo — Homepage/Repository/Bug-Tracker in pyproject.toml also still target IBM/mcp-context-forge.

7. Packaging nits: license-files = ["LICENSE"] and MANIFEST.in include LICENSE, but no LICENSE file ships (editable install survived; sdist/wheel will lack it). [tool.setuptools.package-data] references resources/plugins/config.yaml relative to the package dir — matches nothing.

8. Dead/buggy code carried over: _extract_payload_key never called; _preprocess_request's elif "is_admin" in user branch does a substring test on a str user — effectively dead, and unknown users get Role::"None" (silent deny); unused AUDIT_CODE, REQUIRES_HUMAN_APPROVAL_CODE, INVALID_CEDAR_CONFIG, cedar_context_key. Acceptable for a verbatim move; fine to defer.

Suggested path

1-line pin fix (>=1.0.8,<1.0.11) + CI workflow gets this mergeable with criteria met. Items 3–6 are small, contained, and worth fixing here rather than re-copying upstream's bugs — but if strict-verbatim is the policy, at minimum fix 1 and 2, and file follow-ups for 3–5.

Address maintainer review on PR IBM#50:
- pyproject.toml: pin mcp-contextforge-gateway to '>=1.0.8,<1.0.11' to resolve dependency conflict with mcp<2, declare cpex>=0.1.3 explicitly, remove tool.uv.sources, remove conflicting pytest-env, update project URLs to contextforge-examples, and fix package-data.
- LICENSE: add missing Apache 2.0 license file to plugins/external/cedar/.
- plugin.py: fix fail-open behavior in tool_post_invoke when tool is Allowed but neither view_full nor view_redacted is permitted, returning a DENY violation.
- tests: add regression test test_cedarpolicyplugin_post_tool_invoke_both_views_denied.
- configs: correct Prompts:: to Prompt::, fix doubled quotes in Resource:: URIs, and include resource hooks.
- plugin-manifest.yaml: update available_hooks with canonical names and resource hooks, populate default_configs.
- schema.py: clean up leftover OPA docstring references.
- README.md: fix .env.template, config.yaml paths, OPA remnants, server description, typo, and step numbering.
- .github/workflows/cedar-plugin-tests.yml: add CI workflow for ruff lint and pytest.

Signed-off-by: reginaldalfret <reginaldalfret@gmail.com>

@jonpspri jonpspri left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: request changes — two small items, same defect class this commit already fixed elsewhere. Everything blocking from my previous review is resolved and proven by execution, not just by reading.

Re-verification (executed at 1acf749)

  • pip install -e plugins/external/cedar in a fresh Python 3.12 venv → exit 0. Resolver picks gateway 1.0.10 / cpex 0.1.3 / mcp 1.30.0 — the ResolutionImpossible conflict is gone.
  • pytest tests/ → 13 passed, 91% coverage, including the new test_cedarpolicyplugin_post_tool_invoke_both_views_denied, which exercises exactly the fixed branch.
  • ruff check . → clean.
  • tool_post_invoke fail-open fix confirmed at plugin.py:551-558 (DENY violation, continue_processing=False) — now consistent with the sibling hooks.
  • CI: .github/workflows/cedar-plugin-tests.yml runs ruff + pytest on py3.12 with path filters and pinned action SHAs; pip install -e ".[dev]" covers pytest/pytest-cov/ruff. Matches the repo's per-component pattern.

Acceptance criteria (#21)

Criterion Status
Plugin lands at plugins/external/cedar/ ✅
Installable via pip install -e ✅ proven by execution
Tests pass ✅ 13/13, 91% cov
CI covers lint + test ✅
README reflects new home ✅

Blocking — redaction silently no-ops in shipped configs (security-relevant)

1. Redaction regex ships quote-delimited. examples/config-cedar.yaml:21, examples/config-dsl.yaml:21, and README.md:105 all carry pattern: '"\$\d{1,}(,\d{1,})*"'. The YAML single-quoting preserves the literal " characters, so the compiled regex requires a quote character before the $ — it can never match currency text. A user granted only view_redacted receives full, unredacted output with no error. This commit fixed the identical doubled-quote defect in the Resource:: URIs but left it in the regex patterns in the same files (the test suite uses the correct unquoted form, which is why tests pass). Fix: pattern: "\$\d{1,}(,\d{1,})*" in all three places.

2. resources/plugins/config.yaml grants redaction it can't perform. The policy grants Role::"non-admin" only Action::"view_redacted_output" (lines 44–78), but the plugin config block omits policy_redaction_spec entirely; plugin.py:318-319 then returns the payload unchanged. The README's end-to-end walkthrough is built on this file, so following it demonstrates redaction that isn't happening. Fix: add policy_redaction_spec mirroring examples/config-cedar.yaml (with the quote fix from item 1).

Both are ~3-line edits to files this commit already touches — after those, this is an approve from me.

Non-blocking (fine as immediate follow-ups)

  • cedarpolicyplugin/__init__.py:22-23 and Containerfile:43: 🔵 IBM/mcp-context-forge URLs survived the URL pass. Repoint at this repo.
  • pyproject.toml:58: 🔵 Changelog links to a CHANGELOG.md that doesn't exist here. Drop the entry.
  • README.md:93: 🔵 config snippet documents mode: "enforce" | permissive | disabled; every shipped config uses mode: "sequential" | transform | disabled. Align the snippet.
  • .ruff.toml:38,42: 🔵 target-version = "py311" contradicts requires-python = ">=3.12", and select = ["E4","E7","E9","F"] is weaker than the sibling convention (url_to_markdown_server: py312, E W F B I N UP). Align when convenient.

Question

  • plugin-manifest.yaml:11-15: ships default_configs (plural) with no name/kind/tags; the pinned cpex 0.1.3 PluginManifest model requires name, kind, tags, default_config (singular) — verified against the installed package, so this manifest fails cpex validation as shipped. It does match our own pii_filter convention, so I don't want it fixed in isolation: is the plural key the intended plugin-ecosystem format (and cpex's model is stale), or should both manifests move to the cpex schema in a follow-up?

Note / self-correction

  • My earlier comment about tests/pytest.ini:10 (env_files) was wrong: that's a pytest-dotenv option and pytest-dotenv is in dev extras, so CI (which installs .[dev]) is fine. No change needed; only bare non-dev runs emit an unknown-option warning.

Inherited upstream smells (duplicated hook scaffolding, dead _extract_payload_key/cedar_context_key, monorepo leftovers in MANIFEST.in/.dockerignore/dev extras) remain as expected for a move — follow-up material, not PR demands, consistent with how we treated item 8 originally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CHORE]: Move cedarpolicyplugin to plugins/external/cedar

2 participants