Report vulnerabilities privately to contact@passioncode.ai. Do not open an issue that carries credentials or a working exploit.
What passioncode update touches (setup and doctor: below): ~/.passioncode/ (releases, state, quarantine,
logs); Claude Code's plugin registry through the claude plugin CLI only;
~/.agents/skills/<member skill> and <agent channel>/<member skill> links; and
~/.claude/skills/<member skill>, which it moves into the quarantine once the plugin
that replaces it is verified. For a member's old single-member marketplace
(legacyMarketplaces in family.json), once that plugin is verified and nothing else
installed comes from it, it also moves the extraKnownMarketplaces entry out of
~/.claude/settings.json (a copy of the file is kept in the quarantine) and runs
claude plugin marketplace remove <name>. For each installed app with an MCP server in its
bundle (Fabric Dashboards, Fabric, Fabric Switchboard) it also registers that entry for every
agent that is present: through
claude mcp add --scope user for Claude Code (it never writes ~/.claude.json itself),
and in ~/.codex/config.toml, ~/.gemini/settings.json and ~/.kimi-code/mcp.json,
copying each existing file into the quarantine first; an entry that already exists is
left alone and reported. Displaced files are moved rather than
deleted: a recovery record is written before the move in
~/.passioncode/quarantine/<run>/moved.json. restore processes the latest batch,
checkpoints completed entries and refuses to overwrite a conflicting user replacement.
uninstall removes owned links/plugins, clears the active release and disables
automatic updates; it retains the current and previous release copies and recovery
records. Each update removes the launcher's own older releases (its output, reinstallable
from npm) and keeps the one it replaced for rollback. A failed removal
returns a nonzero exit code. See the CLI recovery contract.
doctor reads: ~/.claude.json, ~/.codex/config.toml, ~/.gemini/settings.json,
~/.kimi-code/mcp.json (entry names, commands and arguments; environment values are passed to
the server it starts and never printed), the Fabric service descriptors (never their token
files), the apps' Info.plist, and the GitHub releases API. It starts each registered stdio MCP
server for one proving call and stops it (never Fabric's, which enrols a session as it starts), and writes only ~/.passioncode/doctor.json and a
release cache.
setup also downloads releases from GitHub into ~/.passioncode/downloads/ (removed after
each app), copies a verified app into /Applications, moves an app it replaces into ~/.Trash/,
runs update (above), runs launchctl kickstart gui/<uid>/<label> for a family service whose
descriptor names launchd and that does not answer, and opens the apps it installed. It never
stops or kills a process, never replaces a running app, never asks for or stores a secret, and
never installs a byte whose signature, checksum, team or notarization does not verify. The trust
root is the release key bundled at lib/release-key.asc, pinned by fingerprint in
lib/openpgp.js; a new key needs a launcher release. PASSIONCODE_RELEASE_KEY replaces it for
tests and is named in setup's output whenever it is set.
The SessionStart hook reads ~/.passioncode/state.json and reuses a check for up to
24 hours. An absent, invalid, future or expired timestamp starts
npm view @passioncode-ai/passioncode version maintainers _npmUser --json, detached, logging to
~/.passioncode/logs/. It records the latest version, the package's npm maintainers
and the account that published that version.
The hook installs a newer version on its own only when all of these hold:
update.autois on (the default);- the npm probe exited successfully, the recorded maintainers are non-empty, the
publisher is known, and every maintainer and the publishing
account is listed in
trust.jsoninside the plugin (npmPublishers); a release published by this repository'srelease.ymlthrough npm trusted publishing appears asgithub-actions-oidc(npm'snpm-oidc-no-reply@github.comidentity, matched by email, never by display name), and only a maintainer can register that workflow; - the version is a plain
x.y.z.
It then runs npx --yes @passioncode-ai/passioncode@<that exact version> update --quiet, detached, so
the version that was checked is the version that runs. In every other case it spawns
nothing: a version from an account outside the list is reported in one line naming the
account ("… not a trusted PassionCode publisher — not installing"); a name nobody has
published (npm E404) is silent; a failed check is shown once.
The current allowlist lives in plugin/passioncode/trust.json.
A missing or empty allowlist cannot authorize an update. Adding an account there is a release decision:
it is reviewed like code and ships in the package. A person who wants no background
updates at all runs npx @passioncode-ai/passioncode@latest config set update.auto off.
The package contains executable launcher code and member plugin hooks as well as skill text. The vendor scans supported text-file sizes for credential-shaped strings and rejects an invalid self-plugin trust list; that scan is not a proof that all possible secrets or malicious code are absent. Review member code at its pinned tag. Automatic updates trust the npm registry's attribution and the local cached state; they do not independently verify signatures or sandbox the installed code.
Evidence: update(), restore() and uninstall() in lib/launcher.js;
parseView() and decide() in plugin/passioncode/hooks/update-check.js;
scanForSecrets() in scripts/vendor.mjs; regression tests in
test/launcher.test.js and test/self-update.test.js.