Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f1a6fc4
feat(web-shell): share HTML artifacts through the OSS publisher
qqqys Aug 19, 2026
03f9870
feat(web-shell): let the share dialog set a public domain for the link
qqqys Aug 19, 2026
fa917a2
feat(web-shell): add managed artifact sharing
qqqys Aug 25, 2026
34173f1
Merge remote-tracking branch 'upstream/main' into feat/artifact-share
qqqys Aug 25, 2026
3063685
fix(cli): update artifact settings import after main merge
qqqys Aug 25, 2026
b16bdcd
fix(webui): scope artifact sharing to active workspace
qqqys Aug 25, 2026
bba81a3
Merge remote-tracking branch 'upstream/main' into feat/artifact-share
Aug 25, 2026
73381f8
fix(cli): fix artifact sharing test failures and review findings (#10…
qwen-code-dev-bot Aug 25, 2026
1f6c187
fix(cli): complete AppContainer SubagentManager mock for AgentTool st…
qwen-code-dev-bot Aug 25, 2026
e34a6cf
Merge branch 'main' into feat/artifact-share
qwen-code-dev-bot Aug 26, 2026
a0035e3
Merge branch 'main' into feat/artifact-share
qqqys Aug 26, 2026
09747eb
Merge branch 'main' into feat/artifact-share
qwen-code-dev-bot Aug 26, 2026
4ab71b5
Merge branch 'main' into feat/artifact-share
qwen-code-dev-bot Aug 26, 2026
f4eab1b
fix(cli): harden artifact sharing env scrubbing and Netlify setup (#1…
qwen-code-dev-bot Aug 27, 2026
74e1489
fix(core): repair telemetry-swap test mock missing getToolRegistry (#…
qwen-code-dev-bot Aug 27, 2026
7330ba5
Merge branch 'main' into feat/artifact-share
qwen-code-dev-bot Aug 27, 2026
b2f4c82
Merge remote-tracking branch 'upstream/main' into feat/artifact-share
qqqys Aug 28, 2026
de76d60
fix(web-shell): close the round-6 review findings on artifact sharing
qqqys Aug 28, 2026
7d2f844
Merge remote-tracking branch 'upstream/main' into feat/artifact-share
qqqys Aug 28, 2026
8f3ccb8
Merge branch 'main' into feat/artifact-share
qwen-code-dev-bot Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(cli): harden artifact sharing env scrubbing and Netlify setup (#1…
  • Loading branch information
qwen-code-dev-bot committed Aug 27, 2026
commit f4eab1b2de8c34c3b65691078ae3d1e8ceaf3c24
4 changes: 2 additions & 2 deletions packages/cli/src/serve/process-env-guard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ const allowedProcessEnvAccesses = normalizeAllowances([
'packages/cli/src/serve/routes/workspace-artifact-publish.ts',
{
reason:
'Artifact provider CLIs are process-scoped: they install under the daemon-wide Qwen directory rather than any workspace, and provider child processes inherit the daemon environment with PATH pinned to the daemon PATH.',
accesses: { 'key:PATH': 1, whole: 3 },
'Artifact provider CLIs are process-scoped: they install under the daemon-wide Qwen directory rather than any workspace, and provider child processes inherit the daemon environment with PATH pinned to the daemon PATH. The credential-store location variables (XDG_* / APPDATA / LOCALAPPDATA) are read back from the daemon environment in a loop so a workspace overlay cannot redirect where the daemon-managed provider logins live.',
accesses: { 'computed:key': 1, 'key:PATH': 1, whole: 3 },
},
],
[
Expand Down
164 changes: 153 additions & 11 deletions packages/cli/src/serve/routes/workspace-artifact-publish.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import * as path from 'node:path';
import * as http from 'node:http';
import * as fsp from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import express from 'express';
Expand Down Expand Up @@ -508,6 +509,14 @@ describe('GET /workspace/artifact/publish-config', () => {
env: {
PATH: '/workspace/bin',
NODE_OPTIONS: '--import /workspace/evil.js',
NETLIFY_AUTH_TOKEN: 'attacker-token',
NETLIFY_API_URL: 'https://attacker.example',
NETLIFY_SITE_ID: 'attacker-site',
CLOUDFLARE_API_TOKEN: 'attacker-token',
CLOUDFLARE_API_BASE_URL: 'https://attacker.example',
CF_API_BASE_URL: 'https://attacker.example',
VERCEL_TOKEN: 'attacker-token',
XDG_DATA_HOME: '/workspace/.xdg',
},
});
mockSettings({
Expand All @@ -520,11 +529,9 @@ describe('GET /workspace/artifact/publish-config', () => {
},
});
const runCommand: ArtifactRouteCommandRunner = vi.fn(
async (command, args, options) => {
async (command, args) => {
expect(command).toBe(process.execPath);
expect(args[0]).toBe(TEST_NETLIFY_ENTRY);
expect(options.env['PATH']).toBe(process.env['PATH']);
expect(options.env['NODE_OPTIONS']).toBeUndefined();
if (args[1] === '--version') return '27.1.2';
if (args[1] === 'api') throw new Error('not authenticated');
throw new Error(`Unexpected command: ${args.join(' ')}`);
Expand All @@ -548,6 +555,22 @@ describe('GET /workspace/artifact/publish-config', () => {
.mocked(runCommand)
.mock.calls.every(([command]) => command === process.execPath),
).toBe(true);
const netlifyCalls = vi
.mocked(runCommand)
.mock.calls.filter(([, args]) => args[0] === TEST_NETLIFY_ENTRY);
expect(netlifyCalls.length).toBeGreaterThan(0);
for (const [, , options] of netlifyCalls) {
expect(options.env['PATH']).toBe(process.env['PATH']);
expect(options.env['NODE_OPTIONS']).toBeUndefined();
expect(options.env['NETLIFY_AUTH_TOKEN']).toBeUndefined();
expect(options.env['NETLIFY_API_URL']).toBeUndefined();
expect(options.env['NETLIFY_SITE_ID']).toBeUndefined();
expect(options.env['CLOUDFLARE_API_TOKEN']).toBeUndefined();
expect(options.env['CLOUDFLARE_API_BASE_URL']).toBeUndefined();
expect(options.env['CF_API_BASE_URL']).toBeUndefined();
expect(options.env['VERCEL_TOKEN']).toBeUndefined();
expect(options.env['XDG_DATA_HOME']).toBe(process.env['XDG_DATA_HOME']);

ghost Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred: Suggestion-level witness gap (XDG/APPDATA restore loop only witnessed for XDG_DATA_HOME against process.env), still open. This round was scoped to the rejected verification gate under the budget warning, so non-essential work was not retried.

中文说明

延后:建议级见证缺口(XDG/APPDATA 恢复循环只有 XDG_DATA_HOME 对着 process.env 有见证),仍开放。本轮在预算警告下只处理被拒的验证门禁,未重试非必要工作。

}
});

it('runs the JavaScript CLI entrypoint on Windows instead of .cmd shims', async () => {
Expand Down Expand Up @@ -815,7 +838,10 @@ describe('POST /workspace/artifact/netlify/setup', () => {
.post('/workspace/artifact/netlify/setup')
.send({ action: 'poll' });
Comment on lines +939 to +941

ghost Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred under the workflow's budget warning — this round was a same-run verification repair limited to the deterministic test rejection (pollLogin's denied/pending/expired branches uncovered). Valid suggestion; queued for a follow-up round, not dropped.

中文说明

因工作流预算警告延后——本轮是针对确定性测试拒绝的同轮验证修复(pollLogin's denied/pending/expired branches uncovered)。建议有效;已排入后续轮次,不会丢弃。

ghost Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred under the workflow's budget warning — this round was a same-run verification repair limited to the deterministic test rejection (pollLogin's denied/pending/expired branches uncovered). Valid suggestion; queued for a follow-up round, not dropped.

中文说明

因工作流预算警告延后——本轮是针对确定性测试拒绝的同轮验证修复(pollLogin's denied/pending/expired branches uncovered)。建议有效;已排入后续轮次,不会丢弃。

ghost Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred under the workflow's budget warning — this round was a same-run verification repair limited to the deterministic test rejection (pollLogin's denied/pending/expired branches uncovered). Valid suggestion; queued for a follow-up round, not dropped.

中文说明

因工作流预算警告延后——本轮是针对确定性测试拒绝的同轮验证修复(pollLogin's denied/pending/expired branches uncovered)。建议有效;已排入后续轮次,不会丢弃。

ghost Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred under the workflow's budget warning — this round was a same-run verification repair limited to the deterministic test rejection (pollLogin's denied/pending/expired branches uncovered). Valid suggestion; queued for a follow-up round, not dropped.

中文说明

因工作流预算警告延后——本轮是针对确定性测试拒绝的同轮验证修复(pollLogin's denied/pending/expired branches uncovered)。建议有效;已排入后续轮次,不会丢弃。


expect(response.status).toBe(200);
expect(
response.status,
`setup returned ${JSON.stringify(response.body)}`,
Comment on lines +943 to +945

ghost Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred: Suggestion-level witness gap (pollLogin denied/pending/unexpected branches untested), still open. This round was scoped to the rejected verification gate under the budget warning, so non-essential work was not retried.

中文说明

延后:建议级见证缺口(pollLogin 的 denied/pending/意外状态分支无测试),仍开放。本轮在预算警告下只处理被拒的验证门禁,未重试非必要工作。

).toBe(200);
expect(response.body.setup).toMatchObject({
stage: 'ready',
authenticated: true,
Expand Down Expand Up @@ -888,7 +914,10 @@ describe('POST /workspace/artifact/netlify/setup', () => {
.post('/workspace/artifact/netlify/setup')
.send({ action: 'prepare' });

expect(response.status).toBe(200);
expect(
response.status,
`setup returned ${JSON.stringify(response.body)}`,
).toBe(200);
expect(response.body.setup).toMatchObject({
stage: 'ready',
authenticated: true,
Expand Down Expand Up @@ -974,7 +1003,10 @@ describe('POST /workspace/artifact/netlify/setup', () => {
.post('/workspace/artifact/netlify/setup')
.send({ action: 'connect' });

expect(response.status).toBe(200);
expect(
response.status,
`setup returned ${JSON.stringify(response.body)}`,
).toBe(200);
expect(response.body.setup).toMatchObject({
stage: 'ready',
linked: true,
Expand Down Expand Up @@ -1174,7 +1206,10 @@ describe('POST /workspace/artifact/netlify/setup', () => {
.post('/workspace/artifact/netlify/setup')
.send({ action: 'connect' });

expect(response.status).toBe(200);
expect(
response.status,
`setup returned ${JSON.stringify(response.body)}`,
).toBe(200);
expect(response.body.setup).toMatchObject({
stage: 'ready',
configured: true,
Expand Down Expand Up @@ -1227,6 +1262,85 @@ describe('POST /workspace/artifact/netlify/setup', () => {
.mock.calls.some(([, args]) => args[0] === 'sites:create'),
).toBe(false);
});

// Self-referential symlink creation is not reliably permitted on Windows.
it.skipIf(process.platform === 'win32')(
'creates a dedicated project when the boundary probe cannot read .git',
async () => {
const workspaceCwd = await fsp.mkdtemp(
path.join(tmpdir(), 'qwen-art-boundary-'),
);
const settingsByWorkspace: Record<string, Record<string, unknown>> = {
[workspaceCwd]: {},
};
mockSettings(settingsByWorkspace);
await fsp.symlink(
path.join(workspaceCwd, '.git'),
path.join(workspaceCwd, '.git'),
);
try {
const primary = runtime('primary', workspaceCwd, { primary: true });
const runCommand: ArtifactRouteCommandRunner = vi.fn(
async (_command, args) => {
if (args[0] === '--version') return '27.1.2';
if (args[0] === 'api' && args[1] === 'getCurrentUser') {
return JSON.stringify({ id: 'user-id' });
}
if (args[0] === 'api' && args[1] === 'getSite') {
return JSON.stringify({
id: 'created-site',
name: 'Created site',
});
}
if (args[0] === 'status') throw new Error('not linked');
if (args[0] === 'sites:create') {
return JSON.stringify({
id: 'created-site',
name: 'Created site',
});
}
throw new Error(`Unexpected command: ${args.join(' ')}`);
},
);
const persistSettings = vi.fn(async (_workspace, writes) => {
const host: Record<string, unknown> = {};
for (const write of writes) {
if (write.key === 'artifact.host.uploadCommand') {
host['uploadCommand'] = write.value;
}
if (write.key === 'artifact.host.urlFromCommandOutput') {
host['urlFromCommandOutput'] = write.value;
}
}
settingsByWorkspace[workspaceCwd] = { host };
});
const app = express();
app.use(express.json());
registerWorkspaceArtifactPublishRoutes(app, {
getPrimaryRuntime: () => primary,
sendBridgeError,
mutate: allowMutations,
runCommand: adaptTestRunner(runCommand),
persistSettings,
});

const response = await request(app)
.post('/workspace/artifact/netlify/setup')
.send({ action: 'prepare' });

expect(
response.status,
`setup returned ${JSON.stringify(response.body)}`,
).toBe(200);
expect(response.body.setup).toMatchObject({
stage: 'ready',
linkedSite: { id: 'created-site' },
});
} finally {
await fsp.rm(workspaceCwd, { recursive: true, force: true });
}
},
);
});

describe('multi-provider artifact setup', () => {
Expand Down Expand Up @@ -1893,6 +2007,37 @@ describe('POST /workspace/artifact/publish', () => {
.mocked(readyNetlify)
.mock.calls.some(([, args]) => args[0] === 'status'),
).toBe(false);
expect(
vi
.mocked(readyNetlify)
.mock.calls.some(
([, args]) => args[0] === 'api' && args[1] === 'updateSite',
),
).toBe(false);
expect(response.body).toMatchObject({
provider: 'netlify',
url: 'https://preview.example.com/report',
});
});

it('strips site protection only for the managed Netlify site', async () => {
const readBytesWindow = windowReader(HTML);
const primary = runtime('primary', '/workspace', {
primary: true,
readBytesWindow,
});
mockSettings({
'/workspace': {
host: NETLIFY_HOST,
share: { netlify: { siteId: 'site-id' } },
},
});

const response = await request(makePrimaryApp(primary))
.post('/workspace/artifact/publish')
.send({ path: 'out/report.html', provider: 'netlify' });

expect(response.status).toBe(200);
const updateCall = vi
.mocked(readyNetlify)
.mock.calls.find(
Expand All @@ -1914,10 +2059,6 @@ describe('POST /workspace/artifact/publish', () => {
expect(mocked.hostPublish.mock.invocationCallOrder[0]).toBeLessThan(
vi.mocked(readyNetlify).mock.invocationCallOrder[updateCallIndex]!,
);
expect(response.body).toMatchObject({
provider: 'netlify',
url: 'https://preview.example.com/report',
});
});

it('publishes through a pinned Cloudflare Pages project', async () => {
Expand Down Expand Up @@ -2514,6 +2655,7 @@ describe('POST /workspace/artifact/publish', () => {
mockSettings({
'/workspace': {
host: NETLIFY_HOST,
share: { netlify: { siteId: 'site-id' } },
},
});
const protectedNetlify: ArtifactRouteCommandRunner = vi.fn(
Expand Down
30 changes: 27 additions & 3 deletions packages/cli/src/serve/routes/workspace-artifact-publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,27 @@ function providerEnv(runtime: WorkspaceRuntime): NodeJS.ProcessEnv {
delete env['CLOUDFLARE_ACCOUNT_ID'];
delete env['VERCEL_ORG_ID'];
delete env['VERCEL_PROJECT_ID'];
delete env['CLOUDFLARE_API_TOKEN'];
delete env['CLOUDFLARE_API_BASE_URL'];
delete env['CF_API_BASE_URL'];
Comment on lines +457 to +459

ghost Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred by maintainer decision (round-6 summary comment): the env-denylist class keeps yielding bypasses and the real fix is an allowlist-constructed provider env — tracked as a follow-up, not half-fixed here. Re-posted at round 6 (3876242226), round 7 (3879310205) and round 8 (3882827680). This round was scoped to the rejected verification gate only.

中文说明

按维护者决定延后(第 6 轮总结评论):环境拒绝清单类问题不断产生绕过,真正的修复是白名单构造的 provider 环境——作为后续跟进,不在此处做半吊子修复。已在第 6 轮(3876242226)、第 7 轮(3879310205)、第 8 轮(3882827680)重新发布。本轮只处理被拒的验证门禁。

delete env['VERCEL_TOKEN'];
delete env['NETLIFY_AUTH_TOKEN'];
delete env['NETLIFY_API_URL'];
delete env['NETLIFY_SITE_ID'];
Comment on lines +461 to +463

ghost Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred by maintainer decision (round-6 summary comment): the env-denylist class keeps yielding bypasses and the real fix is an allowlist-constructed provider env — tracked as a follow-up, not half-fixed here. Re-posted at round 7 (3879310205) and round 8 (3882827680). This round was scoped to the rejected verification gate only.

中文说明

按维护者决定延后(第 6 轮总结评论):环境拒绝清单类问题不断产生绕过,真正的修复是白名单构造的 provider 环境——作为后续跟进,不在此处做半吊子修复。已在第 7 轮(3879310205)与第 8 轮(3882827680)重新发布。本轮只处理被拒的验证门禁。

Comment on lines +461 to +463

ghost Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred by maintainer decision (round-6 summary comment): the env-denylist class is tracked as a follow-up (allowlist-constructed provider env), not half-fixed in this PR. Re-posted at round 8 (3882827680) with the config-dir entrance. This round was scoped to the rejected verification gate only.

中文说明

按维护者决定延后(第 6 轮总结评论):环境拒绝清单类问题作为后续跟进(白名单构造的 provider 环境),不在本 PR 中做半吊子修复。已在第 8 轮(3882827680)连同配置目录入口重新发布。本轮只处理被拒的验证门禁。

Comment on lines +461 to +463

ghost Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred by maintainer decision (round-6 summary comment): the env-denylist class is tracked as a follow-up (allowlist-constructed provider env), not half-fixed in this PR. Still open at the current head (8f3ccb8). This round was scoped to the rejected verification gate only.

中文说明

按维护者决定延后(第 6 轮总结评论):环境拒绝清单类问题作为后续跟进(白名单构造的 provider 环境),不在本 PR 中做半吊子修复。在当前头(8f3ccb8ae6)仍开放。本轮只处理被拒的验证门禁。

// Restore the daemon's credential-store locations instead of deleting
// them: logins performed by this daemon live under those paths.
Comment on lines +464 to +465

ghost Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred by maintainer decision (round-6 summary comment): per-workspace credential isolation is a rewrite of the credential-home handling, tracked as a follow-up rather than half-fixed here. Re-posted at round 7 (3879310218) and round 8 (3882827685). This round was scoped to the rejected verification gate only.

中文说明

按维护者决定延后(第 6 轮总结评论):按工作区隔离凭据是对凭据目录处理的重写,作为后续跟进,不在此处做半吊子修复。已在第 7 轮(3879310218)与第 8 轮(3882827685)重新发布。本轮只处理被拒的验证门禁。

for (const key of [
'XDG_DATA_HOME',
Comment on lines +466 to +467

ghost Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred by maintainer decision (round-6 summary comment): per-workspace credential isolation is tracked as a follow-up, not half-fixed in this PR. Re-posted at round 8 (3882827685). This round was scoped to the rejected verification gate only.

中文说明

按维护者决定延后(第 6 轮总结评论):按工作区隔离凭据作为后续跟进,不在本 PR 中做半吊子修复。已在第 8 轮(3882827685)重新发布。本轮只处理被拒的验证门禁。

'XDG_STATE_HOME',
Comment on lines +466 to +468

ghost Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred by maintainer decision (round-6 summary comment): per-workspace credential isolation is tracked as a follow-up, not half-fixed in this PR. Still open at the current head (8f3ccb8). This round was scoped to the rejected verification gate only.

中文说明

按维护者决定延后(第 6 轮总结评论):按工作区隔离凭据作为后续跟进,不在本 PR 中做半吊子修复。在当前头(8f3ccb8ae6)仍开放。本轮只处理被拒的验证门禁。

'XDG_CACHE_HOME',
'XDG_CONFIG_HOME',
'APPDATA',
'LOCALAPPDATA',
]) {
const daemonValue = process.env[key];
if (daemonValue === undefined) delete env[key];
else env[key] = daemonValue;
}
return env;
}

Expand Down Expand Up @@ -1579,8 +1600,9 @@ async function assertLinkBoundary(runtime: WorkspaceRuntime): Promise<void> {
await fsp.stat(path.join(current, '.git'));
Comment on lines +1648 to +1650

ghost Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still open at the current head (8f3ccb8); no fix has landed for the remaining boundary geometries (no-.git tree; lexical vs realpath divergence). Re-posted at round 8 (3882827711). Deferred to the next round under the budget warning; see the round report for the gate-rejection verification.

中文说明

在当前头(8f3ccb8ae6)仍开放;剩余边界几何(无 .git 树;词法与 realpath 分叉)尚无修复落地。已在第 8 轮(3882827711)重新发布。按预算警告延后至下一轮;门禁拒绝的验证见本轮报告。

repositoryRoot = current;
break;
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
} catch {
// Only a discovered .git proves nesting; an unreadable entry proves
// nothing, and rethrowing turns transient fs errors into setup 500s.
}
const parent = path.dirname(current);
if (parent === current) break;
Expand Down Expand Up @@ -2948,7 +2970,9 @@ async function handlePublish(
`Could not publish the artifact through ${selectedProvider}. Try again.`,
);
});
if (selectedProvider === 'netlify') {
if (selectedProvider === 'netlify' && settings.netlify.siteId) {
Comment thread
qqqys marked this conversation as resolved.
Outdated
// Only the dedicated site this flow created may have protection
// stripped; a host-configured site keeps its password/SSO settings.
await makeSitePublic(
resolvedPublisher.command,
resolvedPublisher.targetId,
Expand Down