Define operation-name normalization table and enforcement tests #106

Open
opened 2026-07-02 17:37:44 -05:00 by sysadmin · 1 comment
Owner

Summary

Define and test operation-name normalization for profiles.json v2.

Source discussion

Refs #100

Scope

Canonical operations should be namespaced, such as:

gitea.pr.merge
gitea.pr.approve
gitea.pr.create
gitea.branch.push
jenkins.read
jenkins.build.read
glitchtip.read
glitchtip.event.read

Legacy unqualified operations may be accepted only through explicit normalization.

Examples:

merge           → gitea.pr.merge
approve         → gitea.pr.approve
request_changes → gitea.pr.request_changes
review          → gitea.pr.review
comment         → gitea.pr.comment
read            → gitea.read

Security requirements

  • Normalize before checking allowed_operations and forbidden_operations.
  • forbidden_operations always overrides allowed_operations.
  • Unknown operations fail closed.
  • Ambiguous operations fail closed.
  • Cross-service operation names must not be accepted by the wrong service.
  • Normalization must not silently widen permissions.

Required test matrix

Cover:

  • fully qualified allowed operation
  • fully qualified forbidden operation
  • legacy unqualified allowed operation
  • legacy unqualified forbidden operation
  • unknown operation
  • ambiguous operation
  • service mismatch
  • forbidden-overrides-allowed
  • empty allowed list
  • missing allowed list
  • duplicate operations after normalization

Non-goals

  • Do not implement service tools.
  • Do not change release/tag state.
  • Do not broaden permissions.

Acceptance criteria

  • normalization table is documented
  • normalization happens before enforcement
  • fail-closed behavior is tested
  • existing v1 unqualified operation behavior remains compatible where intentionally supported
  • tests prove no permission widening
## Summary Define and test operation-name normalization for `profiles.json v2`. ## Source discussion Refs #100 ## Scope Canonical operations should be namespaced, such as: ```text gitea.pr.merge gitea.pr.approve gitea.pr.create gitea.branch.push jenkins.read jenkins.build.read glitchtip.read glitchtip.event.read ``` Legacy unqualified operations may be accepted only through explicit normalization. Examples: ```text merge → gitea.pr.merge approve → gitea.pr.approve request_changes → gitea.pr.request_changes review → gitea.pr.review comment → gitea.pr.comment read → gitea.read ``` ## Security requirements * Normalize before checking `allowed_operations` and `forbidden_operations`. * `forbidden_operations` always overrides `allowed_operations`. * Unknown operations fail closed. * Ambiguous operations fail closed. * Cross-service operation names must not be accepted by the wrong service. * Normalization must not silently widen permissions. ## Required test matrix Cover: * fully qualified allowed operation * fully qualified forbidden operation * legacy unqualified allowed operation * legacy unqualified forbidden operation * unknown operation * ambiguous operation * service mismatch * forbidden-overrides-allowed * empty allowed list * missing allowed list * duplicate operations after normalization ## Non-goals * Do not implement service tools. * Do not change release/tag state. * Do not broaden permissions. ## Acceptance criteria * normalization table is documented * normalization happens before enforcement * fail-closed behavior is tested * existing v1 unqualified operation behavior remains compatible where intentionally supported * tests prove no permission widening
sysadmin added the mcpsecuritytesting labels 2026-07-02 17:38:30 -05:00
Author
Owner

Implementation planning comment

Readiness

Ready with changes

Summary

The proposal to namespace operations and translate legacy unqualified ones is essential for multi-service safety. However, to prevent permission-widening and cross-service conflicts, normalization must be strictly context-sensitive (scoped by service type) rather than a global list, and it must occur identically on both allowed and forbidden lists before checking constraints.

Dependencies

  • #103 (v2 Parser): Prerequisite. The base parser for v2 schema must exist before normalization logic can be integrated.
  • #106 (This issue): Prerequisite for final Jenkins/GlitchTip tool enforcement, as they must consume the unified namespaced allowed/forbidden operation lists.

Risks

  • Permission Widening via Forbidden Override Bypass: If a user specifies forbidden_operations: ["merge"] and the system normalizes the allowed list but fails to normalize the forbidden list to gitea.pr.merge, the forbidden_operations check will miss the match and silently allow a forbidden operation.
  • Cross-Service Namespace Clash: A generic unqualified name like read is ambiguous. It must resolve to gitea.read, jenkins.read, or glitchtip.read depending on the service. A global mapping list without service context could lead to credentials/operations leaking or mapping incorrectly.

Acceptance criteria changes

  • Service-Scoped Normalization Map: The normalization logic must scope unqualified names by service type (Gitea, Jenkins, GlitchTip). Unqualified names from one service must not be valid or resolve under another service context.
  • Deprecation Logging: Require the parser to emit a runtime warning when an unqualified legacy name is resolved, guiding users to migrate their profiles to namespaced operations.
  • Deduplication: Verify that duplicates arising from normalization (e.g. both merge and gitea.pr.merge defined) are deduplicated cleanly.
  • Cross-Service Mismatch Invariant: Add an explicit test case proving that a Gitea-context legacy operation name (e.g., merge) is rejected when specified under a Jenkins or GlitchTip service block (fail-closed).

