cli-blueprint 7.0.40 → 7.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/broker-account-storage.mjs +30 -13
- package/official-skill-update.mjs +3 -2
- package/package.json +1 -1
- package/skill/SKILL.md +2 -2
- package/skill/skill.json +1 -1
|
@@ -13,6 +13,18 @@ const ACL_SID_ALIASES = Object.freeze({ SY: SYSTEM_SID, WD: 'S-1-1-0', BA: 'S-1-
|
|
|
13
13
|
BU: 'S-1-5-32-545', AU: 'S-1-5-11', CO: 'S-1-3-0', CG: 'S-1-3-1', AN: 'S-1-5-7' })
|
|
14
14
|
const SID_PATTERN = /^S-1-(?:[0-9]+-)*[0-9]+$/
|
|
15
15
|
const ACL_TIMEOUT_MS = 15_000
|
|
16
|
+
const WINDOWS_SYSTEM_EXECUTABLES = new Set(['whoami.exe', 'icacls.exe', 'where.exe'])
|
|
17
|
+
|
|
18
|
+
export function windowsSystemExecutable(name, environment = process.env) {
|
|
19
|
+
if (!WINDOWS_SYSTEM_EXECUTABLES.has(name)) throw new Error('Unsupported Windows system executable')
|
|
20
|
+
const root = environment.SystemRoot
|
|
21
|
+
if (typeof root !== 'string' || !/^[A-Za-z]:[\\/]/.test(root)
|
|
22
|
+
|| /[\u0000-\u001f"<>|?*]/.test(root) || root.slice(2).includes(':')
|
|
23
|
+
|| root.split(/[\\/]/).includes('..')) {
|
|
24
|
+
throw new Error('SystemRoot must identify an absolute Windows installation directory')
|
|
25
|
+
}
|
|
26
|
+
return win32.join(root, 'System32', name)
|
|
27
|
+
}
|
|
16
28
|
|
|
17
29
|
export function currentAccountHome() {
|
|
18
30
|
const home = userInfo().homedir
|
|
@@ -69,17 +81,19 @@ export function assertRestrictedWindowsAcl(text, ownerSid) {
|
|
|
69
81
|
if (expected.size) throw new Error('Windows account ACL is missing the user or SYSTEM')
|
|
70
82
|
}
|
|
71
83
|
|
|
72
|
-
async function windowsOwnerSid(run) {
|
|
73
|
-
const result = await run('whoami.exe', ['/user', '/fo', 'csv', '/nh'],
|
|
84
|
+
async function windowsOwnerSid(run, environment) {
|
|
85
|
+
const result = await run(windowsSystemExecutable('whoami.exe', environment), ['/user', '/fo', 'csv', '/nh'],
|
|
86
|
+
{ shell: false, windowsHide: true, timeout: ACL_TIMEOUT_MS })
|
|
74
87
|
const candidates = result.stdout.match(/S-1-(?:[0-9]+-)*[0-9]+/g)
|
|
75
88
|
if (candidates === null || candidates.length !== 1 || !SID_PATTERN.test(candidates[0])) throw new Error('Current Windows account SID could not be verified')
|
|
76
89
|
return candidates[0]
|
|
77
90
|
}
|
|
78
91
|
|
|
79
|
-
async function readWindowsAcl(path, run) {
|
|
92
|
+
async function readWindowsAcl(path, run, environment) {
|
|
80
93
|
const temporary = join(dirname(path), '.acl-' + randomUUID() + '.txt')
|
|
81
94
|
try {
|
|
82
|
-
await run('icacls.exe', [path, '/save', temporary, '/q'],
|
|
95
|
+
await run(windowsSystemExecutable('icacls.exe', environment), [path, '/save', temporary, '/q'],
|
|
96
|
+
{ shell: false, windowsHide: true, timeout: ACL_TIMEOUT_MS })
|
|
83
97
|
return aclText(await readFile(temporary))
|
|
84
98
|
} finally {
|
|
85
99
|
try { await rm(temporary) } catch (error) { if (error.code !== 'ENOENT') throw error }
|
|
@@ -88,19 +102,21 @@ async function readWindowsAcl(path, run) {
|
|
|
88
102
|
|
|
89
103
|
async function protectWindowsPath(path, directory, dependencies) {
|
|
90
104
|
const run = dependencies.execFile === undefined ? runFile : dependencies.execFile
|
|
91
|
-
const
|
|
105
|
+
const environment = dependencies.environment === undefined ? process.env : dependencies.environment
|
|
106
|
+
const owner = await windowsOwnerSid(run, environment)
|
|
92
107
|
const flags = directory ? '(OI)(CI)F' : 'F'
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
108
|
+
const icacls = windowsSystemExecutable('icacls.exe', environment)
|
|
109
|
+
await run(icacls, [path, '/inheritance:r', '/grant:r', '*' + owner + ':' + flags,
|
|
110
|
+
'*' + SYSTEM_SID + ':' + flags], { shell: false, windowsHide: true, timeout: ACL_TIMEOUT_MS })
|
|
111
|
+
const { entries } = windowsAclEntries(await readWindowsAcl(path, run, environment))
|
|
96
112
|
for (const entry of entries) {
|
|
97
113
|
const sid = Object.hasOwn(ACL_SID_ALIASES, entry.sid) ? ACL_SID_ALIASES[entry.sid] : entry.sid
|
|
98
114
|
if (entry.type === 'A' && [owner, SYSTEM_SID].includes(sid)) continue
|
|
99
115
|
if (!SID_PATTERN.test(sid)) throw new Error('Unexpected Windows ACL trustee')
|
|
100
|
-
await run(
|
|
101
|
-
{ windowsHide: true, timeout: ACL_TIMEOUT_MS })
|
|
116
|
+
await run(icacls, [path, entry.type === 'D' ? '/remove:d' : '/remove:g', '*' + sid],
|
|
117
|
+
{ shell: false, windowsHide: true, timeout: ACL_TIMEOUT_MS })
|
|
102
118
|
}
|
|
103
|
-
assertRestrictedWindowsAcl(await readWindowsAcl(path, run), owner)
|
|
119
|
+
assertRestrictedWindowsAcl(await readWindowsAcl(path, run, environment), owner)
|
|
104
120
|
}
|
|
105
121
|
|
|
106
122
|
export async function protectAccountPath(path, directory, dependencies = {}) {
|
|
@@ -130,6 +146,7 @@ export async function verifyAccountPath(path, dependencies = {}) {
|
|
|
130
146
|
return
|
|
131
147
|
}
|
|
132
148
|
const run = dependencies.execFile === undefined ? runFile : dependencies.execFile
|
|
133
|
-
const
|
|
134
|
-
|
|
149
|
+
const environment = dependencies.environment === undefined ? process.env : dependencies.environment
|
|
150
|
+
const owner = await windowsOwnerSid(run, environment)
|
|
151
|
+
assertRestrictedWindowsAcl(await readWindowsAcl(path, run, environment), owner)
|
|
135
152
|
}
|
|
@@ -3,7 +3,7 @@ import { execFile, spawn } from 'node:child_process'
|
|
|
3
3
|
import { promisify } from 'node:util'
|
|
4
4
|
import { lstat, mkdtemp, readFile, rename, rm } from 'node:fs/promises'
|
|
5
5
|
import { join, win32 } from 'node:path'
|
|
6
|
-
import { assertAccountAncestors, currentAccountHome, ensureAccountDirectory } from './broker-account-storage.mjs'
|
|
6
|
+
import { assertAccountAncestors, currentAccountHome, ensureAccountDirectory, windowsSystemExecutable } from './broker-account-storage.mjs'
|
|
7
7
|
import { pathToFileURL } from 'node:url'
|
|
8
8
|
import { accountBrokerDirectory } from './broker-credentials.mjs'
|
|
9
9
|
import { createBrokerTransport } from './broker-transport.mjs'
|
|
@@ -70,7 +70,8 @@ export function windowsNpmEntry(output) {
|
|
|
70
70
|
|
|
71
71
|
async function npmInvocation(environment) {
|
|
72
72
|
if (process.platform !== 'win32') return { executable: 'npm', args: [] }
|
|
73
|
-
const found = await runFile('where.exe', ['npm'],
|
|
73
|
+
const found = await runFile(windowsSystemExecutable('where.exe', environment), ['npm'],
|
|
74
|
+
{ env: environment, shell: false, windowsHide: true, timeout: LOOKUP_TIMEOUT_MS })
|
|
74
75
|
const entry = windowsNpmEntry(found.stdout)
|
|
75
76
|
for (const path of [entry.command, entry.script]) {
|
|
76
77
|
await assertAccountAncestors(win32.dirname(path), 'win32')
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: '把一个目标编译为可执行、可验证、可追溯的工程
|
|
|
5
5
|
|
|
6
6
|
# Blueprint Skill
|
|
7
7
|
|
|
8
|
-
Package version: v7.0.
|
|
8
|
+
Package version: v7.0.41
|
|
9
9
|
|
|
10
10
|
远端 Hermes 编译器版本:0.4.0(独立于 npm 包版本)
|
|
11
11
|
|
|
@@ -253,7 +253,7 @@ After `capabilities`, read `officialCatalog`. Default allowlist is official skil
|
|
|
253
253
|
| B3 | 业务模板库与粗粒度模式 | 规划中 | 当前没有模板操作,`template` 与 `coarseMode` 均不是受支持输入。 |
|
|
254
254
|
| B4 | 验收回传、开放问题闭环、Validator 桥接 | 部分实现 | `acceptance-report` 已逐项核对共享 TestEvidence;`answer-questions` 与自动调用 Validator 仍未实现。 |
|
|
255
255
|
|
|
256
|
-
只调用 `capabilities` 返回的六个操作。不要根据规划中条目构造请求,也不要把 npm 包版本 `v7.0.
|
|
256
|
+
只调用 `capabilities` 返回的六个操作。不要根据规划中条目构造请求,也不要把 npm 包版本 `v7.0.41` 与远端 Hermes 编译器版本 `0.4.0` 混为一谈。
|
|
257
257
|
|
|
258
258
|
## 机器任务与验收回传
|
|
259
259
|
|
package/skill/skill.json
CHANGED