@tinycloud/cli 1.3.0-beta.18 → 1.3.0-beta.20

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.
@@ -23736,6 +23736,20 @@ var v3TargetSchema = external_exports.object({
23736
23736
  nodeAudience: external_exports.string().min(1),
23737
23737
  spaceId: external_exports.string().refine(isCanonicalPathSegment, { message: "expected a canonical space id" })
23738
23738
  }).strict();
23739
+ var ARTIFACT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]{2,62}$/;
23740
+ var artifactPinSchema = external_exports.object({
23741
+ v: external_exports.literal(1),
23742
+ artifactId: external_exports.string().regex(ARTIFACT_ID_PATTERN),
23743
+ version: external_exports.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
23744
+ /** SHA-256 (lowercase hex) of the exact `manifest.json` bytes. The manifest itself stays an owner-side record. */
23745
+ manifestSha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
23746
+ /** An addressed share reads one exact file, so exactly one entry: the stored bytes (ciphertext) it will fetch. */
23747
+ files: external_exports.array(external_exports.object({
23748
+ path: external_exports.string().min(1).max(255),
23749
+ sha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
23750
+ bytes: external_exports.number().int().nonnegative()
23751
+ }).strict()).length(1)
23752
+ }).strict();
23739
23753
  var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
23740
23754
  version: external_exports.literal(3),
23741
23755
  shareId: external_exports.string().min(1),
@@ -23755,7 +23769,13 @@ var unsignedShareEnvelopeV3BaseSchema = external_exports.object({
23755
23769
  expiry: external_exports.string().datetime({ offset: true }),
23756
23770
  display: displaySchema,
23757
23771
  encrypted: external_exports.literal(true),
23758
- metadata: contentMetadataSchema
23772
+ metadata: contentMetadataSchema,
23773
+ /**
23774
+ * TC-1428: an owner-signed statement that this share reads one write-once artifact version. Additive: absent on
23775
+ * every other share, and covered by the envelope signature because the signature spans the whole unsigned object.
23776
+ * The shape is checked here; its binding to the content source is `verifyArtifactShare` in share-sdk.
23777
+ */
23778
+ artifactPin: artifactPinSchema.optional()
23759
23779
  }).strict();
23760
23780
  function validateV3Invariants(value, ctx) {
23761
23781
  const actions = [...value.actions];
@@ -23791,6 +23811,12 @@ var shareEnvelopeV3Schema = unsignedShareEnvelopeV3BaseSchema.extend({ signature
23791
23811
  var ENVELOPE_AAD_LABEL = "tinycloud-share-envelope-v1";
23792
23812
  var AAD = utf8Bytes(ENVELOPE_AAD_LABEL);
23793
23813
  var MAX_INLINE_BYTES = 256 * 1024;
23814
+ var ARTIFACT_ROOT = "artifacts";
23815
+ function parseArtifactVersionPath(path) {
23816
+ const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
23817
+ if (match === null) return void 0;
23818
+ return { artifactId: match[1], version: Number(match[2]), file: match[3] };
23819
+ }
23794
23820
  var UNSAFE_FILENAME_CODE_POINT = /[\p{Cc}\p{Cf}\p{Cs}\u2028\u2029]/u;
23795
23821
  function hasUnsafeFilenameCodePoint(value) {
23796
23822
  return UNSAFE_FILENAME_CODE_POINT.test(value);
@@ -23802,7 +23828,9 @@ function canonicalShareFilename(value) {
23802
23828
  }
23803
23829
  return canonical;
23804
23830
  }
23805
- var ARTIFACT_ROOT = "artifacts";
23831
+ function sha256Hex(bytes) {
23832
+ return Array.from(sha2562(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
23833
+ }
23806
23834
  var ARTIFACT_MANIFEST_NAME = "manifest.json";
23807
23835
  var ARTIFACT_MANIFEST_VERSION = 1;
23808
23836
  var ARTIFACT_ID = /^[a-z0-9][a-z0-9_-]{2,62}$/;
@@ -23815,9 +23843,6 @@ var ArtifactError = class extends Error {
23815
23843
  this.code = code22;
23816
23844
  }
23817
23845
  };
23818
- function sha256Hex(bytes) {
23819
- return Array.from(sha2562(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
23820
- }
23821
23846
  function newArtifactId(random = (length23) => crypto.getRandomValues(new Uint8Array(length23))) {
23822
23847
  return `art_${Array.from(random(8), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
23823
23848
  }
@@ -23840,11 +23865,6 @@ function artifactVersionResource(artifactId, version) {
23840
23865
  function artifactVersionFilePath(artifactId, version, filename) {
23841
23866
  return `${artifactVersionResource(artifactId, version)}/${filename}`;
23842
23867
  }
23843
- function parseArtifactVersionPath(path) {
23844
- const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
23845
- if (match === null) return void 0;
23846
- return { artifactId: match[1], version: Number(match[2]), file: match[3] };
23847
- }
23848
23868
  function parseArtifactManifest(bytes) {
23849
23869
  let value;
23850
23870
  try {
@@ -29216,6 +29236,20 @@ var v3TargetSchema2 = external_exports2.object({
29216
29236
  nodeAudience: external_exports2.string().min(1),
29217
29237
  spaceId: external_exports2.string().refine(isCanonicalPathSegment2, { message: "expected a canonical space id" })
29218
29238
  }).strict();
29239
+ var ARTIFACT_ID_PATTERN2 = /^[a-z0-9][a-z0-9_-]{2,62}$/;
29240
+ var artifactPinSchema2 = external_exports2.object({
29241
+ v: external_exports2.literal(1),
29242
+ artifactId: external_exports2.string().regex(ARTIFACT_ID_PATTERN2),
29243
+ version: external_exports2.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
29244
+ /** SHA-256 (lowercase hex) of the exact `manifest.json` bytes. The manifest itself stays an owner-side record. */
29245
+ manifestSha256: external_exports2.string().regex(/^[0-9a-f]{64}$/),
29246
+ /** An addressed share reads one exact file, so exactly one entry: the stored bytes (ciphertext) it will fetch. */
29247
+ files: external_exports2.array(external_exports2.object({
29248
+ path: external_exports2.string().min(1).max(255),
29249
+ sha256: external_exports2.string().regex(/^[0-9a-f]{64}$/),
29250
+ bytes: external_exports2.number().int().nonnegative()
29251
+ }).strict()).length(1)
29252
+ }).strict();
29219
29253
  var unsignedShareEnvelopeV3BaseSchema2 = external_exports2.object({
29220
29254
  version: external_exports2.literal(3),
29221
29255
  shareId: external_exports2.string().min(1),
@@ -29235,7 +29269,13 @@ var unsignedShareEnvelopeV3BaseSchema2 = external_exports2.object({
29235
29269
  expiry: external_exports2.string().datetime({ offset: true }),
29236
29270
  display: displaySchema2,
29237
29271
  encrypted: external_exports2.literal(true),
29238
- metadata: contentMetadataSchema2
29272
+ metadata: contentMetadataSchema2,
29273
+ /**
29274
+ * TC-1428: an owner-signed statement that this share reads one write-once artifact version. Additive: absent on
29275
+ * every other share, and covered by the envelope signature because the signature spans the whole unsigned object.
29276
+ * The shape is checked here; its binding to the content source is `verifyArtifactShare` in share-sdk.
29277
+ */
29278
+ artifactPin: artifactPinSchema2.optional()
29239
29279
  }).strict();
29240
29280
  function validateV3Invariants2(value, ctx) {
29241
29281
  const actions = [...value.actions];
@@ -29579,6 +29619,80 @@ async function parseSealedInlineShareUrl(url, options = {}) {
29579
29619
  if (await computeCid(ciphertext) !== record2.cid) throw new TypeError("sealed inline ciphertext does not match its CID");
29580
29620
  return { kind: "inline", ciphertextCid: record2.cid, ciphertext, key32 };
29581
29621
  }
29622
+ var ARTIFACT_ROOT2 = "artifacts";
29623
+ function parseArtifactVersionPath2(path) {
29624
+ const match = /^artifacts\/([a-z0-9][a-z0-9_-]{2,62})\/versions\/([1-9][0-9]{0,14})\/([^/]+)$/.exec(path);
29625
+ if (match === null) return void 0;
29626
+ return { artifactId: match[1], version: Number(match[2]), file: match[3] };
29627
+ }
29628
+ var ArtifactPinError = class extends Error {
29629
+ code;
29630
+ /** The code is the message: stable, short, and what callers and tests match on. */
29631
+ constructor(code32) {
29632
+ super(code32);
29633
+ this.name = "ArtifactPinError";
29634
+ this.code = code32;
29635
+ }
29636
+ };
29637
+ function artifactBindingView(envelope) {
29638
+ return {
29639
+ ...envelope.artifactPin === void 0 ? {} : { artifactPin: envelope.artifactPin },
29640
+ target: envelope.target,
29641
+ contentSource: envelope.contentSource,
29642
+ resource: envelope.resource,
29643
+ actions: envelope.actions,
29644
+ policyKvActions: envelope.policy.capabilityCeiling.flatMap((capability) => capability.kind === "kv" ? capability.actions : [])
29645
+ };
29646
+ }
29647
+ function sourcePath(view) {
29648
+ const resource = view.contentSource.kvResource;
29649
+ const marker = "/kv/";
29650
+ const at = resource.indexOf(marker);
29651
+ if (at <= 0 || resource.slice(0, at) !== view.target.spaceId) {
29652
+ throw new ArtifactPinError("pin-source-mismatch");
29653
+ }
29654
+ return resource.slice(at + marker.length);
29655
+ }
29656
+ function assertArtifactBinding(view, expected = {}) {
29657
+ const pin = view.artifactPin;
29658
+ const path = sourcePath(view);
29659
+ const inArtifacts = path === ARTIFACT_ROOT2 || path.startsWith(`${ARTIFACT_ROOT2}/`);
29660
+ if (!inArtifacts) {
29661
+ if (pin !== void 0) throw new ArtifactPinError("pin-source-mismatch");
29662
+ return void 0;
29663
+ }
29664
+ if (pin === void 0) throw new ArtifactPinError("missing-pin");
29665
+ const parsed = parseArtifactVersionPath2(path);
29666
+ if (parsed === void 0) throw new ArtifactPinError("unsupported-artifact-path");
29667
+ const file = pin.files[0];
29668
+ const source = view.contentSource;
29669
+ if (
29670
+ // The stored ciphertext of one file: bounded like content (100 MiB) plus the envelope's own framing.
29671
+ 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
29672
+ ) {
29673
+ throw new ArtifactPinError("pin-source-mismatch");
29674
+ }
29675
+ if (view.actions.includes("edit") || view.policyKvActions.includes("tinycloud.kv/put")) {
29676
+ throw new ArtifactPinError("pinned-share-writable");
29677
+ }
29678
+ if (expected.artifactId !== void 0 && expected.artifactId !== pin.artifactId || expected.version !== void 0 && expected.version !== pin.version) {
29679
+ throw new ArtifactPinError("expectation-mismatch");
29680
+ }
29681
+ return pin;
29682
+ }
29683
+ function verifyArtifactShare(envelope, options) {
29684
+ if (envelope.signature.signerDid !== options.expectedOwnerDid) {
29685
+ throw new ArtifactPinError("wrong-signer");
29686
+ }
29687
+ let valid;
29688
+ try {
29689
+ valid = verifyEnvelopeV3SignatureOnly(envelope) && envelope.policy.ownerDid === options.expectedOwnerDid;
29690
+ } catch {
29691
+ valid = false;
29692
+ }
29693
+ if (!valid) throw new ArtifactPinError("bad-signature");
29694
+ return assertArtifactBinding(artifactBindingView(envelope), options.expected);
29695
+ }
29582
29696
  var SHARE_RESULT_VERSION = 1;
29583
29697
  var DEFAULT_MAX_SEALED_BLOB_BYTES = 100 * 1024 * 1024 + 29;
29584
29698
  var ShareReceiveError = class extends Error {
@@ -29609,7 +29723,8 @@ function metadataFor(envelope, origin) {
29609
29723
  ...envelope.display.senderName === void 0 ? {} : { senderName: envelope.display.senderName },
29610
29724
  ...envelope.display.filename === void 0 ? {} : { filename: envelope.display.filename },
29611
29725
  ...envelope.display.mode === void 0 ? {} : { mode: envelope.display.mode }
29612
- }
29726
+ },
29727
+ ...envelope.artifactPin === void 0 ? {} : { artifact: { id: envelope.artifactPin.artifactId, version: envelope.artifactPin.version } }
29613
29728
  };
29614
29729
  }
29615
29730
  async function resolvePolicyShare(link3, options) {
@@ -29640,6 +29755,12 @@ async function resolvePolicyShare(link3, options) {
29640
29755
  } catch {
29641
29756
  throw new ShareReceiveError("signature-invalid", "share signature is invalid");
29642
29757
  }
29758
+ try {
29759
+ verifyArtifactShare(envelope, { expectedOwnerDid: envelope.policy.ownerDid });
29760
+ } catch (error) {
29761
+ if (!(error instanceof ArtifactPinError)) throw error;
29762
+ throw new ShareReceiveError("envelope-invalid", `share artifact pin is invalid (${error.code})`);
29763
+ }
29643
29764
  options.signal?.throwIfAborted();
29644
29765
  const expiry = Date.parse(envelope.expiry);
29645
29766
  if (!Number.isFinite(expiry)) throw new ShareReceiveError("envelope-invalid", "share expiry is invalid");
@@ -36148,6 +36269,20 @@ var v3TargetSchema3 = external_exports3.object({
36148
36269
  nodeAudience: external_exports3.string().min(1),
36149
36270
  spaceId: external_exports3.string().refine(isCanonicalPathSegment3, { message: "expected a canonical space id" })
36150
36271
  }).strict();
36272
+ var ARTIFACT_ID_PATTERN3 = /^[a-z0-9][a-z0-9_-]{2,62}$/;
36273
+ var artifactPinSchema3 = external_exports3.object({
36274
+ v: external_exports3.literal(1),
36275
+ artifactId: external_exports3.string().regex(ARTIFACT_ID_PATTERN3),
36276
+ version: external_exports3.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
36277
+ /** SHA-256 (lowercase hex) of the exact `manifest.json` bytes. The manifest itself stays an owner-side record. */
36278
+ manifestSha256: external_exports3.string().regex(/^[0-9a-f]{64}$/),
36279
+ /** An addressed share reads one exact file, so exactly one entry: the stored bytes (ciphertext) it will fetch. */
36280
+ files: external_exports3.array(external_exports3.object({
36281
+ path: external_exports3.string().min(1).max(255),
36282
+ sha256: external_exports3.string().regex(/^[0-9a-f]{64}$/),
36283
+ bytes: external_exports3.number().int().nonnegative()
36284
+ }).strict()).length(1)
36285
+ }).strict();
36151
36286
  var unsignedShareEnvelopeV3BaseSchema3 = external_exports3.object({
36152
36287
  version: external_exports3.literal(3),
36153
36288
  shareId: external_exports3.string().min(1),
@@ -36167,7 +36302,13 @@ var unsignedShareEnvelopeV3BaseSchema3 = external_exports3.object({
36167
36302
  expiry: external_exports3.string().datetime({ offset: true }),
36168
36303
  display: displaySchema3,
36169
36304
  encrypted: external_exports3.literal(true),
36170
- metadata: contentMetadataSchema3
36305
+ metadata: contentMetadataSchema3,
36306
+ /**
36307
+ * TC-1428: an owner-signed statement that this share reads one write-once artifact version. Additive: absent on
36308
+ * every other share, and covered by the envelope signature because the signature spans the whole unsigned object.
36309
+ * The shape is checked here; its binding to the content source is `verifyArtifactShare` in share-sdk.
36310
+ */
36311
+ artifactPin: artifactPinSchema3.optional()
36171
36312
  }).strict();
36172
36313
  function validateV3Invariants3(value, ctx) {
36173
36314
  const actions = [...value.actions];