github/copilot-sdk · error · IOException

Unsafe runtime asset inventory path:

Error message

Unsafe runtime asset inventory path: 

What it means

NativeRuntimeLoader extracts native runtime assets listed in an inventory file into a local cache directory. Each inventory line is '<octal-mode> <path>'. This error is thrown when the path part of an inventory entry is absolute or, after normalization, starts with '..' — i.e. it does not stay relative to the cache directory. The library rejects such entries defensively to prevent path-traversal style escapes before anything is written to disk.

Solutions

  1. Verify the integrity of the jar/classpath artifact containing the runtime asset inventory (checksum, re-download from official source).
  2. Inspect the inventory resource 'native/<classifier>/.../inventory' and fix any entry so paths are plain relative paths with no '..' segments.
  3. Ensure no custom ClassLoader is injecting an alternate inventory resource that shadows the bundled one.

Example fix

// before (inventory line)
0 sept 755 ../../evil/node
// after
0 sept 755 bin/node
Defensive patterns

Strategy: validation

Validate before calling

// Java: nothing to pre-validate as a caller (inventory is internal), but you can pre-scan the artifact
URL inv = loader.getResource(inventoryResourcePath);
try (BufferedReader r = new BufferedReader(new InputStreamReader(inv.openStream(), StandardCharsets.UTF_8))) {
    for (String line; (line = r.readLine()) != null; ) {
        String p = line.substring(line.indexOf(' ') + 1).trim();
        if (Path.of(p).isAbsolute() || Path.of(p).normalize().startsWith(".."))
            throw new IllegalStateException("Unsafe inventory entry: " + p);
    }
}

Try / catch

try { loader.loadRuntime(); } catch (IOException e) { if (e.getMessage().startsWith("Unsafe runtime asset inventory path")) { /* replace artifact jar; do not retry */ } else throw e; }

Prevention

When it happens

Trigger: extractRuntimeAssetsToCache parses an inventory resource whose line's path field begins with '/', a drive letter, or contains '..' segments that survive normalization (e.g. '../../etc/passwd', '/usr/bin/node').

Common situations: A tampered or corrupted runtime asset inventory inside a repackaged/copilot-native jar; a hand-edited or generated inventory file with wrong path format; a malicious supply-chain artifact substituting the 'native/<classifier>/...' inventory.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/45ed2f0ba46f4960. Report an issue: GitHub.

Appendix: source

Thrown at java/sdk/src/main/java/com/github/copilot/ffi/NativeRuntimeLoader.java:461

        if (inventoryResource == null) {
            return;
        }

        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(inventoryResource.openStream(), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.isBlank()) {
                    continue;
                }
                String[] fields = line.split("\\t", 2);
                if (fields.length != 2) {
                    throw new IOException("Invalid runtime asset inventory entry: " + line);
                }
                boolean executable = (Integer.parseInt(fields[0], 8) & 0111) != 0;
                Path relative = Path.of(fields[1]).normalize();
                if (relative.isAbsolute() || relative.startsWith("..")) {
                    throw new IOException("Unsafe runtime asset inventory path: " + fields[1]);
                }
                Path cached = cacheDir.resolve(relative).normalize();
                if (!cached.startsWith(cacheDir)) {
                    throw new IOException("Runtime asset escapes cache directory: " + fields[1]);
                }
                if (isValidCachedFile(cached) && (!executable || isWindows() || Files.isExecutable(cached))) {
                    continue;
                }

                String resourcePath = "native/" + classifier + "/" + fields[1];
                URL resource = loader.getResource(resourcePath);
                if (resource == null) {
                    throw new FileNotFoundException("Runtime asset not found on classpath: " + resourcePath);
                }
                Files.createDirectories(cached.getParent());
                Path temp = Files.createTempFile(cached.getParent(), "runtime-asset-tmp-", "");
                try {
                    copyResourceToTemp(resource, resourcePath, temp);

View on GitHub (pinned to cd8cf15dc3)