{"record":{"id":"45ed2f0ba46f4960","repo":"github/copilot-sdk","slug":"unsafe-runtime-asset-inventory-path","errorCode":null,"errorMessage":"Unsafe runtime asset inventory path: ","messagePattern":"Unsafe runtime asset inventory path: ","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"java/sdk/src/main/java/com/github/copilot/ffi/NativeRuntimeLoader.java","lineNumber":461,"sourceCode":"        if (inventoryResource == null) {\n            return;\n        }\n\n        try (BufferedReader reader = new BufferedReader(\n                new InputStreamReader(inventoryResource.openStream(), StandardCharsets.UTF_8))) {\n            String line;\n            while ((line = reader.readLine()) != null) {\n                if (line.isBlank()) {\n                    continue;\n                }\n                String[] fields = line.split(\"\\\\t\", 2);\n                if (fields.length != 2) {\n                    throw new IOException(\"Invalid runtime asset inventory entry: \" + line);\n                }\n                boolean executable = (Integer.parseInt(fields[0], 8) & 0111) != 0;\n                Path relative = Path.of(fields[1]).normalize();\n                if (relative.isAbsolute() || relative.startsWith(\"..\")) {\n                    throw new IOException(\"Unsafe runtime asset inventory path: \" + fields[1]);\n                }\n                Path cached = cacheDir.resolve(relative).normalize();\n                if (!cached.startsWith(cacheDir)) {\n                    throw new IOException(\"Runtime asset escapes cache directory: \" + fields[1]);\n                }\n                if (isValidCachedFile(cached) && (!executable || isWindows() || Files.isExecutable(cached))) {\n                    continue;\n                }\n\n                String resourcePath = \"native/\" + classifier + \"/\" + fields[1];\n                URL resource = loader.getResource(resourcePath);\n                if (resource == null) {\n                    throw new FileNotFoundException(\"Runtime asset not found on classpath: \" + resourcePath);\n                }\n                Files.createDirectories(cached.getParent());\n                Path temp = Files.createTempFile(cached.getParent(), \"runtime-asset-tmp-\", \"\");\n                try {\n                    copyResourceToTemp(resource, resourcePath, temp);","sourceCodeStart":443,"sourceCodeEnd":479,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/java/sdk/src/main/java/com/github/copilot/ffi/NativeRuntimeLoader.java#L443-L479","documentation":"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.","triggerScenarios":"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').","commonSituations":"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.","solutions":["Verify the integrity of the jar/classpath artifact containing the runtime asset inventory (checksum, re-download from official source).","Inspect the inventory resource 'native/<classifier>/.../inventory' and fix any entry so paths are plain relative paths with no '..' segments.","Ensure no custom ClassLoader is injecting an alternate inventory resource that shadows the bundled one."],"exampleFix":"// before (inventory line)\n0 sept 755 ../../evil/node\n// after\n0 sept 755 bin/node","handlingStrategy":"validation","validationCode":"// Java: nothing to pre-validate as a caller (inventory is internal), but you can pre-scan the artifact\nURL inv = loader.getResource(inventoryResourcePath);\ntry (BufferedReader r = new BufferedReader(new InputStreamReader(inv.openStream(), StandardCharsets.UTF_8))) {\n    for (String line; (line = r.readLine()) != null; ) {\n        String p = line.substring(line.indexOf(' ') + 1).trim();\n        if (Path.of(p).isAbsolute() || Path.of(p).normalize().startsWith(\"..\"))\n            throw new IllegalStateException(\"Unsafe inventory entry: \" + p);\n    }\n}","typeGuard":null,"tryCatchPattern":"try { loader.loadRuntime(); } catch (IOException e) { if (e.getMessage().startsWith(\"Unsafe runtime asset inventory path\")) { /* replace artifact jar; do not retry */ } else throw e; }","preventionTips":["Verify artifact checksums/signatures before putting native jars on the classpath","Never hand-edit inventory resources; regenerate them with the official tooling","Pin artifact versions from trusted repositories only"],"tags":["path-traversal","security","java","native-assets"],"backgroundTag":"path-traversal-blocked","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}