@openclaw/crabline 0.1.17 → 0.1.19

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.
Files changed (33) hide show
  1. package/README.md +8 -0
  2. package/dist/src/openclaw/private-file.js +71 -24
  3. package/dist/src/openclaw/private-file.js.map +1 -1
  4. package/dist/src/platform/process-owned-lock.d.ts +5 -2
  5. package/dist/src/platform/process-owned-lock.js +20 -10
  6. package/dist/src/platform/process-owned-lock.js.map +1 -1
  7. package/dist/src/servers/discord.js +25 -35
  8. package/dist/src/servers/discord.js.map +1 -1
  9. package/dist/src/servers/http.d.ts +3 -0
  10. package/dist/src/servers/http.js +15 -0
  11. package/dist/src/servers/http.js.map +1 -1
  12. package/dist/src/servers/matrix.js +8 -8
  13. package/dist/src/servers/matrix.js.map +1 -1
  14. package/dist/src/servers/mattermost.js +7 -10
  15. package/dist/src/servers/mattermost.js.map +1 -1
  16. package/dist/src/servers/recorder.d.ts +16 -6
  17. package/dist/src/servers/recorder.js +52 -12
  18. package/dist/src/servers/recorder.js.map +1 -1
  19. package/dist/src/servers/signal.js +8 -8
  20. package/dist/src/servers/signal.js.map +1 -1
  21. package/dist/src/servers/slack.js +8 -8
  22. package/dist/src/servers/slack.js.map +1 -1
  23. package/dist/src/servers/telegram.d.ts +2 -2
  24. package/dist/src/servers/telegram.js +8 -8
  25. package/dist/src/servers/telegram.js.map +1 -1
  26. package/dist/src/servers/whatsapp-baileys-websocket.js +4 -3
  27. package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
  28. package/dist/src/servers/whatsapp.js +13 -18
  29. package/dist/src/servers/whatsapp.js.map +1 -1
  30. package/dist/src/servers/zalo.js +8 -7
  31. package/dist/src/servers/zalo.js.map +1 -1
  32. package/docs/channel-setup.md +14 -1
  33. package/package.json +11 -11
package/README.md CHANGED
@@ -187,6 +187,14 @@ provider server, or `startOpenClawCrablineAdapter`. Crabline awaits the callback
187
187
  after appending each API/admin event to `recorderPath`, so callers can react in
188
188
  process while retaining the JSONL artifact as durable evidence.
189
189
 
190
+ Provider servers initialize recorder ownership before reporting readiness,
191
+ without creating recorder files or emitting synthetic events. Closing a server
192
+ stops new recorder admissions and waits for admitted persistence to finish.
193
+ Shutdown does not wait for event observers, which may themselves call `close()`.
194
+ Concurrent and repeated close calls share the same shutdown. All transports are
195
+ closed even if one fails, and the persistence drain includes asynchronous cleanup
196
+ after a failed recorder lock acquisition.
197
+
190
198
  Recorder files should normally have one filesystem name. If multiple processes
191
199
  cannot share the same OS account home, the home is read-only, or they write
192
200
  through hardlinks to the same recorder inode, set
@@ -730,28 +730,80 @@ async function syncPathAncestry(filePath, syncParent, platform, firstCreatedDire
730
730
  currentPath = parentPath;
731
731
  }
732
732
  }
733
- async function darwinDirectoryHasExtendedAcl(directoryPath) {
733
+ // Directory rights from chmod(1); inheritance flags are deliberately not rights.
734
+ const DARWIN_DIRECTORY_ACL_RIGHTS = new Set([
735
+ "list",
736
+ "add_file",
737
+ "search",
738
+ "delete",
739
+ "add_subdirectory",
740
+ "delete_child",
741
+ "readattr",
742
+ "writeattr",
743
+ "readextattr",
744
+ "writeextattr",
745
+ "readsecurity",
746
+ "writesecurity",
747
+ "chown",
748
+ ]);
749
+ async function readDarwinDirectoryAcl(directoryPath) {
734
750
  if (process.platform !== "darwin") {
735
- return false;
751
+ return "none";
736
752
  }
753
+ const directory = await captureDirectoryIdentity(directoryPath);
737
754
  let output;
738
755
  try {
739
- const result = await execFileAsync("/bin/ls", ["-lde", directoryPath], {
756
+ // A literal basename prevents newline-containing paths from impersonating ACEs.
757
+ const result = await execFileAsync("/bin/ls", ["-lde", "."], {
758
+ cwd: directoryPath,
740
759
  encoding: "utf8",
741
760
  env: { ...process.env, LC_ALL: "C" },
742
761
  maxBuffer: 64 * 1024,
743
762
  });
763
+ if (result.stderr.length > 0) {
764
+ throw new Error("macOS ACL inspection reported a diagnostic.");
765
+ }
744
766
  output = result.stdout;
745
767
  }
746
768
  catch (error) {
747
769
  throw new Error("Could not verify the private directory macOS ACL.", { cause: error });
748
770
  }
749
- const mode = output.trimStart().split(/\s+/u, 1)[0] ?? "";
750
- return mode.includes("+");
771
+ await directory.assertIdentityAt();
772
+ for (const character of output) {
773
+ const code = character.charCodeAt(0);
774
+ if ((code < 32 && character !== "\n") || (code >= 127 && code <= 159)) {
775
+ return "unsafe";
776
+ }
777
+ }
778
+ const [header, ...entries] = output.slice(0, -1).split("\n");
779
+ const mode = header?.match(/^d[r-][w-][xsS-][r-][w-][xsS-][r-][w-][xtT-]([+@]?) +[1-9]\d* +\S+ +\S+ +\d+ +[A-Z][a-z]{2} +\d{1,2} +(?:\d{2}:\d{2}|\d{4}) +\.$/u);
780
+ if (!output.endsWith("\n") || !mode) {
781
+ return "unsafe";
782
+ }
783
+ // Extended attributes take precedence over '+' in ls, even when an ACL exists.
784
+ if (entries.length === 0) {
785
+ return mode[1] === "+" ? "unsafe" : "none";
786
+ }
787
+ if (mode[1] === "") {
788
+ return "unsafe";
789
+ }
790
+ for (const [index, entry] of entries.entries()) {
791
+ // 'inherited' records origin; unlike *_inherit flags it does not propagate.
792
+ const ace = entry.match(/^ +(\d+): (?:user|group):\S+ (?:inherited )?deny ([a-z_]+(?:,[a-z_]+)*)$/u);
793
+ if (!ace ||
794
+ ace[1] !== String(index) ||
795
+ !ace[2].split(",").every((right) => DARWIN_DIRECTORY_ACL_RIGHTS.has(right))) {
796
+ return "unsafe";
797
+ }
798
+ }
799
+ return "nonpropagating-deny";
751
800
  }
752
- async function assertDarwinDirectoryHasNoExtendedAcl(directoryPath) {
753
- if (await darwinDirectoryHasExtendedAcl(directoryPath)) {
754
- throw new Error("Private directory must not have a macOS extended ACL.");
801
+ async function assertDarwinDirectoryAcl(directoryPath, scope) {
802
+ const acl = await readDarwinDirectoryAcl(directoryPath);
803
+ if (acl === "unsafe" || (scope === "private" && acl !== "none")) {
804
+ throw new Error(scope === "private"
805
+ ? "Private directory must not have a macOS extended ACL."
806
+ : "Private mutation ancestry has an unsafe or unverifiable macOS ACL.");
755
807
  }
756
808
  }
757
809
  async function removeDarwinExtendedAcl(directoryPath) {
@@ -812,7 +864,7 @@ async function assertDarwinCreatedAncestryHasNoExtendedAcl(firstCreatedDirectory
812
864
  currentPath = parentPath;
813
865
  }
814
866
  for (const createdPath of createdPaths.reverse()) {
815
- await assertDarwinDirectoryHasNoExtendedAcl(createdPath);
867
+ await assertDarwinDirectoryAcl(createdPath, "private");
816
868
  }
817
869
  }
818
870
  async function captureSingleSafePrivateMutationBoundary(directoryPath, platform, stickyTargetOwnedByCurrentUser) {
@@ -824,6 +876,7 @@ async function captureSingleSafePrivateMutationBoundary(directoryPath, platform,
824
876
  if (currentUserId === undefined || !Number.isSafeInteger(currentUserId) || currentUserId < 0) {
825
877
  throw new Error("Could not resolve the current POSIX user for private mutation.");
826
878
  }
879
+ const boundary = await captureDirectoryIdentity(directoryPath);
827
880
  const stats = await fs.lstat(directoryPath, { bigint: true });
828
881
  if (!stats.isDirectory()) {
829
882
  throw new Error("Private mutation boundary is not a directory.");
@@ -837,8 +890,9 @@ async function captureSingleSafePrivateMutationBoundary(directoryPath, platform,
837
890
  if (writableByAnotherPrincipal && !protectedByStickyOwnership) {
838
891
  throw new Error("Private mutation boundary is writable by another POSIX principal.");
839
892
  }
840
- await assertDarwinDirectoryHasNoExtendedAcl(directoryPath);
841
- return await captureDirectoryIdentity(directoryPath);
893
+ await assertDarwinDirectoryAcl(directoryPath, "ancestor");
894
+ await boundary.assertIdentityAt();
895
+ return boundary;
842
896
  }
843
897
  async function captureSafePrivateMutationBoundary(directoryPath, platform, stickyTargetOwnedByCurrentUser) {
844
898
  const resolvedBoundaryPath = path.resolve(directoryPath);
@@ -993,7 +1047,7 @@ export async function securePrivateDirectory(directoryPath, options = {}) {
993
1047
  }
994
1048
  }
995
1049
  if (!existed) {
996
- await assertDarwinDirectoryHasNoExtendedAcl(path.dirname(directoryPath));
1050
+ await assertDarwinDirectoryAcl(path.dirname(directoryPath), "ancestor");
997
1051
  }
998
1052
  try {
999
1053
  await fs.mkdir(directoryPath, { mode: 0o700 });
@@ -1059,7 +1113,7 @@ export async function securePrivateDirectory(directoryPath, options = {}) {
1059
1113
  const currentStats = await handle.stat({ bigint: true });
1060
1114
  const mutationRootMode = options.markMutationRoot === false ? currentStats.mode & 512n : 512n;
1061
1115
  await handle.chmod(Number(448n | mutationRootMode));
1062
- await assertDarwinDirectoryHasNoExtendedAcl(directoryPath);
1116
+ await assertDarwinDirectoryAcl(directoryPath, "private");
1063
1117
  await (options.syncDirectory ?? (() => handle.sync()))();
1064
1118
  }
1065
1119
  await secured.assertIdentityAt();
@@ -1409,15 +1463,6 @@ async function acquireHardLinkPrivateMutationClaim(parent, options) {
1409
1463
  (runtime.processIdentity.length === 0 || runtime.processIdentity.length > 256))) {
1410
1464
  throw new Error("Private path mutation claim runtime is invalid.");
1411
1465
  }
1412
- if (!Number.isSafeInteger(runtime.pid) ||
1413
- runtime.pid <= 0 ||
1414
- !PRIVATE_MUTATION_CLAIM_OWNER_ID_PATTERN.test(runtime.ownerId) ||
1415
- !Number.isSafeInteger(runtime.processStartedAtMs) ||
1416
- runtime.processStartedAtMs <= 0 ||
1417
- (runtime.processIdentity !== undefined &&
1418
- (runtime.processIdentity.length === 0 || runtime.processIdentity.length > 256))) {
1419
- throw new Error("Private path mutation claim runtime is invalid.");
1420
- }
1421
1466
  const ownerContents = `${JSON.stringify({
1422
1467
  ownerId: runtime.ownerId,
1423
1468
  pid: runtime.pid,
@@ -2138,17 +2183,19 @@ async function captureOwnerOnlyPrivateClaimAncestor(directoryPath, platform) {
2138
2183
  if (currentUserId === undefined || !Number.isSafeInteger(currentUserId) || currentUserId < 0) {
2139
2184
  throw new Error("Could not resolve the current POSIX user for private mutation claims.");
2140
2185
  }
2186
+ const directory = await captureDirectoryIdentity(directoryPath);
2141
2187
  const stats = await fs.lstat(directoryPath, { bigint: true });
2142
2188
  if (!stats.isDirectory()) {
2143
2189
  throw new Error("Private mutation claim ancestry contains a non-directory entry.");
2144
2190
  }
2145
2191
  if (stats.uid !== BigInt(currentUserId) ||
2146
2192
  (stats.mode & 18n) !== 0n ||
2147
- (await darwinDirectoryHasExtendedAcl(directoryPath))) {
2193
+ (await readDarwinDirectoryAcl(directoryPath)) === "unsafe") {
2148
2194
  return null;
2149
2195
  }
2196
+ await directory.assertIdentityAt();
2150
2197
  return {
2151
- directory: await captureDirectoryIdentity(directoryPath),
2198
+ directory,
2152
2199
  mutationRoot: (stats.mode & 512n) !== 0n,
2153
2200
  };
2154
2201
  }