Recommended implementation notes

  • Implement a service-specific mapping dictionary:
    NORMALIZATION_MAP = {
        "gitea": {
            "merge": "gitea.pr.merge",
            "approve": "gitea.pr.approve",
            "request_changes": "gitea.pr.request_changes",
            "review": "gitea.pr.review",
            "comment": "gitea.pr.comment",
            "read": "gitea.read",
        },
        "jenkins": {
            "read": "jenkins.read",
        },
        "glitchtip": {
            "read": "glitchtip.read",
        }
    }
    
  • Implement the normalization method:
    def normalize_op(op: str, service: str) -> str:
        # If already fully qualified, return as-is after checking prefix matches service
        if "." in op:
            if not op.startswith(f"{service}."):
                raise ValueError(f"Operation {op} does not belong to service {service}")
            return op
    
        # If unqualified, check mapping
        mapped = NORMALIZATION_MAP.get(service, {}).get(op)
        if not mapped:
            raise ValueError(f"Unknown operation {op} for service {service}")
        return mapped
    
  • Ensure both allowed_operations and forbidden_operations lists are mapped using this function, then converted to sets to handle deduplication automatically before gating is applied.

Controller Handoff Summary

Work performed

Reviewed Issue #106, identified potential permission widening and service mismatch risks, defined acceptance criteria adjustments, and provided concrete Python code guidance in the issue comment.

Current state

  • Repo: Scaled-Tech-Consulting/Gitea-Tools, master 790c2c8, clean
  • Issues: #106 commented on, remaining issues open
  • PRs: none open
  • Status: planning/discussion phase

Files changed

None.

Validation

Not applicable — issue comment only.

Issues encountered

None.

Review needed?

No review needed — discussion/comment only.

Next recommended action

Wait for owner review and decision on #106. Proceed with Issue #103 implementation planning reviews.

Safety confirmations

  • no self-review ✓
  • no self-merge ✓
  • no release/tag changes ✓
  • no secrets committed ✓
  • no production access used ✓
## Implementation planning comment ### Readiness Ready with changes ### Summary The proposal to namespace operations and translate legacy unqualified ones is essential for multi-service safety. However, to prevent permission-widening and cross-service conflicts, normalization must be strictly context-sensitive (scoped by service type) rather than a global list, and it must occur identically on both allowed and forbidden lists before checking constraints. ### Dependencies - **#103 (v2 Parser):** Prerequisite. The base parser for v2 schema must exist before normalization logic can be integrated. - **#106 (This issue):** Prerequisite for final Jenkins/GlitchTip tool enforcement, as they must consume the unified namespaced allowed/forbidden operation lists. ### Risks * **Permission Widening via Forbidden Override Bypass:** If a user specifies `forbidden_operations: ["merge"]` and the system normalizes the allowed list but fails to normalize the forbidden list to `gitea.pr.merge`, the `forbidden_operations` check will miss the match and silently allow a forbidden operation. * **Cross-Service Namespace Clash:** A generic unqualified name like `read` is ambiguous. It must resolve to `gitea.read`, `jenkins.read`, or `glitchtip.read` depending on the service. A global mapping list without service context could lead to credentials/operations leaking or mapping incorrectly. ### Acceptance criteria changes * **Service-Scoped Normalization Map:** The normalization logic must scope unqualified names by service type (Gitea, Jenkins, GlitchTip). Unqualified names from one service must not be valid or resolve under another service context. * **Deprecation Logging:** Require the parser to emit a runtime warning when an unqualified legacy name is resolved, guiding users to migrate their profiles to namespaced operations. * **Deduplication:** Verify that duplicates arising from normalization (e.g. both `merge` and `gitea.pr.merge` defined) are deduplicated cleanly. * **Cross-Service Mismatch Invariant:** Add an explicit test case proving that a Gitea-context legacy operation name (e.g., `merge`) is rejected when specified under a Jenkins or GlitchTip service block (fail-closed). ### Recommended implementation notes * Implement a service-specific mapping dictionary: ```python NORMALIZATION_MAP = { "gitea": { "merge": "gitea.pr.merge", "approve": "gitea.pr.approve", "request_changes": "gitea.pr.request_changes", "review": "gitea.pr.review", "comment": "gitea.pr.comment", "read": "gitea.read", }, "jenkins": { "read": "jenkins.read", }, "glitchtip": { "read": "glitchtip.read", } } ``` * Implement the normalization method: ```python def normalize_op(op: str, service: str) -> str: # If already fully qualified, return as-is after checking prefix matches service if "." in op: if not op.startswith(f"{service}."): raise ValueError(f"Operation {op} does not belong to service {service}") return op # If unqualified, check mapping mapped = NORMALIZATION_MAP.get(service, {}).get(op) if not mapped: raise ValueError(f"Unknown operation {op} for service {service}") return mapped ``` * Ensure both `allowed_operations` and `forbidden_operations` lists are mapped using this function, then converted to sets to handle deduplication automatically before gating is applied. ### Controller Handoff Summary #### Work performed Reviewed Issue #106, identified potential permission widening and service mismatch risks, defined acceptance criteria adjustments, and provided concrete Python code guidance in the issue comment. #### Current state - Repo: `Scaled-Tech-Consulting/Gitea-Tools`, master `790c2c8`, clean - Issues: #106 commented on, remaining issues open - PRs: none open - Status: planning/discussion phase #### Files changed None. #### Validation Not applicable — issue comment only. #### Issues encountered None. #### Review needed? No review needed — discussion/comment only. #### Next recommended action Wait for owner review and decision on #106. Proceed with Issue #103 implementation planning reviews. #### Safety confirmations - no self-review ✓ - no self-merge ✓ - no release/tag changes ✓ - no secrets committed ✓ - no production access used ✓
jcwalker3 added the status:in-progress label 2026-07-03 02:25:51 -05:00
Sign in to join this conversation.