Skip to content

Missing capabilityAction is canonicalized as empty string on invocations #114

Description

@moisesja

Summary

Invocation.CapabilityAction defaults to string.Empty and is serialized into
the canonical payload, so an invocation document that omits capabilityAction
is canonicalized with "capabilityAction":"" injected — a field that was never
present on the wire. zcap-py and the JCS reference omit it. The injected field
changes the signing payload, so a signature over an invocation that legitimately
omits capabilityAction will not verify across implementations.

Evidence

ZCAP-LD interop harness, real zcap-py 0.6.0 vs zcap-dotnet @ 8a059d1,
2026-06-16. Fixture invocation-missing-capability-action-edge.

Source document (no capabilityAction):

{"id":"urn:uuid:inv-missing-action","capability":"urn:uuid:cap-missing-action","invocationTarget":"https://resource.example/missing-action"}

Canonical output:

  • zcap-py{"capability":"urn:uuid:cap-missing-action","id":...,"invocationTarget":...,"proof":{...}} (omitted)
    sha256 9f913062f54c191ccfd7b7a2fb112a8869d505ef0758282596101512341c0eec
  • zcap-dotnet{"capability":"urn:uuid:cap-missing-action","capabilityAction":"","id":...,...} (injected)
    sha256 a72906592145f2ca7698119830796598feca77b46030651220482de8e4803061

Suggested fix

Make CapabilityAction nullable and [JsonIgnore(WhenWritingNull)] (parallel
to the #37 fix for capability optional fields) so an absent field stays absent
through canonicalization.

Acceptance criteria

  • invocation-missing-capability-action-edge produces the same canonical bytes
    as zcap-py (no capabilityAction key when the source omits it).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingseverity: mediumReal defect; bounded or workaround existsspec-complianceW3C ZCAP-LD conformance

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions