@lanes-sh/link 0.6.6 → 0.6.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lanes-sh/link",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
4
4
  "description": "A self-hostable MCP gateway for all your connections, memory, tasks, files, and secrets",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://lanes.sh/link",
@@ -50,6 +50,31 @@ export function bucketGrants(bucket: string, profiles: readonly string[]): Condi
50
50
  `resource.name.startsWith("projects/_/buckets/${bucket}/objects/${path}")`;
51
51
  const objectIs = (path: string): string =>
52
52
  `resource.name == "projects/_/buckets/${bucket}/objects/${path}"`;
53
+ /**
54
+ * The bucket itself, which is a different resource from anything in it.
55
+ *
56
+ * **`storage.objects.list` is checked against the bucket, never against an
57
+ * object**, so no condition written in terms of `objects/…` can ever grant it
58
+ * — including a prefixed listing, which is one call with a filter rather than
59
+ * a walk of matching resources. Google says so outright: IAM conditions cannot
60
+ * restrict listing by prefix.
61
+ *
62
+ * This was invisible until the day the narrowing actually applied. The
63
+ * `reads-its-config` binding had been sitting at `expression=true` since the
64
+ * first deploy, and `true` matches the bucket resource as readily as an object
65
+ * — so listing worked, and every deploy that thought it had replaced that
66
+ * binding had in fact only added one beside it. The first deploy that removed
67
+ * the old one rolled a revision that could not list its own workspace, exited
68
+ * 1, and took the endpoint's listing with it: `objects.list` had never been
69
+ * granted by anything else.
70
+ *
71
+ * Only `storage.objects.list` can come of it. Every other permission in
72
+ * `objectViewer` is either evaluated against an object — where the prefixes
73
+ * below still decide — or is a project-level permission a binding on a bucket
74
+ * does not reach. So the concession is the names of the objects in this
75
+ * bucket, and reads stay scoped to the config.
76
+ */
77
+ const theBucket = `resource.name == "projects/_/buckets/${bucket}"`;
53
78
 
54
79
  const manifestPrefixes = profiles.map((profile) =>
55
80
  objectsUnder(`${layout.providers(profile)}/`),
@@ -73,7 +98,7 @@ export function bucketGrants(bucket: string, profiles: readonly string[]): Condi
73
98
  // and each profile's own manifests, so name exactly those.
74
99
  role: 'roles/storage.objectViewer',
75
100
  title: 'reads-its-config',
76
- expression: `${objectsUnder('profiles/')} || ${objectIs('lanes-link.yaml')}${manifests ? ` || ${manifests}` : ''}`,
101
+ expression: `${theBucket} || ${objectsUnder('profiles/')} || ${objectIs('lanes-link.yaml')}${manifests ? ` || ${manifests}` : ''}`,
77
102
  },
78
103
  ];
79
104
  }
@@ -108,6 +133,17 @@ export async function bucketSteps(input: {
108
133
  // served publicly; uniform access removes per-object ACLs as a way to
109
134
  // get that wrong.
110
135
  '--uniform-bucket-level-access',
136
+ // Autoclass rather than a lifecycle rule, because no one fixed class is
137
+ // right for this bucket: it holds the config the endpoint reads on every
138
+ // boot next to assets and audit rows nobody opens again. Inside an
139
+ // Autoclass bucket there are no retrieval and no early-deletion fees,
140
+ // which is what makes ARCHIVE safe as the floor rather than a bet on
141
+ // never reading the thing again — a read pulls the object back to
142
+ // Standard at no charge. Objects under 128 KiB never leave Standard, so
143
+ // this costs the config and the log nothing and saves on attachments.
144
+ '--enable-autoclass',
145
+ '--autoclass-terminal-storage-class',
146
+ 'ARCHIVE',
111
147
  ],
112
148
  tolerateFailure: true,
113
149
  },