Skip to content

manifest.integrity declares per-file artifact digests the spec says the runtime re-verifies at unpack — nothing computes them and nothing checks them #11331

Description

@os-sam

Measured while seeding the plugin-manifest liveness ledger for #10728 (verdict recorded as dead in packages/spec/liveness/manifest.json). Filed, not fixed.

Measured on origin/main (b9e9227e3).

What is declared

packages/spec/src/kernel/manifest.zod.ts:107-109, 576integrity: PluginIntegritySchema.optional(), a Record<string, string> of artifact-relative path → digest ("sha256-<base64>"), whose own describe/TSDoc says:

Per-file content digests of the packaged artifact (ADR-0025 §3.2), re-verified by the runtime when it unpacks the .osplugin (ADR §3.5 step 5).

What is measured

Zero reads, in either direction of the lifecycle:

  • Nothing computes the digests — os plugin build (packages/cli/src/commands/plugin/build.ts) bundles with esbuild and never hashes per-file content into a manifest.
  • Nothing verifies them — repo-wide probe for a manifest-shaped .integrity read ((manifest|pkg|package|entry|artifact)?.integrity, non-test, non-comment) returns no hits at all in objectstack, and objectui is likewise zero.

Controls

  • The same probe finds real reads of sibling manifest keys — .packaging (cli/src/commands/plugin/build.ts:126), .data (runtime/src/app-plugin.ts:946), .contributes (objectql/src/engine.ts:4504) — so it is not blind to this object.
  • objectui control: manifest.(id|name|namespace|version) → 46 hits there. The zero is about the path, not the probe.

Adjacent machinery that exists and is NOT this

packages/core/src/security/plugin-artifact-signature.ts verifies an artifact signature, and deliberately returns verified=false rather than throwing when one is absent (its header says so at :144, leaving the trust-tier decision to the caller). That is a different mechanism on a different field. Its presence is part of why this gap is easy to miss: the artifact-security story looks staffed.

Why it matters

A declared integrity check that never runs is the false-compliance shape in its most literal form: to any reader — human, AI, or an auditor reading a published manifest — the presence of an integrity block is indistinguishable from the digests having been checked. Unlike a dead display key, this one is about whether the bytes on disk are the bytes that were published.

⚠️ Scope limit — measure cloud before acting

cloud is unmeasured (carried from #10627: add_repo → "you don't have access to objectstack-ai/cloud"). The install-time unpack that ADR-0025 §3.5 describes is a control-plane path, and manifest.zod.ts:10-18 states the cloud control plane mirrors these shapes when validating a published .osplugin. Measure the cloud leg, with a control probe, before either fork. Same precondition #10724 carries.

The fork (not prejudged)

  • A — Enforce. Emit digests at os plugin build and verify them at unpack. This is what the spec already tells readers happens, so A is the option that makes the shipped documentation true.
  • B — Retire. Tombstone under ADR-0049 (ManifestSchema is not .strict(), so a plain deletion would silently strip the key — the loading precedent at manifest.zod.ts:510-514) and remove the "re-verified by the runtime" sentence with it.

A is the more defensible default here than on a typical dead key: the cost of B is deleting a supply-chain control the project has already published an intent to have. But intent is not delivery, and this is not a decision for an implementation seat.

Filed unassigned for triage.

Blocked-by: #10812


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions