Skip to content

content_marking.build_assertion accepts truthy non-string eat_profile values #399

Closed

Description

@altrudev

Problem

On current main, content_marking.build_assertion() reads the record's eat_profile and only checks its truthiness:

profile = record.get("eat_profile")
...
if not profile:
    raise ContentMarkingError("the record declares no eat_profile")

That rejects missing and falsy values, but accepts any truthy non-string value. The accepted value is then copied directly into the produced assertion:

"eat_profile": profile,

So values such as 1, True, [1], or {"x": 1} can be emitted by build_assertion() even though this field is a profile identifier and the companion assertion format shows it as a string.

This is the adjacent producer-side observation originally recorded in #326. @lywinged independently resurfaced it during review of #390 and noted that it currently has no issue carrying it.

Why this is separate from #326

#326 was a consumer-side presence defect in verify_assertion(): mutual absence of required fields could pass equality checks. That issue was fixed in #335.

This is different:

  • it is on the producer path;
  • the member is present;
  • the defect is a shape/type boundary, not a presence boundary;
  • a malformed truthy value can be emitted by the library itself.

Keeping this separate avoids reopening the already-completed #326 scope.

Minimal reproduction

Start from record bytes accepted by the producer path except for the eat_profile value, then set:

for value in [1, True, [1], {"x": 1}]:
    record["eat_profile"] = value
    assertion = build_assertion(json.dumps(record).encode(), url="https://example.test/r.json")
    assert assertion["data"]["eat_profile"] == value

The current truthiness guard accepts each of those values and preserves its original type.

Useful controls:

  • absent eat_profile -> refused;
  • None -> refused;
  • "" -> refused;
  • valid non-empty profile string -> accepted;
  • truthy non-string values -> currently accepted.

Contract boundary

spec/content-marking-v1.md section 2 defines eat_profile as the record's profile so a consumer knows what it is fetching before it fetches it. The example is textual, and build_assertion() is the producer for that assertion shape.

More importantly, the repository already uses the opposite validation pattern elsewhere: required textual identifiers are checked as non-empty strings rather than by truthiness alone. A producer should not manufacture an assertion shape that its own downstream consumers cannot safely treat as the documented profile identifier.

Bounded fix direction

Replace the truthiness check with an explicit type/emptiness check, for example:

if not isinstance(profile, str) or not profile:
    raise ContentMarkingError("the record declares no valid eat_profile")

Regression matrix:

  • valid non-empty string succeeds;
  • absent / None / empty string refuse;
  • 0, False, [], {} continue to refuse;
  • 1, True, [1], {"x": 1} newly refuse.

No verifier semantics, digest binding, C2PA signature behavior, TRACE signature behavior, or wire-format version needs to change.

Duplicate / ownership check

I searched current issues/PRs for content_marking, build_assertion, eat_profile, and truthiness/type handling. #326 contains the original adjacent observation but is closed and fixed for a different consumer-side defect. I found no open issue or PR carrying this producer-side type boundary.

AI-assisted source review, duplicate search, and regression design; @altrudev remains responsible for the contribution.

Activity

  1. github-actions commented on Sep 22, 2026

    @github-actions
    Contributor

    🔴 Contributor Check: HIGH

    Check Result
    Profile HIGH
    Credential LOW
    Overall HIGH

    Automated check by AgenTrust Contributor Check.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-review:HIGHContributor check flagged HIGH risk

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions