Skip to content

Commit 88b2142

Browse files
committed
fix(plugins): reject invalid inferred package runtimes
1 parent 7482754 commit 88b2142

3 files changed

Lines changed: 45 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Docs: https://docs.openclaw.ai
6161

6262
### Fixes
6363

64+
- Plugins/packages: reject inferred built runtime entries that exist but fail package-boundary checks instead of falling back to TypeScript source for installed packages. Thanks @vincentkoc.
6465
- Plugins/loader: do not retry native-loaded JavaScript plugin modules through the source transformer after native evaluation has already reached a missing dependency, avoiding duplicate top-level side effects. Thanks @vincentkoc.
6566
- Plugins/packages: reject blank `openclaw.runtimeExtensions` entries instead of silently ignoring them and falling back to inferred TypeScript runtime entries. Thanks @vincentkoc.
6667
- Doctor/plugins: remove stale managed npm plugin shadow entries from the managed package lock as well as `package.json` and `node_modules`, so future npm operations do not keep referencing repaired bundled-plugin shadows. Thanks @vincentkoc.

src/plugins/discovery.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,6 +1505,39 @@ describe("discoverOpenClawPlugins", () => {
15051505
return true;
15061506
},
15071507
},
1508+
{
1509+
name: "rejects hardlinked inferred built runtime entries instead of falling back to source",
1510+
expectedDiagnostic: "escapes" as const,
1511+
expectedId: "pack",
1512+
setup: (stateDir: string) => {
1513+
if (process.platform === "win32") {
1514+
return false;
1515+
}
1516+
const globalExt = path.join(stateDir, "extensions", "pack");
1517+
const outsideDir = path.join(stateDir, "outside");
1518+
const outsideFile = path.join(outsideDir, "index.js");
1519+
const linkedFile = path.join(globalExt, "dist", "index.js");
1520+
mkdirSafe(path.join(globalExt, "src"));
1521+
mkdirSafe(path.dirname(linkedFile));
1522+
mkdirSafe(outsideDir);
1523+
fs.writeFileSync(path.join(globalExt, "src", "index.ts"), "export default {}", "utf-8");
1524+
fs.writeFileSync(outsideFile, "export default {}", "utf-8");
1525+
try {
1526+
fs.linkSync(outsideFile, linkedFile);
1527+
} catch (err) {
1528+
if ((err as NodeJS.ErrnoException).code === "EXDEV") {
1529+
return false;
1530+
}
1531+
throw err;
1532+
}
1533+
writePluginPackageManifest({
1534+
packageDir: globalExt,
1535+
packageName: "@openclaw/pack",
1536+
extensions: ["./src/index.ts"],
1537+
});
1538+
return true;
1539+
},
1540+
},
15081541
] as const)("$name", async ({ setup, expectedDiagnostic, expectedId }) => {
15091542
const stateDir = makeTempDir();
15101543
await expectRejectedPackageExtensionEntry({

src/plugins/package-entry-resolution.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -434,19 +434,20 @@ function resolveSafePackageEntry(params: {
434434
return { relativePath: path.relative(params.packageDir, absolutePath).replace(/\\/g, "/") };
435435
}
436436

437-
function resolveExistingPackageEntrySource(params: {
437+
function resolveOptionalExistingPackageEntrySource(params: {
438438
packageDir: string;
439439
packageRootRealPath?: string;
440440
entryPath: string;
441441
sourceLabel: string;
442442
diagnostics: PluginDiagnostic[];
443443
rejectHardlinks?: boolean;
444-
}): string | null {
444+
}): { status: "missing" } | { status: "invalid" } | { status: "resolved"; source: string } {
445445
const source = path.resolve(params.packageDir, params.entryPath);
446446
if (!fs.existsSync(source)) {
447-
return null;
447+
return { status: "missing" };
448448
}
449-
return resolvePackageEntrySource(params);
449+
const resolved = resolvePackageEntrySource(params);
450+
return resolved ? { status: "resolved", source: resolved } : { status: "invalid" };
450451
}
451452

452453
function resolvePackageRuntimeEntrySource(params: {
@@ -499,7 +500,7 @@ function resolvePackageRuntimeEntrySource(params: {
499500
if (shouldInferBuiltRuntimeEntry(params.origin)) {
500501
const builtEntryCandidates = listBuiltRuntimeEntryCandidates(safeEntry.relativePath);
501502
for (const candidate of builtEntryCandidates) {
502-
const runtimeSource = resolveExistingPackageEntrySource({
503+
const runtimeSource = resolveOptionalExistingPackageEntrySource({
503504
packageDir: params.packageDir,
504505
...(params.packageRootRealPath !== undefined
505506
? { packageRootRealPath: params.packageRootRealPath }
@@ -509,8 +510,11 @@ function resolvePackageRuntimeEntrySource(params: {
509510
diagnostics: params.diagnostics,
510511
rejectHardlinks: params.rejectHardlinks,
511512
});
512-
if (runtimeSource) {
513-
return runtimeSource;
513+
if (runtimeSource.status === "resolved") {
514+
return runtimeSource.source;
515+
}
516+
if (runtimeSource.status === "invalid") {
517+
return null;
514518
}
515519
}
516520
if (

0 commit comments

Comments
 (0)