@tinycloud/share-sdk 1.2.0-beta.4 → 1.2.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
+ ArtifactPinError: () => ArtifactPinError,
23
24
  CredentialInvitationError: () => CredentialInvitationError,
24
25
  DEFAULT_MAX_CONTENT_BLOB_BYTES: () => DEFAULT_MAX_CONTENT_BLOB_BYTES,
25
26
  DEFAULT_MAX_SEALED_BLOB_BYTES: () => DEFAULT_MAX_SEALED_BLOB_BYTES,
@@ -42,6 +43,8 @@ __export(src_exports, {
42
43
  ShareReceiveError: () => ShareReceiveError,
43
44
  ShareRecipientClient: () => ShareRecipientClient,
44
45
  addressedCredentialRequirement: () => addressedCredentialRequirement,
46
+ artifactBindingView: () => artifactBindingView,
47
+ assertArtifactBinding: () => assertArtifactBinding,
45
48
  authorizationMethodForTarget: () => authorizationMethodForTarget,
46
49
  authorizeShare: () => authorizeShare,
47
50
  canonicalOwnerSharePolicy: () => canonicalOwnerSharePolicy,
@@ -84,7 +87,8 @@ __export(src_exports, {
84
87
  toBase64Url: () => toBase64Url,
85
88
  toShareErrorInfo: () => toShareErrorInfo,
86
89
  validateOwnerSharePolicyRegistration: () => validateOwnerSharePolicyRegistration,
87
- validateOwnerSharePolicyRegistrationBytes: () => validateOwnerSharePolicyRegistrationBytes
90
+ validateOwnerSharePolicyRegistrationBytes: () => validateOwnerSharePolicyRegistrationBytes,
91
+ verifyArtifactShare: () => verifyArtifactShare
88
92
  });
89
93
  module.exports = __toCommonJS(src_exports);
90
94
 
@@ -5425,6 +5429,20 @@ var v3TargetSchema = external_exports.object({
5425
5429
  nodeAudience: external_exports.string().min(1),
5426
5430
  spaceId: external_exports.string().refine(isCanonicalPathSegment, { message: "expected a canonical space id" })
5427
5431
  }).strict();
5432
+ var ARTIFACT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{2,62}$/;
5433
+ var artifactPinSchema = external_exports.object({
5434
+ v: external_exports.literal(1),
5435
+ artifactId: external_exports.string().regex(ARTIFACT_ID_PATTERN),
5436
+ version: external_exports.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
5437
+ /** SHA-256 (lowercase hex) of the exact `manifest.json` bytes. The manifest itself stays an owner-side record. */
5438
+ manifestSha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
5439
+ /** An addressed share reads one exact file, so exactly one entry: the stored bytes (ciphertext) it will fetch. */
5440
+ files: external_exports.array(external_exports.object({
5441
+ path: external_exports.string().min(1).max(255),
5442
+ sha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
5443
+ bytes: external_exports.number().int().nonnegative()
5444
+ }).strict()).length(1)
5445
+ }).strict();
5428
5446
  var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
5429
5447
  version: external_exports.literal(3),
5430
5448
  shareId: external_exports.string().min(1),
@@ -5444,7 +5462,13 @@ var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
5444
5462
  expiry: external_exports.string().datetime({ offset: true }),
5445
5463
  display: displaySchema,
5446
5464
  encrypted: external_exports.literal(true),
5447
- metadata: contentMetadataSchema
5465
+ metadata: contentMetadataSchema,
5466
+ /**
5467
+ * TC-1428: an owner-signed statement that this share reads one write-once artifact version. Additive: absent on
5468
+ * every other share, and covered by the envelope signature because the signature spans the whole unsigned object.
5469
+ * The shape is checked here; its binding to the content source is `verifyArtifactShare` in share-sdk.
5470
+ */
5471
+ artifactPin: artifactPinSchema.optional()
5448
5472
  }).strict();
