feat: operation-name normalization table with fail-closed enforcement (#106) #122
Reference in New Issue
Block a user
Delete Branch "feat/issue-106-op-normalization"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #106
What
Defines the canonical operation-name normalization table for
profiles.jsonv2 and enforces it before every allowed/forbidden check.Normalization (
gitea_config.py)GITEA_OPERATION_ALIASES— the #103 minimal map promoted to the documented public table (contents unchanged; no new permissions introduced).normalize_operation(op, service="gitea")— canonical namespaced names (gitea.pr.merge,jenkins.read). Legacy unqualified spellings accepted only via the explicit table. Unknown, ambiguous (dotted, non-prefixed, not in table), and cross-service names raiseConfigError(fail closed). Gitea aliases are never applied to other services.check_operation(op, allowed, forbidden, service)— normalizes both the requested operation and the profile lists before any membership check.forbiddenalways overridesallowed. An unnormalizable allowed entry grants nothing; an unnormalizable forbidden entry denies the request — normalization can never silently widen permissions. Empty/missing allowed list denies everything._normalize_op(v2 identity loading) now wraps the public function, adding identity context to errors; load-time behavior unchanged.Enforcement fix (
mcp_server.py)gitea_check_pr_eligibilitypreviously compared the raw action ("merge") against normalized profile lists (["gitea.pr.merge"]): canonical allowed ops never matched, and canonical forbidden entries were never enforced at this gate. The capability check now routes throughcheck_operation; all existing reason strings preserved.Docs
docs/gitea-execution-profiles.mdgains the normalization table and enforcement rules, replacing the stale "runtime enforcement is out of scope" caveat.Tests
452 passed, 6 skipped (27 new in
tests/test_op_normalization.py). Full #106 matrix: fully-qualified allowed/forbidden, legacy unqualified allowed/forbidden, unknown op, ambiguous op, service mismatch, forbidden-overrides-allowed (across mixed spellings), empty allowed, missing allowed, duplicates after normalization, junk-entry no-widening, plus eligibility integration tests. TDD red verified: the two eligibility integration tests fail against the pre-changemcp_server.pyand pass after. Existing v1/env unqualified behaviour stays compatible (regression test included).Non-goals honored
No service tools implemented, no release/tag state changed, no permissions broadened (alias table contents identical to #103).
🤖 Generated with Claude Code
Approved. Reviewed operation-name normalization for #106, including canonical namespaced operations, fail-closed unknown/ambiguous/cross-service behavior, forbidden-overrides-allowed enforcement, junk-entry handling, empty-allowed denial, docs, and tests. Validation passed: eligibility gate from pinned PR head, pytest tests/ -q, git diff --check, py_compile, and secret/provenance sweep.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.