halo-dev/halo · warning · Error

ESM UI provider output must contain at most one entry styles

Error message

ESM UI provider output must contain at most one entry stylesheet.

What it means

normalizeResourcePath Path.normalize()s the relative path and rejects results that are absolute, start with "..", or equal "." — i.e. attempts to escape the provider root. Caught upstream and surfaced as ClassifiedProvider.invalid.

Source

Thrown at ui/packages/ui-plugin-bundler-kit/src/rsbuild-esm.ts:79

            );
          }
          const entryCode = entry.source().toString();
          await validator.validateSource(entryCode, entryFile);
          if (
            !/\bexport\s+default\b/.test(entryCode) &&
            !/\bexport\s*\{[^}]*\bdefault\b[^}]*\}/s.test(entryCode)
          ) {
            throw new Error(
              "ESM UI provider output must expose a default PluginModule export."
            );
          }
          const entryStyles =
            compilation.entrypoints
              .get("main")
              ?.getFiles()
              .filter((fileName) => fileName.endsWith(".css")) || [];
          if (entryStyles.length > 1) {
            throw new Error(
              "ESM UI provider output must contain at most one entry stylesheet."
            );
          }
          const manifest = validateEsmProviderManifest({
            format: "esm",
            entry: `./${entryFile}`,
            ...(entryStyles[0] ? { style: `./${entryStyles[0]}` } : {}),
          });
          compilation.emitAsset(
            ESM_PROVIDER_MANIFEST,
            new sources.RawSource(`${JSON.stringify(manifest, null, 2)}\n`)
          );
          const report = validator.getBuildReport();
          console.info(report.summary);
          if (report.warning) {
            console.warn(report.warning);
          }
        }

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Keep entry/style strictly inside the provider root (console/) — no '..' segments.
  2. Reference only files actually shipped under the plugin's console assets.
  3. Treat this as a security signal: never let user input flow into manifest paths unchecked.

Example fix

// before — escapes the provider root
{
  "format": "esm",
  "entry": "../../other/index.js"
}
// after — file inside console/
{
  "format": "esm",
  "entry": "index.js"
}
Defensive patterns

Strategy: validation

Validate before calling

Path n = Path.of(entry.replace('\\', '/')).normalize();
if (n.isAbsolute() || n.startsWith("..") || n.toString().equals(".")) {
    throw new IllegalStateException("entry escapes provider root: " + entry);
}

Prevention

When it happens

Trigger: ui-plugin.json's entry/style contains parent traversal (e.g. "../../secret.js") or normalizes to the current directory ".".

Common situations: Author tries to reference a file outside console/; malicious/buggy manifest aiming at sibling plugin assets; path collapsing to '.' after normalization.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/20e06b897a1a069e. Report an issue: GitHub.