5449
5473
  function validateV3Invariants(value, ctx) {
5450
5474
  const actions = [...value.actions];
@@ -6036,6 +6060,84 @@ async function parseSealedInlineShareUrl(url, options = {}) {
6036
6060
  return { kind: "inline", ciphertextCid: record.cid, ciphertext, key32 };
6037
6061
  }
6038
6062
 
6063
+ // src/artifact-core.ts
6064
+ var ARTIFACT_ROOT = "artifacts";
6065
+ function parseArtifactVersionPath(path) {
6066
+ const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
6067
+ if (match === null) return void 0;
6068
+ return { artifactId: match[1], version: Number(match[2]), file: match[3] };
6069
+ }
6070
+
6071
+ // src/artifact-pin.ts
6072
+ var ArtifactPinError = class extends Error {
6073
+ code;
6074
+ /** The code is the message: stable, short, and what callers and tests match on. */
6075
+ constructor(code3) {
6076
+ super(code3);
6077
+ this.name = "ArtifactPinError";
6078
+ this.code = code3;
6079
+ }
6080
+ };
6081
+ function artifactBindingView(envelope) {
6082
+ return {
6083
+ ...envelope.artifactPin === void 0 ? {} : { artifactPin: envelope.artifactPin },
6084
+ target: envelope.target,
6085
+ contentSource: envelope.contentSource,
6086
+ resource: envelope.resource,
6087
+ actions: envelope.actions,
6088
+ policyKvActions: envelope.policy.capabilityCeiling.flatMap((capability) => capability.kind === "kv" ? capability.actions : [])
6089
+ };
6090
+ }
6091
+ function sourcePath(view) {
6092
+ const resource = view.contentSource.kvResource;
6093
+ const marker = "/kv/";
6094
+ const at = resource.indexOf(marker);
6095
+ if (at <= 0 || resource.slice(0, at) !== view.target.spaceId) {
6096
+ throw new ArtifactPinError("pin-source-mismatch");
6097
+ }
6098
+ return resource.slice(at + marker.length);
6099
+ }
6100
+ function assertArtifactBinding(view, expected = {}) {
6101
+ const pin = view.artifactPin;
6102
+ const path = sourcePath(view);
6103
+ const inArtifacts = path === ARTIFACT_ROOT || path.startsWith(`${ARTIFACT_ROOT}/`);
6104
+ if (!inArtifacts) {
6105
+ if (pin !== void 0) throw new ArtifactPinError("pin-source-mismatch");
6106
+ return void 0;
6107
+ }
6108
+ if (pin === void 0) throw new ArtifactPinError("missing-pin");
6109
+ const parsed = parseArtifactVersionPath(path);
6110
+ if (parsed === void 0) throw new ArtifactPinError("unsupported-artifact-path");
6111
+ const file = pin.files[0];
6112
+ const source = view.contentSource;
6113
+ if (
6114
+ // The stored ciphertext of one file: bounded like content (100 MiB) plus the envelope's own framing.
6115
+ file.bytes > 128 * 1024 * 1024 || pin.artifactId !== parsed.artifactId || pin.version !== parsed.version || file.path !== parsed.file || view.resource.kind !== "exact" || view.resource.path !== path || source.selector !== "exact" || source.mode !== "immutable" || source.initialCiphertextDigestHex !== file.sha256
6116
+ ) {
6117
+ throw new ArtifactPinError("pin-source-mismatch");
6118
+ }
6119
+ if (view.actions.includes("edit") || view.policyKvActions.includes("tinycloud.kv/put")) {
6120
+ throw new ArtifactPinError("pinned-share-writable");
6121
+ }
6122
+ if (expected.artifactId !== void 0 && expected.artifactId !== pin.artifactId || expected.version !== void 0 && expected.version !== pin.version) {
6123
+ throw new ArtifactPinError("expectation-mismatch");
6124
+ }
6125
+ return pin;
6126
+ }
6127
+ function verifyArtifactShare(envelope, options) {
6128
+ if (envelope.signature.signerDid !== options.expectedOwnerDid) {
6129
+ throw new ArtifactPinError("wrong-signer");
6130
+ }
6131
+ let valid;
6132
+ try {
6133
+ valid = verifyEnvelopeV3SignatureOnly(envelope) && envelope.policy.ownerDid === options.expectedOwnerDid;
6134
+ } catch {
6135
+ valid = false;
6136
+ }
6137
+ if (!valid) throw new ArtifactPinError("bad-signature");
6138
+ return assertArtifactBinding(artifactBindingView(envelope), options.expected);
6139
+ }
6140
+
6039
6141
  // src/receive.ts
6040
6142
  var SHARE_RESULT_VERSION = 1;
6041
6143
  var DEFAULT_MAX_SEALED_BLOB_BYTES = 100 * 1024 * 1024 + 29;
@@ -6072,7 +6174,8 @@ function metadataFor(envelope, origin) {
6072
6174
  ...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
6073
6175
  ...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
6074
6176
  ...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
6075
- }
6177
+ },
6178
+ ...envelope.artifactPin === void 0 ? {} : { artifact: { id: envelope.artifactPin.artifactId, version: envelope.artifactPin.version } }
6076
6179
  };
6077
6180
  }
