@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.
- package/README.md +8 -0
- package/dist/src/openclaw/private-file.js +71 -24
- package/dist/src/openclaw/private-file.js.map +1 -1
- package/dist/src/platform/process-owned-lock.d.ts +5 -2
- package/dist/src/platform/process-owned-lock.js +20 -10
- package/dist/src/platform/process-owned-lock.js.map +1 -1
- package/dist/src/servers/discord.js +25 -35
- package/dist/src/servers/discord.js.map +1 -1
- package/dist/src/servers/http.d.ts +3 -0
- package/dist/src/servers/http.js +15 -0
- package/dist/src/servers/http.js.map +1 -1
- package/dist/src/servers/matrix.js +8 -8
- package/dist/src/servers/matrix.js.map +1 -1
- package/dist/src/servers/mattermost.js +7 -10
- package/dist/src/servers/mattermost.js.map +1 -1
- package/dist/src/servers/recorder.d.ts +16 -6
- package/dist/src/servers/recorder.js +52 -12
- package/dist/src/servers/recorder.js.map +1 -1
- package/dist/src/servers/signal.js +8 -8
- package/dist/src/servers/signal.js.map +1 -1
- package/dist/src/servers/slack.js +8 -8
- package/dist/src/servers/slack.js.map +1 -1
- package/dist/src/servers/telegram.d.ts +2 -2
- package/dist/src/servers/telegram.js +8 -8
- package/dist/src/servers/telegram.js.map +1 -1
- package/dist/src/servers/whatsapp-baileys-websocket.js +4 -3
- package/dist/src/servers/whatsapp-baileys-websocket.js.map +1 -1
- package/dist/src/servers/whatsapp.js +13 -18
- package/dist/src/servers/whatsapp.js.map +1 -1
- package/dist/src/servers/zalo.js +8 -7
- package/dist/src/servers/zalo.js.map +1 -1
- package/docs/channel-setup.md +14 -1
- 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
|
-
|
|
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
|
|
751
|
+
return "none";
|
|
736
752
|
}
|
|
753
|
+
const directory = await captureDirectoryIdentity(directoryPath);
|
|
737
754
|
let output;
|
|
738
755
|
try {
|
|
739
|
-
|
|
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
|
-
|
|
750
|
-
|
|
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
|
|
753
|
-
|
|
754
|
-
|
|
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
|
|
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
|
|
841
|
-
|
|
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
|
|
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
|
|
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
|
|
2193
|
+
(await readDarwinDirectoryAcl(directoryPath)) === "unsafe") {
|
|
2148
2194
|
return null;
|
|
2149
2195
|
}
|
|
2196
|
+
await directory.assertIdentityAt();
|
|
2150
2197
|
return {
|
|
2151
|
-
directory
|
|
2198
|
+
directory,
|
|
2152
2199
|
mutationRoot: (stats.mode & 512n) !== 0n,
|
|
2153
2200
|
};
|
|
2154
2201
|
}
|