PAI Knowledge OS — Personal AI Infrastructure with federated memory and project management
AI Security Review
scanned 3d ago · by lpm-firewall-ai
The inspected scanner candidates do not establish credential theft or malicious exfiltration: the embedding transfer is a verified model download, and settings mutation is tied to explicit CLI setup. I could not resolve the broader runtime behavior in the large CLI and daemon bundles, so the verdict remains open.
Embedding download occurs when model provisioning is invoked; agent settings changes require a CLI setup command.
Impact
No confirmed harm was identified in the inspected excerpts; unresolved runtime behavior prevents a clean determination.
Mechanism
The inspected code fetches a model from Hugging Face and verifies it before use. The CLI can also update Claude settings; the complete runtime call paths were not resolved.
AI rationale
The inspected scanner candidates do not establish credential theft or malicious exfiltration: the embedding transfer is a verified model download, and settings mutation is tied to explicit CLI setup. I could not resolve the broader runtime behavior in the large CLI and daemon bundles, so the verdict remains open.
AI called this Manual Review at 65.0% confidence as Unknown with medium false-positive risk.
AI review observations
The manifest shows a prepublish-only hook; no preinstall, install, or postinstall hook was found in the inspected scripts.
The flagged embedding code downloads a model from Hugging Face and checks its size and SHA-256 before importing it into Ollama.
The CLI asks before patching Claude settings, and provider setup accepts an explicitly supplied API base URL.
Several runtime and configuration paths remain unresolved, so the inspected benign behavior does not establish that the package is clean.
Evidence against
The CLI can add hooks and permissions to the user's Claude settings and has a worker install command; the full behavior and reachability of these paths were not resolved.
1501* iTerm's split command parameter execs without a shell (proven live: panes
L1502: * survive `sleep 30` and a single-quoted `/bin/sh -c`, die instantly on
L1503: * `export …; …` or a redirect), so the command must be one exec-able line.
import { r as isLoopbackProvider } from "./run-env-DBJ8ml-Z.mjs";
L10: import { $ as waitingLine, D as bashExitCode, E as WAIT_EXIT_STILL_RUNNING, G as statusMarker, I as renderReport, J as detachedLine, K as statusQueue, O as isWaitPollCommand, P as ...
L11: import { E as probeLoopback, O as resolveLocalProvider, S as LOCAL_MECHANISM_LINE, k as resolveRunnerProvider } from "./budget-CuL0Q5aB.mjs";
...
L23: import { basename, dirname, join, relative, resolve } from "node:path";
L24: import { execFileSync, spawn } from "node:child_process";
L25: import { Document, parse } from "yaml";
...
L78: function agentsDir() {
L79: return join(homedir(), ".claude", "agents");
L80: }
...
L499: /**
L500: * `HH:MM:SS` at an offset east of UTC in minutes (Date.getTimezoneOffset()
L501: * negated), fro
•Manifest-trigger-reachable source links an external AI-agent control path to a behavior-bearing write operation.
dir(), "pai-sk[redacted]");
if (!existssync(templatepath)) {
console.log(c.warn("skill template not found: " + templatepath));
console.log(c.dim(" skipping pai skill installation."));
return false;
}
const skilldir = join(homedir(), ".claude", "skills", "pai");
const skillfile = join(skilldir, "skill.md");
if (existssync(skillfile)) {
const content = readfilesync(skillfile, "utf-8");
const isgenerated = content.includes("generated by pai setup");
if (isgenerated) console.log(c.dim(" found existing pai-generated skill.md."));
else {
console.log(c.yellow(" found existing skill.md (not pai-generated)."));
console.log(c.dim(" a backup will be created bef
High
Trigger Reachable External Ai Agent Control Surface Mutation
Manifest-trigger-reachable source writes behavior-bearing configuration into a user or project AI-agent control surface.
3Cross-file remote execution chain: dist/cli/index.mjs spawns dist/daemon-mcp/index.mjs; helper contains network access plus dynamic code execution.
L3: import { a as slugify$1, i as parseSessionFilename } from "../migrate-B1t833CE.mjs";
L4: import { $ as workersYamlPath, $t as readRunLock, A as nativeAnthropicProvider, An as effectiveCap, At as requestPlanStop, B as readWorkersSection, Bt as PlanTracker, Cn as buildSl...
L5: import { a as schedulerLogPath, c as migratePaiFile, d as resolvePaiFile, i as paiSocketPath, l as paiHomeDir, n as daemonLogPath, o as PaiFileMigrationError, s as migratePaiDir, u...
...
L48: import chalk from "chalk";
L49: import { execFile, execFileSync, execSync, spawn, spawnSync } from "node:child_process";
L50: import { parseDocument } from "yaml";
L51: import {
High
Cross File Remote Execution Context
Source spawns a local helper that also contains network and dynamic execution context; review data flow before blocking.
3Trigger-reachable persistence chain: manifest.bin -> dist/cli/index.mjs
L3: import { a as slugify$1, i as parseSessionFilename } from "../migrate-B1t833CE.mjs";
L4: import { $ as workersYamlPath, $t as readRunLock, A as nativeAnthropicProvider, An as effectiveCap, At as requestPlanStop, B as readWorkersSection, Bt as PlanTracker, Cn as buildSl...
L5: import { a as schedulerLogPath, c as migratePaiFile, d as resolvePaiFile, i as paiSocketPath, l as paiHomeDir, n as daemonLogPath, o as PaiFileMigrationError, s as migratePaiDir, u...
...
L48: import chalk from "chalk";
L49: import { execFile, execFileSync, execSync, spawn, spawnSync } from "node:child_process";
L50: import { parseDocument } from "yaml";
L51: import { createHash, randomBytes, randomUUID } from "node:crypto";
L52: import http f
High
Trigger Reachable Persistence
A manifest entrypoint or package-local install chain reaches persistence behavior.
3import { u as paiHomePath } from"./runtime-paths-DkZoxofH.mjs";
4import { t as assertTestWriteAllowed } from"./test-isolation-BKFK1vO5.mjs";
5import { t as appendLedger } from"./ledger-D6xicYlV.mjs";
6import { a as unregistrableReason } from"./registrable-Xmwi1FO9.mjs";
7import { n as ensurePaiMarker } from"./pai-marker-BR9rjM9k.mjs";
8import { t as STOP_WORDS } from"./stop-words-CjFmOPb9.mjs";
9import { i as budgetExceeded, l as budgetWall, r as budgetCache, s as budgetSkipReason } from"./budget-Cotw_0DB.mjs";
10import { E as SESSION_WORKER_PROVIDER_ENV } from"./run-env-DEXqzLFS.mjs";
11import { f as SESSION_NAME_ENV, m as getProfile, n as buildLaunchPlan, o as launchableProviderNames, s as maskLaunchEnv, t as LaunchError } from"./launch-DQV5wkM9.mjs";
12import { n as OPT, t as CliError } from"./define-azO7JMEA.mjs";
13import { i as getRegistryBackend } from"./factory-BcoI5nmc.mjs";
14import { n as formatDetection, r as formatDetectionJson, t as detectProject } from"./detect-BWm0RAdr.mjs";
15import { a as getSessionCount, c as upsertTag$1, i as getProjectTags, n as getProject$1, o as requireProject, r as getProjectAliases, s as resolveIdentifier, t as getLastSessionDate } from"./helpers-DMpsNOBl.mjs";
16import { a as resolveHome, n as loadScanConfig } from"./scan-GwQy6hh0.mjs";
17import { d as printExitDir, i as probeResume, u as transcriptModelFor } from"./launch-CqUR-B8J.mjs";
18import { n as fetchLiveSessions } from"./aibroker-client-CwLGWbMq.mjs";
19import { a as resolveSessionByNameOrId, s as scanSessions, t as fmtAge } from"./session-scan-DV6RtIQ4.mjs";
1import { c as ok, i as err, n as dim } from"./utils-DM8x34dB.mjs";
2import { $ as workersYamlPath, A as nativeAnthropicProvider, At as nowStamp, B as readWorkersSection, Bt as PROJECT_CONFIG_REL, Ct as isChatPane, D as isModelCapability, Dt
1import { B as readWorkersSection, E as isLocalProvider, Gn as loadStatuses, H as resolveModelCapability, Hn as isLive, kr as workersLogDir, lr as loadStatusConfig, pr as writeContinueToForge, tn as appendLedger, w as defaultWorkersConfig, xr as ensureNoMcpConfig } from"./config-CliCeFct.mjs"
1512/** Absolute path of the running pai CLI; falls back to PATH lookup at spawn. */
1513functionabsoluteCliPath() {
1514constargv1= process.argv[1] ??"";
1515if (argv1.endsWith("pai")) return argv1;
1516return"pai";
1517}
1518constrealHostDeps= {
1519psOf(pid) {
1520try {
1521const [ppid, tty] =execFileSync("ps", [
as
loadStatuses, Et
as
loadStatus, Ft
as
saveStatus, Gt
as
parseStatusConfig, H
as
resolveModelCapability, Ht
as
listClaims, Jt
as
writeContinueToForge, Kt
as
setClaim, Lt
as
setWorkerNote, O
as
keysDir, Pt
as
reconcileStatuses, R
as
providerCostTier, T
as
getProviderOrNative, Tt
as
isTerminal, U
as
resolveProviderKey, Ut
as
listStale, V
as
resolveCapability, W
as
writeWorkersSection, Wt
as
loadStatusConfig, X
as
readMergeV ...
3import { u as paiHomePath } from"./runtime-paths-BKtuLoJm.mjs";
4import { b as writeJsonAtomic, c as loadConfig, d as paiConfigYamlFilePath, f as readMainConfigRaw, h as migrateMainConfigToYaml, i as DEFAULTS, n as CONFIG_FILE, p as writeMainConfigRaw, s as expandHome, u as paiConfigFilePath, v as writeYamlFileAtomic, y as readJsonStrict } from"./config-D7w6FDaM.mjs";
5import { t as appendLedger } from"./ledger-D6xicYlV.mjs";
6import { E as pendingResults, S as worktreesStats, T as pendingGate, c as formatWorktreesStats, f as leftoverWorktrees, h as ownedTree, l as gcWorktrees, m as mergeWorker, o as discardWorker, s as formatKnowledge, v as verifyWorker, w as formatPending } from"./worktree-W53N7LGA.mjs";
7import { t as currentWorkerId } from"./worker-id-A-QUVZ3C.mjs";
8import { d as sessionTag, m as workerInScope, o as ownerSessionName, r as currentTabKey, s as resolveSession } from"./scope-BqOquiaZ.mjs";
9import { r as isLoopbackProvider } from"./run-env-DBJ8ml-Z.mjs";
10import { $ as waitingLine, D as bashExitCode, E as WAIT_EXIT_STILL_RUNNING, G as statusMarker, I as renderReport, J as detachedLine, K as statusQueue, O as isWaitPollCommand, P as parseWorkerReport, Q as waitOnResultLine, W as realDeps, X as realWaitDeps, Y as parseWaitOn, Z as waitOnCallLine, g as lastCommand, k as runWait, q as waitOn } from"./tool-errors-C66RGvbR.mjs";
11import { E as probeLoopback, O as resolveLocalProvider, S as LOCAL_MECHANISM_LINE, k as resolveRunnerProvider } from"./budget-CuL0Q5aB.mjs";
12import { i as detectSessionProvider } from"./session-provider-DNKHNN3i.mjs";
13import { a as clearCooldown, d as quotaSkipThreshold, h as resolveTarget$1, l as probeQuota } from"./routing-DF_qeHIK.mjs";
14import { d as BATCH_ENV } from"./launch-XCsS230Z.mjs";
15import { n as OPT, t as CliError } from"./define-JSQUwY3E.mjs";
16import { c as resolveFromModule } from"./postgres-DhpArsC9.mjs";
17import { At as renderUsage, Dt as resolveHostSession, Et as paneBackend, I as launchWorker, L as mergeBranch, Mt as usageJson, Nt as workerUsage, O as clickrControlsArgv, Ot as parseWhen, P as capture, Q as tail, Tt as openPaneForWorker, V as stopLaunchedWorkers, _ as resolveWorkers, _t as renderPlanLines, at as queueLockPath, bt as closeEndedPanes, c as deadlineLabel, ct as withRunLock, d as parseDenialText, dt as followPlanLog, f as handoffFromInside, g as liveResolveCtx, h as workerDepth, k as markControlsHeld, l as denialLines, m as parentFromEnv, mt as planLogTail, p as readInbox, pt as l ...
18import { C as endLabel, m as renderBuildQueue, o as buildsDir, r as killTree, u as listBuildsSync, v as poolSlots } from"./kill-tree-BuZjJPFM.mjs";
19import { r as sayToWorker } from"./operator-B2QDzKMW.mjs";
20import { t as runChain } from"./chain-BFcwF_Qy.mjs";
18import { copyFileSync as copyFileSync$1, existsSync as existsSync$1, linkSync as linkSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, renameSync as renameSync$1, writeFileSync as writeFileSync$1 } from"fs";
19import { basename as basename$1, join as join$1, resolve as resolve$1 } from"path";
20import { homedir as homedir$1 } from"os";
21
22//#region src/session/checkpoint-block.ts
23/**
24* Shared "## Continue" checkpoint logic for a project's TODO.md.
25*
26* WHY THIS MODULE EXISTS
27* ----------------------
28* Before this, `pause.ts` and `handover.ts` each carried their own copy of
29* findProjectTodo / stripContinueSection / block-builder. Both wrote the same
30* fixed four-line block, and both stripped any existing ## Continue section
31* unconditionally. The consequence was that a rich, model-authored checkpoint
32* could never survive:
33*
34* 1. `pai pause` had no way to accept a body, so the model printed its
•
Manifest-reachable source captures an API credential, sends it to a fixed unofficial gateway, and persists that redirection in agent or shell configuration.
dist/cli/index.mjs:
import { appendFileSync, chmodSync, closeSync, copyFileSync, cpSync, createReadStream, createWriteStream, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readS...
import { createInterface } from "node:readline";
for (const p of pages) writeFileSync(join(dir, p.name), p.markdown);
writeFileSync(file, md + "\n");
if (!existsSync(envFile)) writeFileSync(envFile, "arm load thermal\n");
writeFileSync(file, `# A/B results\n\nGenerated by \`pai worker bench\`; void rows excluded from pass/total.\n\n${aggregate(rows)}\n`);
const SETTINGS_FILE = join(join(homedir(), ".claude"), "settings.json");
* ~/.cla
High
Credential Redirect Persistence
Manifest-reachable source sends a prompted API credential to a fixed unofficial gateway and persists the redirection.
•Manifest-trigger-reachable source links an external AI-agent control path to a behavior-bearing write operation.
dir(), "pai-sk[redacted]");
if (!existssync(templatepath)) {
console.log(c.warn("skill template not found: " + templatepath));
console.log(c.dim(" skipping pai skill installation."));
return false;
}
const skilldir = join(homedir(), ".claude", "skills", "pai");
const skillfile = join(skilldir, "skill.md");
if (existssync(skillfile)) {
const content = readfilesync(skillfile, "utf-8");
const isgenerated = content.includes("generated by pai setup");
if (isgenerated) console.log(c.dim(" found existing pai-generated skill.md."));
else {
console.log(c.yellow(" found existing skill.md (not pai-generated)."));
console.log(c.dim(" a backup will be created bef
High
Trigger Reachable External Ai Agent Control Surface Mutation
Manifest-trigger-reachable source writes behavior-bearing configuration into a user or project AI-agent control surface.
3Cross-file remote execution chain: dist/cli/index.mjs spawns dist/daemon-mcp/index.mjs; helper contains network access plus dynamic code execution.
L3: import { a as slugify$1, i as parseSessionFilename } from "../migrate-B1t833CE.mjs";
L4: import { $ as workersYamlPath, $t as readRunLock, A as nativeAnthropicProvider, An as effectiveCap, At as requestPlanStop, B as readWorkersSection, Bt as PlanTracker, Cn as buildSl...
L5: import { a as schedulerLogPath, c as migratePaiFile, d as resolvePaiFile, i as paiSocketPath, l as paiHomeDir, n as daemonLogPath, o as PaiFileMigrationError, s as migratePaiDir, u...
...
L48: import chalk from "chalk";
L49: import { execFile, execFileSync, execSync, spawn, spawnSync } from "node:child_process";
L50: import { parseDocument } from "yaml";
L51: import {
High
Cross File Remote Execution Context
Source spawns a local helper that also contains network and dynamic execution context; review data flow before blocking.
3Trigger-reachable persistence chain: manifest.bin -> dist/cli/index.mjs
L3: import { a as slugify$1, i as parseSessionFilename } from "../migrate-B1t833CE.mjs";
L4: import { $ as workersYamlPath, $t as readRunLock, A as nativeAnthropicProvider, An as effectiveCap, At as requestPlanStop, B as readWorkersSection, Bt as PlanTracker, Cn as buildSl...
L5: import { a as schedulerLogPath, c as migratePaiFile, d as resolvePaiFile, i as paiSocketPath, l as paiHomeDir, n as daemonLogPath, o as PaiFileMigrationError, s as migratePaiDir, u...
...
L48: import chalk from "chalk";
L49: import { execFile, execFileSync, execSync, spawn, spawnSync } from "node:child_process";
L50: import { parseDocument } from "yaml";
L51: import { createHash, randomBytes, randomUUID } from "node:crypto";
L52: import http f
High
Trigger Reachable Persistence
A manifest entrypoint or package-local install chain reaches persistence behavior.