6078
6181
  async function resolvePolicyShare(link, options) {
@@ -6103,6 +6206,12 @@ async function resolvePolicyShare(link, options) {
6103
6206
  } catch {
6104
6207
  throw new ShareReceiveError("signature-invalid", "share signature is invalid");
6105
6208
  }
6209
+ try {
6210
+ verifyArtifactShare(envelope, { expectedOwnerDid: envelope.policy.ownerDid });
6211
+ } catch (error) {
6212
+ if (!(error instanceof ArtifactPinError)) throw error;
6213
+ throw new ShareReceiveError("envelope-invalid", `share artifact pin is invalid (${error.code})`);
6214
+ }
6106
6215
  options.signal?.throwIfAborted();
6107
6216
  const expiry = Date.parse(envelope.expiry);
6108
6217
  if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
@@ -6821,6 +6930,14 @@ function assertMailboxCommitment(commitment, target) {
6821
6930
  async function publishAddressedShare(input) {
6822
6931
  assertSafeInput(input);
6823
6932
  const { target } = prepareAddressedShare(input);
6933
+ assertArtifactBinding({
6934
+ ...input.artifactPin === void 0 ? {} : { artifactPin: input.artifactPin },
6935
+ target: { spaceId: input.spaceId },
6936
+ contentSource: input.contentSource,
6937
+ resource: input.resource,
6938
+ actions: input.actions,
6939
+ policyKvActions: input.policyActions
6940
+ });
6824
6941
  const options = { ...input, filename: canonicalShareFilename(input.filename) };
6825
6942
  if (target.kind !== "recipientDid") assertMailboxCommitment(options.credentialRequirement, target);
6826
6943
  const expiry = rfc3339Seconds(options.expiresAt);
@@ -6887,7 +7004,8 @@ async function publishAddressedShare(input) {
6887
7004
  filename: options.filename,
6888
7005
  ...options.mediaType.startsWith("text/") ? { encoding: "utf-8" } : {},
6889
7006
  ...options.artifact === void 0 ? {} : { artifact: options.artifact }
6890
- }
7007
+ },
7008
+ ...options.artifactPin === void 0 ? {} : { artifactPin: options.artifactPin }
6891
7009
  };
6892
7010
  unsignedShareEnvelopeV3Schema.parse(unsigned);
6893
7011
  const envelopeSignature = await options.authority.sign((0, import_sha2562.sha256)(textEncoder.encode(`${ENVELOPE_V3_DOMAIN}${canonicalize(unsigned)}`)));
@@ -8581,6 +8699,7 @@ var ShareRecipientClient = class {
8581
8699
  const envelope = this.options.envelope;
8582
8700
  const signer = this.nativeSigner ?? this.signer;
8583
8701
  if (envelope.version !== 3 || this.session === void 0 || this.v3Authorization === void 0 || this.v3NodeAudience === void 0 || signer === void 0) throw new Error("v3 policy session signer is required");
8702
+ assertArtifactBinding(artifactBindingView(envelope));
8584
8703
  if (!matchesInitialCiphertextDigest(bytes3, envelope.contentSource.initialCiphertextDigestHex)) throw new Error("v3 owner KV ciphertext digest mismatch");
8585
8704
  const encrypted = parseV3InlineEncryptedEnvelope(bytes3, envelope);
8586
8705
  const receiverPrivateKey = crypto.getRandomValues(new Uint8Array(32));
@@ -8770,6 +8889,7 @@ async function openNativeShare(sharing, link) {
8770
8889
  }
8771
8890
  // Annotate the CommonJS export names for ESM import in node:
8772
8891
  0 && (module.exports = {
8892
+ ArtifactPinError,
8773
8893
  CredentialInvitationError,
8774
8894
  DEFAULT_MAX_CONTENT_BLOB_BYTES,
8775
8895
  DEFAULT_MAX_SEALED_BLOB_BYTES,
@@ -8792,6 +8912,8 @@ async function openNativeShare(sharing, link) {
8792
8912
  ShareReceiveError,
8793
8913
  ShareRecipientClient,
8794
8914
  addressedCredentialRequirement,
8915
+ artifactBindingView,
8916
+ assertArtifactBinding,
8795
8917
  authorizationMethodForTarget,
8796
8918
  authorizeShare,
8797
8919
  canonicalOwnerSharePolicy,
@@ -8834,6 +8956,7 @@ async function openNativeShare(sharing, link) {
8834
8956
  toBase64Url,
8835
8957
  toShareErrorInfo,
8836
8958
  validateOwnerSharePolicyRegistration,
8837
- validateOwnerSharePolicyRegistrationBytes
8959
+ validateOwnerSharePolicyRegistrationBytes,
8960
+ verifyArtifactShare
8838
8961
  });
8839
8962
  //# sourceMappingURL=index.cjs.map