Skip to content

Job cancellation authority is principal-scoped, not session-scoped, and null principals bypass the check (§7.6, §14) #74

@nficano

Description

@nficano

Category: spec-conformance Severity: minor
Location: src/Arcp.Runtime/JobManager.cs:444-455, src/Arcp.Runtime/SessionState.Dispatch.cs:85-91
Spec: ARCP v1.1 §7.6, §13.3, §14

What

Spec §7.6/§13.3 state cancellation is reserved for "the session that submitted the job" ("Only sess_A1 may cancel it"), and §14 says "Subscription MUST NOT confer cancel authority." Cancel only compares the principal subject, not the submitting session. A second session of the same principal (e.g. a dashboard that subscribed) can therefore cancel a job it did not submit. Additionally, the guard is skipped entirely when either principal is null (requesterPrincipal is not null && job.SubmitterPrincipal is not null), so a null-subject requester bypasses the check and cancels any job.

Evidence

public bool Cancel(JobId jobId, string? requesterPrincipal, string? reason)
{
    if (!_jobs.TryGetValue(jobId, out var job)) return false;
    if (requesterPrincipal is not null && job.SubmitterPrincipal is not null &&
        !string.Equals(requesterPrincipal, job.SubmitterPrincipal, StringComparison.Ordinal))
    {
        throw new PermissionDeniedException("Subscribers MUST NOT cancel jobs (spec §7.6)");
    }
    job.CancellationSource.Cancel();
    return true;
}

The dispatcher passes Principal?.Subject (a principal), never the submitting SessionId (SessionState.Dispatch.cs:89).

Proposed fix

  1. Pass the requesting SessionId into Cancel and compare it to job.SessionId (the submitting session) — reject with PERMISSION_DENIED when they differ, regardless of principal.
  2. Treat a null/unknown requester as unauthorized (fail closed) rather than skipping the check.

Acceptance criteria

  • A subscriber session (even same principal) cancelling a job it did not submit receives PERMISSION_DENIED.
  • A cancel from the original submitting session succeeds.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions