@byokit/openclaw 0.3.0 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.1 (2026-09-30)
6
+
7
+
8
+
9
+ - FIX: `authSeal` no longer rejects retained-login migration when engine state contains symlinks or runtime entries. File symlinks whose fully resolved targets are regular files inside the isolated engine root are sealed and restored as regular files at the link paths; outside-root, dangling and directory symlinks (including loops), sockets, FIFOs and devices are skipped without reading their contents.
10
+ - Depends on @byokit/ui-core 0.4.0.
11
+
5
12
  ## 0.3.0 (2026-09-30)
6
13
 
7
14
 
package/README.md CHANGED
@@ -196,7 +196,9 @@ const server = new OpenClawKit({
196
196
  Without `authSeal`, engine credentials remain plaintext. With it, successful `prepare()` and `stop()` leave
197
197
  only a sealed snapshot, `auth-store.sealed`, for those directories. The adapter authenticates the snapshot
198
198
  before any restoration; a wrong key, tampering, or a missing adapter rejects. Files restored for the engine
199
- have mode 0600 and directories 0700. Symlinks and special files inside the sealed directories are refused.
199
+ have mode 0600 and directories 0700. File symlinks are sealed only when their fully resolved targets are
200
+ regular files inside the isolated engine root; they restore as regular files at the link paths. Outside-root,
201
+ dangling and directory symlinks (including loops), sockets, FIFOs and devices are skipped.
200
202
  One kit owns the store at a time; a live owner or orphan gateway blocks preparation instead of racing its writes.
201
203
  Sealing copies the whole store through memory, so startup and stop cost grows with session history.
202
204
 
@@ -1,7 +1,7 @@
1
1
  // The pin persists OAuth JSON in both shared and agent SQLite. There is no public persistence hook.
2
2
  // Seal the complete isolated stores, including journals, only after their writer has exited.
3
- import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, closeSync, fsyncSync, openSync } from 'node:fs';
4
- import { dirname, join, relative, resolve } from 'node:path';
3
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync, closeSync, fsyncSync, openSync } from 'node:fs';
4
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
5
5
  const encoder = new TextEncoder();
6
6
  const archive = (name) => /^(auth-profiles|auth-state|auth|oauth)\.json\.(migrated-.+|sqlite-import\..+\.bak)$/.test(name)
7
7
  || name.endsWith('.moved-to-engine');
@@ -18,7 +18,7 @@ const live = (pid) => {
18
18
  };
19
19
  function regular(path) {
20
20
  if (!lstatSync(path).isFile())
21
- throw new Error('credential store requires regular files');
21
+ throw new Error(`credential store requires regular files: ${path}`);
22
22
  }
23
23
  function syncDir(path) {
24
24
  if (process.platform === 'win32')
@@ -153,6 +153,7 @@ export class AuthStore {
153
153
  }
154
154
  collect() {
155
155
  const s = { v: 1, dirs: [], files: [] };
156
+ const root = realpathSync(this.o.root);
156
157
  const walk = (path) => {
157
158
  const name = relative(this.o.root, path).split('\\').join('/');
158
159
  const stat = lstatSync(path);
@@ -163,9 +164,27 @@ export class AuthStore {
163
164
  walk(join(path, child));
164
165
  return;
165
166
  }
166
- regular(path);
167
- chmodSync(path, 0o600);
168
- s.files.push([name, readFileSync(path).toString('base64')]);
167
+ let file = path;
168
+ if (stat.isSymbolicLink()) {
169
+ try {
170
+ file = realpathSync(path);
171
+ }
172
+ catch (error) {
173
+ if (['ENOENT', 'ENOTDIR', 'ELOOP'].includes(error.code ?? ''))
174
+ return;
175
+ throw error;
176
+ }
177
+ const target = relative(root, file);
178
+ if (isAbsolute(target) || target === '..' || target.startsWith(`..${sep}`))
179
+ return;
180
+ if (!lstatSync(file).isFile())
181
+ return;
182
+ }
183
+ else if (!stat.isFile())
184
+ return;
185
+ // Read the checked target, but save the link's path: restore materializes a regular file there.
186
+ chmodSync(file, 0o600);
187
+ s.files.push([name, readFileSync(file).toString('base64')]);
169
188
  };
170
189
  for (const dir of ['state', 'home'])
171
190
  if (existsSync(join(this.o.root, dir)))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byokit/openclaw",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "The OpenClaw runtime kit: the pinned engine's full operator surface with plain-words helpers for members, sign-in, runs and approvals.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -61,7 +61,7 @@
61
61
  "ws": "8.21.3"
62
62
  },
63
63
  "devDependencies": {
64
- "@byokit/ui-core": "0.3.0"
64
+ "@byokit/ui-core": "0.4.0"
65
65
  },
66
66
  "publishConfig": {
67
67
  "access": "public"