Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b84db4b0a | ||
|
|
9809cf73e9 |
@@ -6,8 +6,8 @@ metadata:
|
|||||||
author: workspace-swiss-knife
|
author: workspace-swiss-knife
|
||||||
version: "2.0"
|
version: "2.0"
|
||||||
spec: agentskills.io/specification
|
spec: agentskills.io/specification
|
||||||
origin-repository: git@github.ibm.com:CTOTools-skills-code-agent/drawio-main.git
|
origin-repository: git@github.ibm.com:CTOTools-skills-code-agent/diagrams-drawio.git
|
||||||
origin-path: $HOME/projects-ibm/cognitive-architect/workspace-skills-code-agent/drawio-main
|
origin-path: $HOME/projects-skills-code-agent/ws-skills-code-agent/diagrams-drawio
|
||||||
repository: https://gitea.lego-cloud.eu/home-v1-skills-code-agent/diagrams-drawio
|
repository: https://gitea.lego-cloud.eu/home-v1-skills-code-agent/diagrams-drawio
|
||||||
compatibility: Designed for Cline, Claude Code, GitHub Copilot, OpenAI Codex, and other compatible agent environments
|
compatibility: Designed for Cline, Claude Code, GitHub Copilot, OpenAI Codex, and other compatible agent environments
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { constants } from "node:fs";
|
import { constants } from "node:fs";
|
||||||
import { open, readdir, realpath } from "node:fs/promises";
|
import { open, readdir, realpath, stat } from "node:fs/promises";
|
||||||
import type { FileHandle } from "node:fs/promises";
|
import type { FileHandle } from "node:fs/promises";
|
||||||
import { isAbsolute, relative, resolve } from "node:path";
|
import { isAbsolute, relative, resolve } from "node:path";
|
||||||
|
|
||||||
@@ -643,7 +643,7 @@ async function openConfined(candidate: string, root: string): Promise<{ handle:
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const canonical = await realpath(`/proc/self/fd/${handle.fd}`);
|
const canonical = await canonicalPathOfOpenHandle(handle, candidate);
|
||||||
if (!isConfined(root, canonical)) throw new Error("Opened source escapes outside declared root");
|
if (!isConfined(root, canonical)) throw new Error("Opened source escapes outside declared root");
|
||||||
return { handle, canonical };
|
return { handle, canonical };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -652,6 +652,35 @@ async function openConfined(candidate: string, root: string): Promise<{ handle:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let procFdAvailable: boolean | undefined;
|
||||||
|
|
||||||
|
/** Path to address an open directory by: its `/proc/self/fd/N` entry on Linux, else its verified canonical path. */
|
||||||
|
async function descriptorPath(handle: FileHandle, canonical: string): Promise<string> {
|
||||||
|
if (procFdAvailable === undefined) {
|
||||||
|
try { await realpath(`/proc/self/fd/${handle.fd}`); procFdAvailable = true; } catch { procFdAvailable = false; }
|
||||||
|
}
|
||||||
|
return procFdAvailable ? `/proc/self/fd/${handle.fd}` : canonical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the canonical path of an already-opened file. On Linux `/proc/self/fd/N` answers for the
|
||||||
|
* exact descriptor; on macOS/BSD (no procfs) fall back to resolving the candidate path and proving it
|
||||||
|
* names the same inode as the open handle, so a swap between open() and realpath() is still rejected.
|
||||||
|
*/
|
||||||
|
async function canonicalPathOfOpenHandle(handle: FileHandle, candidate: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
return await realpath(`/proc/self/fd/${handle.fd}`);
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
||||||
|
}
|
||||||
|
const canonical = await realpath(candidate);
|
||||||
|
const [opened, resolved] = await Promise.all([handle.stat(), stat(canonical)]);
|
||||||
|
if (opened.dev !== resolved.dev || opened.ino !== resolved.ino) {
|
||||||
|
throw new Error("Opened source does not match its canonical path");
|
||||||
|
}
|
||||||
|
return canonical;
|
||||||
|
}
|
||||||
|
|
||||||
async function readBounded(handle: FileHandle, limit: number, directory: boolean): Promise<Buffer> {
|
async function readBounded(handle: FileHandle, limit: number, directory: boolean): Promise<Buffer> {
|
||||||
const initial = await handle.stat();
|
const initial = await handle.stat();
|
||||||
if (!initial.isFile()) throw new Error("Source entry must be a regular file");
|
if (!initial.isFile()) throw new Error("Source entry must be a regular file");
|
||||||
@@ -711,12 +740,13 @@ export async function importSource(request: SourceImportRequest): Promise<Source
|
|||||||
|
|
||||||
const results: SourceImportResult[] = [];
|
const results: SourceImportResult[] = [];
|
||||||
let consumed = 0;
|
let consumed = 0;
|
||||||
const walk = async (directory: FileHandle): Promise<void> => {
|
const walk = async (directory: { handle: FileHandle; canonical: string }): Promise<void> => {
|
||||||
for (const name of (await readdir(`/proc/self/fd/${directory.fd}`)).sort(codePointCompare)) {
|
const base = await descriptorPath(directory.handle, directory.canonical);
|
||||||
const opened = await openConfined(resolve(`/proc/self/fd/${directory.fd}`, name), rootPath);
|
for (const name of (await readdir(base)).sort(codePointCompare)) {
|
||||||
|
const opened = await openConfined(resolve(base, name), rootPath);
|
||||||
try {
|
try {
|
||||||
const entryInfo = await opened.handle.stat();
|
const entryInfo = await opened.handle.stat();
|
||||||
if (entryInfo.isDirectory()) await walk(opened.handle);
|
if (entryInfo.isDirectory()) await walk(opened);
|
||||||
else if (entryInfo.isFile()) {
|
else if (entryInfo.isFile()) {
|
||||||
const dot = name.lastIndexOf("."); const extension = dot >= 0 ? name.slice(dot).toLowerCase() : "";
|
const dot = name.lastIndexOf("."); const extension = dot >= 0 ? name.slice(dot).toLowerCase() : "";
|
||||||
if (extensions[request.sourceKind].has(extension)) {
|
if (extensions[request.sourceKind].has(extension)) {
|
||||||
@@ -729,7 +759,7 @@ export async function importSource(request: SourceImportRequest): Promise<Source
|
|||||||
} finally { await opened.handle.close(); }
|
} finally { await opened.handle.close(); }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
try { await walk(source.handle); }
|
try { await walk(source); }
|
||||||
finally { await source.handle.close(); }
|
finally { await source.handle.close(); }
|
||||||
if (results.length === 0) throw new Error(`No supported ${request.sourceKind} source files found`);
|
if (results.length === 0) throw new Error(`No supported ${request.sourceKind} source files found`);
|
||||||
const nodeById = new Map<string, DiagramNode>(); const edgeById = new Map<string, DiagramEdge>(); const diagnostics: ImportDiagnostic[] = [];
|
const nodeById = new Map<string, DiagramNode>(); const edgeById = new Map<string, DiagramEdge>(); const diagnostics: ImportDiagnostic[] = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user