Dashboard › craft › Distillation
0fc17c58-bcbc-4f01-9c95-4f9a74607376["lore_tm_v1_Dexnuv0-WhBUuwhz59dXHtad7HsvV6ndI0sJjFvD63I","lore_tm_v1_7IkaVC03i8VY3bWqRw8KwrUoaZgBib3kzVzdH-wQwvM","lore_tm_v1_FNTvIeSe_smzJMlGHDf-A6lY-hIC94A9DkcsjGH489w","lore_tm_v1_0Z6u_PImfA3gVqcr0AP55__Z3WcKV7oYScE1RE6AaOU","lore_tm_v1_xXKWWDfoDnE0lFD55qGMkKqzvbgkU9G7GhmfMCXn6BI"]
Date: Aug 25, 2026
/home/byk/Code/getsentry/craft-workspaces-schema/src/commands/publish.ts lines 550-749: publishMain(argv: PublishOptions) checks dirty Git state unless argv.noGitChecks; resolves/checks out argv.rev or ${config.releaseBranchPrefix || DEFAULT_RELEASE_BRANCH_NAME}/${newVersion}; checks CI status via checkRevisionStatus(statusProvider, revision, argv.noStatusCheck), prints artifact summary, and checks required artifacts. It expands workspace targets via expandWorkspaceTargets(config.targets || []). It resolves publishStateGithubConfig with getGlobalGitHubConfig() and falls back to null on error; calls getPublishStatePath(newVersion, publishStateGithubConfig). Legacy .craft-publish-${newVersion}.json in the project directory is warned about but never read because repo contents can be attacker-influenced and pre-populate already-published targets. If the state file exists, it parses JSON.parse(readFileSync(publishStateFile).toString()), resets targetsToPublish to all targetConfigList IDs, then deletes every key in publishState.published; otherwise initializes publishState = { published: Object.create(null) }./home/byk/Code/getsentry/craft-workspaces-schema/src/utils/publishState.ts: getCraftStateDir() returns join(process.env.XDG_STATE_HOME, 'craft') when XDG_STATE_HOME is nonempty, otherwise join(homedir(), '.local', 'state', 'craft'). getPublishStateFilename(version, githubConfig, cwd = process.cwd()) sanitizes strings by lowercasing and replacing non-[a-z0-9._-] runs with _; with GitHub config returns publish-state-${owner}-${repo}-${sha1(cwd).slice(0, 12)}-${safeVersion}.json; without GitHub config returns publish-state-${sha256(cwd).slice(0, 16)}-${safeVersion}.json. getPublishStatePath() joins that filename to the Craft state directory. Module comments state this move avoids repository-writable state files that could make Craft silently skip targets; it notes $XDG_STATE_HOME/craft/, fallback $HOME/.local/state/craft/, and getsentry/publish Docker workflow runs with HOME=/root./home/byk/Code/getsentry/craft-workspaces-schema/src/targets/base.ts: BaseTarget.getId(target: TargetConfig) returns ${target.name}[${target.id}] when target.id exists, otherwise target.name || '__undefined__'. BaseTarget stores target config, artifact provider, optional GitHub repository config, and include/exclude artifact-name filters.resolveWorkspaceConfig() in src/config.ts:114-116 accesses workspaces[workspaceName] without an own-property check. Prototype names such as toString, constructor, and __proto__ are truthy, pass validation, produce no workspace overrides through Object.entries(), and return top-level release configuration at src/config.ts:129-160; applyWorkspaceSelection() accepts this at src/config.ts:231-239. An attacker-controlled or malformed CRAFT_WORKSPACE could route a workspace release to the implicit base release rather than failing closed.src/utils/publishState.ts:78-90; workspace identity is omitted when src/commands/publish.ts:685-699 computes the state path. Thus a successful cli publish of version X can make an mcp publish at the same repo/cwd/version load shared state and remove matching target IDs at src/commands/publish.ts:719-739. Since default BaseTarget IDs are target names (src/targets/base.ts:29-33), two workspace github targets collide as github, causing the second release to silently skip publication.