@forwardimpact/libmacos
Advanced tools
+4
-1
| { | ||
| "name": "@forwardimpact/libmacos", | ||
| "version": "0.1.2", | ||
| "version": "0.1.3", | ||
| "description": "macOS bundle assembly, code signing, and OS permission helpers — desktop delivery without platform ceremony.", | ||
@@ -34,2 +34,5 @@ "keywords": [ | ||
| }, | ||
| "dependencies": { | ||
| "@forwardimpact/libutil": "^0.1.60" | ||
| }, | ||
| "devDependencies": { | ||
@@ -36,0 +39,0 @@ "@forwardimpact/libmock": "^0.1.0" |
+37
-16
@@ -9,6 +9,8 @@ // @ts-check | ||
| import { dlopen, ptr } from "bun:ffi"; | ||
| import { openSync, closeSync, readFileSync, unlinkSync } from "node:fs"; | ||
| import { randomUUID } from "node:crypto"; | ||
| import { tmpdir } from "node:os"; | ||
| import { join } from "node:path"; | ||
| import { createDefaultRuntime } from "@forwardimpact/libutil/runtime"; | ||
| // responsibility_spawnattrs_setdisclaim makes the spawned child disclaim | ||
@@ -100,7 +102,9 @@ // TCC "responsible process" status, so macOS checks the parent's responsible | ||
| * @param {string} filePath | ||
| * @param {object} [runtime] - Runtime collaborator bag | ||
| * @returns {string} | ||
| */ | ||
| export function readOutput(filePath) { | ||
| export function readOutput(filePath, runtime = createDefaultRuntime()) { | ||
| const { fsSync } = runtime; | ||
| try { | ||
| return readFileSync(filePath, "utf-8"); | ||
| return fsSync.readFileSync(filePath, "utf-8"); | ||
| } catch { | ||
@@ -110,3 +114,3 @@ return ""; | ||
| try { | ||
| unlinkSync(filePath); | ||
| fsSync.unlinkSync(filePath); | ||
| } catch { | ||
@@ -129,7 +133,15 @@ // temp file may already be gone | ||
| * @param {string} [cwd] - Working directory for the child process | ||
| * @param {object} [runtime] - Runtime collaborator bag | ||
| * @returns {{ pid: number, stdoutFile: string, stderrFile: string }} | ||
| */ | ||
| export function spawn(executable, args, env, cwd) { | ||
| export function spawn( | ||
| executable, | ||
| args, | ||
| env, | ||
| cwd, | ||
| runtime = createDefaultRuntime(), | ||
| ) { | ||
| const { proc, clock, fsSync } = runtime; | ||
| const argv = buildStringArray([executable, ...args]); | ||
| const envObj = env ?? { ...process.env }; | ||
| const envObj = env ?? { ...proc.env }; | ||
| const envStrings = Object.entries(envObj) | ||
@@ -140,8 +152,11 @@ .filter(([, v]) => typeof v === "string") | ||
| // Capture stdout/stderr via temp files instead of pipes. | ||
| const tag = `outpost-${process.pid}-${Date.now()}`; | ||
| // Capture stdout/stderr via temp files instead of pipes. The tag must be | ||
| // unique across concurrent spawns; `runtime.proc` exposes no pid, so a | ||
| // random UUID replaces the former `${pid}-${Date.now()}` scheme (clock.now() | ||
| // alone is not unique within a millisecond). | ||
| const tag = `outpost-${clock.now()}-${randomUUID()}`; | ||
| const stdoutFile = join(tmpdir(), `${tag}-stdout`); | ||
| const stderrFile = join(tmpdir(), `${tag}-stderr`); | ||
| const stdoutFd = openSync(stdoutFile, "w", 0o600); | ||
| const stderrFd = openSync(stderrFile, "w", 0o600); | ||
| const stdoutFd = fsSync.openSync(stdoutFile, "w", 0o600); | ||
| const stderrFd = fsSync.openSync(stderrFile, "w", 0o600); | ||
@@ -183,4 +198,4 @@ // Allocate attr and file_actions on the heap | ||
| // Close file fds in the parent (child has its own copies) | ||
| closeSync(stdoutFd); | ||
| closeSync(stderrFd); | ||
| fsSync.closeSync(stdoutFd); | ||
| fsSync.closeSync(stderrFd); | ||
@@ -192,3 +207,3 @@ libc.symbols.posix_spawnattr_destroy(attr); | ||
| try { | ||
| unlinkSync(stdoutFile); | ||
| fsSync.unlinkSync(stdoutFile); | ||
| } catch { | ||
@@ -198,3 +213,3 @@ // cleanup best-effort | ||
| try { | ||
| unlinkSync(stderrFile); | ||
| fsSync.unlinkSync(stderrFile); | ||
| } catch { | ||
@@ -214,5 +229,11 @@ // cleanup best-effort | ||
| * @param {number} [pollIntervalMs=100] - Polling interval in milliseconds | ||
| * @param {object} [runtime] - Runtime collaborator bag | ||
| * @returns {Promise<number>} Exit status | ||
| */ | ||
| export async function waitForExit(pid, pollIntervalMs = 100) { | ||
| export async function waitForExit( | ||
| pid, | ||
| pollIntervalMs = 100, | ||
| runtime = createDefaultRuntime(), | ||
| ) { | ||
| const { clock } = runtime; | ||
| const status = new Int32Array(1); | ||
@@ -226,4 +247,4 @@ while (true) { | ||
| // Child not yet exited — yield to event loop | ||
| await new Promise((resolve) => setTimeout(resolve, pollIntervalMs)); | ||
| await clock.sleep(pollIntervalMs); | ||
| } | ||
| } |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
28648
2.65%247
8.81%1
-50%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added