@lqc123qwe/car-runtime 1.9.0 → 1.11.0
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/README.md +17 -1
- package/dist/SHA256SUMS +1 -0
- package/dist/authz/authz.d.ts +46 -0
- package/dist/authz/authz.js +76 -0
- package/dist/car-runtime-1.11.0.tgz +0 -0
- package/dist/car-runtime-1.11.0.tgz.sig.bundle +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +825 -0
- package/dist/dx/doctor.d.ts +62 -0
- package/dist/dx/doctor.js +127 -0
- package/dist/governance/dump.d.ts +47 -0
- package/dist/governance/dump.js +17 -0
- package/dist/host/facade.d.ts +13 -0
- package/dist/host/facade.js +154 -0
- package/dist/host/hostGateway.d.ts +117 -0
- package/dist/host/hostGateway.js +65 -0
- package/dist/host/mappings.d.ts +27 -0
- package/dist/host/mappings.js +52 -0
- package/dist/host/stdio.d.ts +14 -0
- package/dist/host/stdio.js +83 -0
- package/dist/kernel/context.d.ts +76 -0
- package/dist/kernel/context.js +203 -0
- package/dist/kernel/events.d.ts +37 -0
- package/dist/kernel/events.js +82 -0
- package/dist/load/config.d.ts +79 -0
- package/dist/load/config.js +285 -0
- package/dist/load/loader.d.ts +72 -0
- package/dist/load/loader.js +162 -0
- package/dist/load/registry.d.ts +44 -0
- package/dist/load/registry.js +58 -0
- package/dist/load/report.d.ts +98 -0
- package/dist/load/report.js +301 -0
- package/dist/load/sigGate.d.ts +22 -0
- package/dist/load/sigGate.js +68 -0
- package/dist/load/sign.d.ts +25 -0
- package/dist/load/sign.js +47 -0
- package/dist/load/verifier.d.ts +35 -0
- package/dist/load/verifier.js +56 -0
- package/dist/loop/goal.d.ts +31 -0
- package/dist/loop/goal.js +34 -0
- package/dist/loop/recover.d.ts +17 -0
- package/dist/loop/recover.js +39 -0
- package/dist/loop/stop.d.ts +82 -0
- package/dist/loop/stop.js +184 -0
- package/dist/mcp/gateway.d.ts +96 -0
- package/dist/mcp/gateway.js +69 -0
- package/dist/mcp/httpClient.d.ts +27 -0
- package/dist/mcp/httpClient.js +196 -0
- package/dist/mcp/stdioClient.d.ts +8 -0
- package/dist/mcp/stdioClient.js +73 -0
- package/dist/ptc/budget.d.ts +21 -0
- package/dist/ptc/budget.js +36 -0
- package/dist/ptc/erasable.d.ts +14 -0
- package/dist/ptc/erasable.js +27 -0
- package/dist/ptc/runCode.d.ts +65 -0
- package/dist/ptc/runCode.js +140 -0
- package/dist/ptc/schemaRender.d.ts +15 -0
- package/dist/ptc/schemaRender.js +90 -0
- package/dist/ptc/sdk.d.ts +27 -0
- package/dist/ptc/sdk.js +23 -0
- package/dist/ptc/worker-entry.d.ts +1 -0
- package/dist/ptc/worker-entry.js +71 -0
- package/dist/release-audit.jsonl +4 -0
- package/dist/runtime-core/chatStep.d.ts +43 -0
- package/dist/runtime-core/chatStep.js +121 -0
- package/dist/runtime-core/credentials.d.ts +49 -0
- package/dist/runtime-core/credentials.js +118 -0
- package/dist/runtime-core/errors.d.ts +30 -0
- package/dist/runtime-core/errors.js +36 -0
- package/dist/runtime-core/llm.d.ts +76 -0
- package/dist/runtime-core/llm.js +638 -0
- package/dist/runtime-core/redaction.d.ts +39 -0
- package/dist/runtime-core/redaction.js +79 -0
- package/dist/runtime-core/telemetry.d.ts +88 -0
- package/dist/runtime-core/telemetry.js +332 -0
- package/dist/runtime-core/tools.d.ts +15 -0
- package/dist/runtime-core/tools.js +12 -0
- package/dist/runtime-core/trace.d.ts +55 -0
- package/dist/runtime-core/trace.js +137 -0
- package/dist/runtime-core/types.d.ts +104 -0
- package/dist/runtime-core/types.js +13 -0
- package/dist/sandbox/sandbox.d.ts +68 -0
- package/dist/sandbox/sandbox.js +153 -0
- package/dist/sbom.json +263 -0
- package/dist/seam.d.ts +24 -0
- package/dist/seam.js +23 -0
- package/dist/security/secrets-baseline.d.ts +43 -0
- package/dist/security/secrets-baseline.js +176 -0
- package/dist/security/secrets.d.ts +45 -0
- package/dist/security/secrets.js +138 -0
- package/dist/session/export.d.ts +55 -0
- package/dist/session/export.js +81 -0
- package/dist/session/fork.d.ts +51 -0
- package/dist/session/fork.js +63 -0
- package/dist/session/format.d.ts +21 -0
- package/dist/session/format.js +49 -0
- package/dist/session/indexStore.d.ts +44 -0
- package/dist/session/indexStore.js +265 -0
- package/dist/session/log.d.ts +79 -0
- package/dist/session/log.js +157 -0
- package/dist/session/store.d.ts +9 -0
- package/dist/session/store.js +37 -0
- package/dist/telemetry/metrics.d.ts +25 -0
- package/dist/telemetry/metrics.js +26 -0
- package/package.json +17 -1
- package/src/authz/authz.ts +12 -0
- package/src/loop/stop.ts +43 -10
- package/src/runtime-core/chatStep.ts +22 -2
- package/src/runtime-core/llm.ts +96 -13
- package/src/runtime-core/types.ts +22 -1
- package/src/seam.ts +24 -0
- package/src/session/log.ts +1 -1
- package/.gitattributes +0 -11
- package/.github/workflows/ci.yml +0 -293
- package/.github/workflows/release.yml +0 -54
- package/adr/ADR-001.md +0 -4
- package/adr/ADR-002.md +0 -5
- package/adr/ADR-003.md +0 -35
- package/adr/host-onboarding.md +0 -68
- package/adr/mlps-audit-checklist.md +0 -41
- package/adr/win32-spike.md +0 -45
- package/docs/plugin-authoring.md +0 -82
- package/native/landlock-run/Makefile +0 -19
- package/native/landlock-run/landlock-run.c +0 -162
- package/scripts/contract-check.ts +0 -56
- package/scripts/invariant-n1.ts +0 -96
- package/scripts/package-check.ts +0 -29
- package/scripts/ptc-baseline/run-baseline.ts +0 -118
- package/scripts/release-artifacts.ts +0 -111
- package/scripts/release-pipeline.ts +0 -245
- package/scripts/secrets-baseline.ts +0 -33
- package/scripts/secrets-scan.ts +0 -56
- package/scripts/win32-ffi-debug.ts +0 -143
- package/scripts/win32-koffi-spike.ts +0 -537
- package/test/contract/eventmap.golden.json +0 -7
- package/test/fixtures/hello-plugin.ts +0 -5
- package/test/kernel.spec.ts +0 -136
- package/test/registry-e2e/run-e2e.ts +0 -213
- package/test/s11.spec.ts +0 -129
- package/test/s12.spec.ts +0 -135
- package/test/s13.spec.ts +0 -117
- package/test/s14.spec.ts +0 -117
- package/test/s15.spec.ts +0 -80
- package/test/s16.spec.ts +0 -133
- package/test/s17.spec.ts +0 -102
- package/test/s18.spec.ts +0 -81
- package/test/s19.spec.ts +0 -138
- package/test/s2.spec.ts +0 -245
- package/test/s20.spec.ts +0 -241
- package/test/s21.spec.ts +0 -231
- package/test/s22-m7-store-format.spec.ts +0 -135
- package/test/s23-m7-export-index.spec.ts +0 -189
- package/test/s24-signature-gate.spec.ts +0 -185
- package/test/s25-m8-runtime-core.spec.ts +0 -600
- package/test/s26-signature-closeout.spec.ts +0 -349
- package/test/s27-host-load-wiring.spec.ts +0 -134
- package/test/s28-session-index-followup.spec.ts +0 -236
- package/test/s29-otel-producer.spec.ts +0 -252
- package/test/s3.spec.ts +0 -179
- package/test/s30-release-artifacts.spec.ts +0 -127
- package/test/s31-real-model-wiring.spec.ts +0 -329
- package/test/s32-telemetry-mcp-ptc.spec.ts +0 -218
- package/test/s33-anthropic-adapter.spec.ts +0 -354
- package/test/s34-mcp-remote-transport.spec.ts +0 -250
- package/test/s35-config-ts-zod.spec.ts +0 -145
- package/test/s36-host-tool-surface.spec.ts +0 -250
- package/test/s37-projection-dedupe.spec.ts +0 -161
- package/test/s38-config-jiti.spec.ts +0 -116
- package/test/s39-otlp-span-semantics.spec.ts +0 -100
- package/test/s4.spec.ts +0 -169
- package/test/s40-metrics-cumulative.spec.ts +0 -67
- package/test/s41-mcp-serve-turn-span.spec.ts +0 -148
- package/test/s42-traceparent-propagation.spec.ts +0 -415
- package/test/s43-remote-http-traceparent.spec.ts +0 -262
- package/test/s5.spec.ts +0 -95
- package/test/s6.spec.ts +0 -169
- package/test/s7.spec.ts +0 -136
- package/test/s7b-baseline.spec.ts +0 -20
- package/test/s9.spec.ts +0 -89
- package/test/sandbox-escape/j17-orchestrate.sh +0 -30
- package/test/sandbox-escape/run-matrix.sh +0 -98
- package/tsconfig.json +0 -18
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 发布工程预演(本地 dry-run 版 8 阶段流水线,对齐《部署设计》§4/§6)
|
|
3
|
-
*
|
|
4
|
-
* 真实执行:门禁 13 项中本地可跑的 9 项(测试/不变量/密钥扫描/版本一致性/SBOM/校验和/
|
|
5
|
-
* 逃逸矩阵定义校验/依赖零原生审计/dist-tag 晋级模拟+回滚演练)
|
|
6
|
-
* DRY-RUN:npm publish(OIDC provenance)/ cosign keyless 签名 / GitHub Releases 归档
|
|
7
|
-
* ——需远端凭据与仓库,预演报告标注 DRY-RUN 与正式执行前置条件
|
|
8
|
-
*
|
|
9
|
-
* 运行:node --experimental-transform-types scripts/release-pipeline.ts
|
|
10
|
-
*/
|
|
11
|
-
import { execSync, execFileSync } from 'node:child_process'
|
|
12
|
-
import { createHash } from 'node:crypto'
|
|
13
|
-
import { readFileSync, writeFileSync, readdirSync, statSync, mkdirSync, existsSync } from 'node:fs'
|
|
14
|
-
import { join } from 'node:path'
|
|
15
|
-
import { buildReleaseArtifacts, releaseAssetNames } from './release-artifacts.ts'
|
|
16
|
-
|
|
17
|
-
const ROOT = new URL('..', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1')
|
|
18
|
-
const DIST = join(ROOT, 'dist')
|
|
19
|
-
const VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8')).version as string
|
|
20
|
-
const audit: any[] = []
|
|
21
|
-
const auditLog = (stage: string, gate: string, verdict: string, detail: Record<string, unknown> = {}) =>
|
|
22
|
-
audit.push({ stage, gate, verdict, ts: Date.now(), ...detail })
|
|
23
|
-
const sha256 = (s: string | Buffer) => createHash('sha256').update(s).digest('hex')
|
|
24
|
-
|
|
25
|
-
function walk(dir: string, ext: string): string[] {
|
|
26
|
-
const out: string[] = []
|
|
27
|
-
for (const name of readdirSync(dir)) {
|
|
28
|
-
const p = join(dir, name)
|
|
29
|
-
if (statSync(p).isDirectory()) out.push(...walk(p, ext))
|
|
30
|
-
else if (p.endsWith(ext)) out.push(p)
|
|
31
|
-
}
|
|
32
|
-
return out
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const results: Array<{ stage: string; gate: string; verdict: string; detail?: string }> = []
|
|
36
|
-
const record = (stage: string, gate: string, ok: boolean, detail = '', dryRun = false) => {
|
|
37
|
-
const verdict = dryRun ? 'DRY-RUN' : ok ? 'PASS' : 'FAIL'
|
|
38
|
-
results.push({ stage, gate, verdict, detail })
|
|
39
|
-
auditLog(stage, gate, verdict, { detail })
|
|
40
|
-
if (!dryRun && !ok) throw new Error(`GATE FAILED: ${stage}/${gate} ${detail}`)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ── 阶段 1:PR 门禁(int)──
|
|
44
|
-
{
|
|
45
|
-
const stage = 'S1-PR 门禁'
|
|
46
|
-
// reporter 钉死 tap:非 TTY 默认 reporter 随 Node 版本漂移(23.x 输出 spec 格式,正则解析不到 # pass/# fail)
|
|
47
|
-
const out = execSync('node --experimental-transform-types --test --test-reporter=tap test/*.spec.ts', { cwd: ROOT, encoding: 'utf-8' })
|
|
48
|
-
const pass = /# pass (\d+)/.exec(out)?.[1]
|
|
49
|
-
const fail = /# fail (\d+)/.exec(out)?.[1]
|
|
50
|
-
record(stage, 'G-01 单测全绿', fail === '0', `${pass} pass / ${fail} fail`)
|
|
51
|
-
record(stage, 'G-02 N1 日志不变量断言', out.includes('48/48') || fail === '0', 'assertModelVisibleLogged 内嵌于测试套件')
|
|
52
|
-
// 密钥扫描(gitleaks 等价简化规则集:真实 CI 用 gitleaks 全量规则)
|
|
53
|
-
// 豁免清单与 scripts/secrets-scan.ts(CI 权威版)保持同步:规则库 + E-5 万级基准集生成器
|
|
54
|
-
// (内含仿真密钥模板,命中属预期)+ 对应标定 spec;豁免须显式列文件路径(M2-S10 口径)。
|
|
55
|
-
// 同步失守教训:E-5 入库时只扩了 CI 清单,本地 G-03 漏扩 → 复跑误 FAIL(2026-09-26 已对齐)
|
|
56
|
-
const secretPatterns = [/sk-[A-Za-z0-9]{16,}/, /ghp_[A-Za-z0-9]{20,}/, /BEGIN (RSA |EC )?PRIVATE KEY/, /AKIA[A-Z0-9]{12,}/]
|
|
57
|
-
const scanExempt = (f: string) => {
|
|
58
|
-
const n = f.replaceAll('\\', '/')
|
|
59
|
-
return /\/src\/security\/secrets(\.ts|-baseline\.ts)$/.test(n)
|
|
60
|
-
|| /\/test\/s7(b-baseline)?\.spec\.ts$/.test(n)
|
|
61
|
-
|| /\/scripts\/secrets-baseline\.ts$/.test(n)
|
|
62
|
-
}
|
|
63
|
-
let hits = 0
|
|
64
|
-
let scanned = 0
|
|
65
|
-
for (const f of [...walk(join(ROOT, 'src'), '.ts'), ...walk(join(ROOT, 'test'), '.ts')]) {
|
|
66
|
-
if (scanExempt(f)) continue
|
|
67
|
-
scanned++
|
|
68
|
-
const content = readFileSync(f, 'utf-8')
|
|
69
|
-
for (const p of secretPatterns) if (p.test(content)) hits++
|
|
70
|
-
}
|
|
71
|
-
record(stage, 'G-03 硬编码密钥扫描 = 0', hits === 0, `${hits} hits(扫描 ${scanned} 个 .ts,豁免:secrets.ts 规则库 + s7 标定基准,豁免清单见脚本注记)`)
|
|
72
|
-
// 依赖审计:1.5-S3(D-22)zod 为首个常规运行时依赖 → 门禁口径从「零依赖断言」切换为
|
|
73
|
-
// npm audit(omit=dev,high 阻塞)——与 ci.yml j01 依赖审计同款;audit 不可达(离线)= DRY-RUN
|
|
74
|
-
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'))
|
|
75
|
-
const depCount = Object.keys(pkg.dependencies ?? {}).length
|
|
76
|
-
let auditOk = true
|
|
77
|
-
let auditDetail = '无运行时依赖(audit 免除)'
|
|
78
|
-
if (depCount > 0) {
|
|
79
|
-
if (process.env.REMOTE_CHECK === '1') {
|
|
80
|
-
try {
|
|
81
|
-
execSync('npm audit --omit=dev --audit-level=high', { cwd: ROOT, stdio: 'pipe' })
|
|
82
|
-
auditDetail = `运行时依赖 ${depCount} 项(D-22/D-24:zod ^3 + jiti ^2);npm audit --omit=dev --audit-level=high 0 findings`
|
|
83
|
-
} catch {
|
|
84
|
-
auditOk = false
|
|
85
|
-
auditDetail = `运行时依赖 ${depCount} 项;npm audit --audit-level=high 有 findings(阻塞——G-04 D-22 口径)`
|
|
86
|
-
}
|
|
87
|
-
} else {
|
|
88
|
-
auditDetail = `运行时依赖 ${depCount} 项(D-22/D-24:zod ^3 + jiti ^2);npm audit 在 REMOTE_CHECK=1 实跑(DRY-RUN)`
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
record(stage, 'G-04 依赖审计', auditOk, auditDetail)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ── 阶段 2:构建与制品 ──
|
|
95
|
-
{
|
|
96
|
-
const stage = 'S2-构建与制品'
|
|
97
|
-
// 1.3-S2(W4-1):制品构建抽至 release-artifacts.ts(本地流水线与 CI j16 双端单一事实源——
|
|
98
|
-
// 同仓树逐字节同制品);此处产出 tgz + SHA256SUMS + sbom,G-11 在 S6 独立复核
|
|
99
|
-
const built = buildReleaseArtifacts({
|
|
100
|
-
version: VERSION,
|
|
101
|
-
onRecord: (s, detail) => auditLog(stage, s.toUpperCase(), 'PASS', { detail }),
|
|
102
|
-
})
|
|
103
|
-
record(stage, 'G-05 SBOM 生成', true, `${built.components} 个组件(运行时依赖 0 项;tar.gz ${built.bytes} bytes 已构建——W4 单一事实源)`)
|
|
104
|
-
// 版本一致性:精确 semver(含 prerelease——1.0-rc.x 是 rc 渠道合法形态;禁止 build 元数据与 loose 版本)
|
|
105
|
-
const ok = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(VERSION) && !/\+/.test(VERSION)
|
|
106
|
-
record(stage, 'G-06 版本一致性', ok, `version=${VERSION}(精确 semver 含 prerelease——rc 渠道合法形态;npm 不可变语义:禁止覆盖已发布版本)`)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ── 阶段 3:沙箱逃逸回归(Linux 门禁,WSL2 实跑后凭审计产物转 PASS)──
|
|
110
|
-
{
|
|
111
|
-
const stage = 'S3-沙箱逃逸回归'
|
|
112
|
-
const matrixFile = join(ROOT, '..', 'deepseek-harness', 'm0-poc', 'poc4-sandbox-escape.ts')
|
|
113
|
-
// Linux 实跑审计产物(WSL2 内跑完后回拷,20 行逐用例 verdict=PASS)
|
|
114
|
-
const auditLinux = join(ROOT, '..', 'deepseek-harness', 'm0-poc', 'poc4-audit-linux.jsonl')
|
|
115
|
-
let linuxPass = false
|
|
116
|
-
let linuxDetail = '20 条用例 × 5 类(v0 冻结);尚未在 Linux/WSL2 实跑——AL-04 P1:任一逃逸阻塞发布'
|
|
117
|
-
let linuxDryRun = process.platform !== 'linux'
|
|
118
|
-
if (existsSync(auditLinux)) {
|
|
119
|
-
const cases = readFileSync(auditLinux, 'utf-8').trim().split('\n').filter(Boolean).map(l => JSON.parse(l))
|
|
120
|
-
const passN = cases.filter(c => c.verdict === 'PASS').length
|
|
121
|
-
linuxPass = cases.length === 20 && passN === 20
|
|
122
|
-
linuxDryRun = false
|
|
123
|
-
linuxDetail = linuxPass
|
|
124
|
-
? `20/20 PASS(backend=${cases[0]?.backend ?? '?'})→ AL-04 满足`
|
|
125
|
-
: `审计产物不完整/含非 PASS:${cases.length} 行,${passN} PASS——AL-04 P1 任一逃逸即阻塞`
|
|
126
|
-
} else if (!existsSync(matrixFile)) {
|
|
127
|
-
linuxPass = false
|
|
128
|
-
linuxDryRun = false
|
|
129
|
-
linuxDetail = '逃逸矩阵用例定义文件缺失'
|
|
130
|
-
}
|
|
131
|
-
record(stage, 'G-07 逃逸矩阵实跑', linuxPass, linuxDetail, linuxDryRun)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ── 阶段 4-5:provenance 与远端归档(远端依赖,DRY-RUN)──
|
|
135
|
-
// G-08 双轨语义(1.3 D-15):本地 keypair no-tlog = 预演态(S6b);CI j16 keyless OIDC+Rekor = 正式态
|
|
136
|
-
// G-10:REMOTE_CHECK=1 且具备 GH_TOKEN 时实查 Release 资产齐备性,否则 DRY-RUN
|
|
137
|
-
{
|
|
138
|
-
const stage = 'S4-provenance与远端归档'
|
|
139
|
-
let g10Ok = false
|
|
140
|
-
let g10Detail = '需远端仓库(远端)'
|
|
141
|
-
let g10Dry = true
|
|
142
|
-
if (process.env.GH_TOKEN && process.env.REMOTE_CHECK === '1') {
|
|
143
|
-
try {
|
|
144
|
-
const ghBin = 'D:/WorkBuddy/agent/tools/bin/gh.exe'
|
|
145
|
-
// tag 形态双兼容:v 前缀(v1.0.0 惯例)与无前缀(1.1.0 实挂形态)都查,并集去重——
|
|
146
|
-
// 硬编码单形态会把真实归档误判 FAIL(4c84b17 G-09 期望 tag 动态化同类教训,2026-09-27)
|
|
147
|
-
const fetchAssets = (tag: string): string[] => {
|
|
148
|
-
try {
|
|
149
|
-
return execFileSync(ghBin, ['api', `repos/lqc624/car-runtime/releases/tags/${tag}`, '--jq', '.assets[].name'], { encoding: 'utf-8', env: process.env }).trim().split('\n').filter(Boolean)
|
|
150
|
-
} catch { return [] }
|
|
151
|
-
}
|
|
152
|
-
const assets = [...new Set([...fetchAssets(`v${VERSION}`), ...fetchAssets(VERSION)])]
|
|
153
|
-
// 资产清单单一事实源(1.3 W4-4/R-3):releaseAssetNames(五件套,无 car-release.pub)
|
|
154
|
-
const need = releaseAssetNames(VERSION)
|
|
155
|
-
const missing = need.filter(n => !assets.includes(n))
|
|
156
|
-
g10Ok = missing.length === 0
|
|
157
|
-
g10Dry = false
|
|
158
|
-
g10Detail = g10Ok
|
|
159
|
-
? `Release v${VERSION} 归档齐备(${assets.length} assets:tgz+签名bundle+SHA256SUMS+SBOM+审计——D-14 五件套)`
|
|
160
|
-
: `Release 资产缺失:${missing.join(', ')}`
|
|
161
|
-
} catch { /* 远端不可达或 Release 不存在 → 保持 DRY-RUN */ }
|
|
162
|
-
}
|
|
163
|
-
record(stage, 'G-10 GitHub Releases 归档(napi 二进制+SBOM+校验和)', g10Ok, g10Detail, g10Dry)
|
|
164
|
-
// G-09:REMOTE_CHECK=1 时实查 npm registry——版本在架 + 期望 dist-tag + provenance 证明。
|
|
165
|
-
// 期望 tag 按版本形态动态选择(与 release.yml publish tag 同口径,M5 §1.1):
|
|
166
|
-
// prerelease(1.0.0-rc.1)→ rc;稳定版(1.0.0)→ latest——1.0 发布后 rc tag 保留指向 rc.1,
|
|
167
|
-
// 硬编码查 rc 会把 1.0.0 的 post-publish 复跑误判 FAIL(09-26 与 release.yml 同批修口径)
|
|
168
|
-
const expectedTag = VERSION.includes('-') ? 'rc' : 'latest'
|
|
169
|
-
let g9Ok = false
|
|
170
|
-
let g9Detail = '需 npm publish 通道(远端)'
|
|
171
|
-
let g9Dry = true
|
|
172
|
-
if (process.env.REMOTE_CHECK === '1') {
|
|
173
|
-
try {
|
|
174
|
-
const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
175
|
-
const view = execFileSync(npmBin, ['view', '@lqc123qwe/car-runtime', 'version', `dist-tags.${expectedTag}`, 'dist.attestations.provenance.predicateType', '--json'], { encoding: 'utf-8', shell: process.platform === 'win32' })
|
|
176
|
-
const j = JSON.parse(view)
|
|
177
|
-
const onRegistry = j.version === VERSION
|
|
178
|
-
const tagOk = j[`dist-tags.${expectedTag}`] === VERSION
|
|
179
|
-
const provenance = typeof j['dist.attestations.provenance.predicateType'] === 'string' && j['dist.attestations.provenance.predicateType'].includes('slsa.dev/provenance')
|
|
180
|
-
g9Ok = onRegistry && tagOk && provenance
|
|
181
|
-
g9Dry = false
|
|
182
|
-
g9Detail = g9Ok
|
|
183
|
-
? `npm 在架 @lqc123qwe/car-runtime@${VERSION}(${expectedTag} tag ✓ + SLSA provenance ✓,Actions OIDC 可信发布)`
|
|
184
|
-
: `registry 不满足:version=${j.version} ${expectedTag}=${j[`dist-tags.${expectedTag}`]} provenance=${j['dist.attestations.provenance.predicateType'] ?? '无'}`
|
|
185
|
-
} catch { /* registry 不可达或包未发布 → 保持 DRY-RUN */ }
|
|
186
|
-
}
|
|
187
|
-
record(stage, 'G-09 npm provenance(OIDC 可信发布)', g9Ok, g9Detail, g9Dry)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// ── 阶段 6:制品校验(G-11:对 S2 产出的制品做独立复核,不重建不写死) ──
|
|
191
|
-
const TARBALL = join(DIST, `car-runtime-${VERSION}.tgz`)
|
|
192
|
-
{
|
|
193
|
-
const stage = 'S6-制品封装'
|
|
194
|
-
// 校验和必须对「裸字节」求哈希——曾误写为 sha256(bytes.toString('base64')),
|
|
195
|
-
// 记录值成为 base64 字符串的哈希,外部 sha256sum 校验必然失败(rc.1 已发生)。
|
|
196
|
-
const bytes = readFileSync(TARBALL)
|
|
197
|
-
const actual = sha256(bytes)
|
|
198
|
-
const [recorded, recordedName] = readFileSync(join(DIST, 'SHA256SUMS'), 'utf-8').trim().split(/\s+/)
|
|
199
|
-
const ok = recorded === actual && recordedName === `car-runtime-${VERSION}.tgz`
|
|
200
|
-
record(stage, 'G-11 制品校验和(SHA-256)', ok, ok
|
|
201
|
-
? `car-runtime-${VERSION}.tgz (${bytes.length} bytes) → SHA256SUMS ${recorded!.slice(0, 16)}…(S2 产出独立复核,外部 sha256sum 可验证)`
|
|
202
|
-
: `校验和自检失败:SHA256SUMS 记录 ${recorded} ≠ 制品裸字节哈希 ${actual}`)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// ── 阶段 6b:cosign 本地实签 + 验签(G-08,keypair 模式;CI 正式发布切换 keyless OIDC)──
|
|
206
|
-
{
|
|
207
|
-
const stage = 'S6b-cosign签名'
|
|
208
|
-
const cosignBin = process.env.COSIGN_BIN ?? 'D:/WorkBuddy/agent/tools/bin/cosign.exe'
|
|
209
|
-
const keysDir = 'D:/WorkBuddy/agent/tools/cosign-keys'
|
|
210
|
-
const sigConfig = join(keysDir, 'no-tlog.json')
|
|
211
|
-
const bundle = join(DIST, `car-runtime-${VERSION}.tgz.sig.bundle`)
|
|
212
|
-
const env = { ...process.env, COSIGN_PASSWORD: readFileSync(join(keysDir, 'car-release.password'), 'utf-8').trim() }
|
|
213
|
-
execFileSync(cosignBin, ['sign-blob', '--key', join(keysDir, 'car-release.key'),
|
|
214
|
-
'--signing-config', sigConfig, '--bundle', bundle, TARBALL], { env })
|
|
215
|
-
execFileSync(cosignBin, ['verify-blob', '--key', join(keysDir, 'car-release.pub'),
|
|
216
|
-
'--insecure-ignore-tlog', '--bundle', bundle, TARBALL], { env })
|
|
217
|
-
record(stage, 'G-08 cosign 签名', existsSync(bundle), `本地 keypair 实签+验签 PASS(no-tlog 模式,bundle: car-runtime-${VERSION}.tgz.sig.bundle)——D-15 预演轨;正式轨 = CI j16 keyless OIDC+Rekor`)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// ── 阶段 7:dist-tag 晋级模拟 + 回滚演练(发布渠道语义)──
|
|
221
|
-
{
|
|
222
|
-
const stage = 'S7-dist-tag 晋级模拟'
|
|
223
|
-
// 本地模拟 npm 三渠道:beta → rc → latest(等价金丝雀)
|
|
224
|
-
const registry = existsSync(join(DIST, 'registry-sim.json'))
|
|
225
|
-
? JSON.parse(readFileSync(join(DIST, 'registry-sim.json'), 'utf-8'))
|
|
226
|
-
: { 'beta': null, 'rc': null, 'latest': null, immutable: [] }
|
|
227
|
-
registry['beta'] = VERSION
|
|
228
|
-
writeFileSync(join(DIST, 'registry-sim.json'), JSON.stringify(registry, null, 2))
|
|
229
|
-
record(stage, 'G-12 渠道晋级:beta → rc → latest', registry['beta'] === VERSION, JSON.stringify(registry))
|
|
230
|
-
// 回滚演练:latest 回指旧版本 + 前滚补丁纪律(npm 版本不可变 → 回滚=dist-tag 回指)
|
|
231
|
-
registry['latest'] = registry['rc'] ?? registry['beta']
|
|
232
|
-
writeFileSync(join(DIST, 'registry-sim.json'), JSON.stringify(registry, null, 2))
|
|
233
|
-
record(stage, 'G-13 回滚演练:latest 回指 + 前滚补丁', true, 'npm 版本不可变(禁止覆盖已发布版本);回滚=dist-tag 回指,破坏性变更走 major+reader 兼容(SessionEventMap 只增不删)')
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// ── 阶段 8:审计留痕 ──
|
|
237
|
-
writeFileSync(join(DIST, 'release-audit.jsonl'), audit.map(a => JSON.stringify(a)).join('\n') + '\n')
|
|
238
|
-
|
|
239
|
-
// ── 汇总 ──
|
|
240
|
-
console.log('\n===== 发布工程预演结果 =====')
|
|
241
|
-
for (const r of results) console.log(` [${r.verdict.padEnd(7)}] ${r.stage} / ${r.gate}${r.detail ? ' — ' + r.detail : ''}`)
|
|
242
|
-
const pass = results.filter(r => r.verdict === 'PASS').length
|
|
243
|
-
const dry = results.filter(r => r.verdict === 'DRY-RUN').length
|
|
244
|
-
console.log(`\n合计:${pass} PASS / ${dry} DRY-RUN / 0 FAIL(制品:dist/ 五件套——tar.gz + 签名bundle + SHA256SUMS + sbom + release-audit)`)
|
|
245
|
-
console.log('DRY-RUN 项正式执行前置:GitHub 仓库 + OIDC + npm publish 凭据 + cosign + Linux CI runner(逃逸矩阵 G-07);正式签名轨 = CI j16 keyless(D-15)')
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* E-5 secrets 基准集 CLI(M2 退出标准 #4 终验入口)
|
|
3
|
-
*
|
|
4
|
-
* 用法:node --experimental-transform-types scripts/secrets-baseline.ts
|
|
5
|
-
* 产出:dist/ci/secrets-baseline.json(计数与聚合指标,无样本原文)
|
|
6
|
-
* 退出码:判据全过 = 0;任一判据不满足 = 1(漏报 ≥1.5% / FPR >5% / precision <80%)
|
|
7
|
-
*/
|
|
8
|
-
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
9
|
-
import { runBaseline } from '../src/security/secrets-baseline.ts'
|
|
10
|
-
|
|
11
|
-
const result = runBaseline(1000, 9000)
|
|
12
|
-
const report = {
|
|
13
|
-
generatedAt: new Date().toISOString(),
|
|
14
|
-
seedPos: 20260918,
|
|
15
|
-
seedNeg: 20260919,
|
|
16
|
-
...result,
|
|
17
|
-
}
|
|
18
|
-
mkdirSync('dist/ci', { recursive: true })
|
|
19
|
-
writeFileSync('dist/ci/secrets-baseline.json', JSON.stringify(report, null, 2))
|
|
20
|
-
|
|
21
|
-
console.log('E-5 secrets baseline (1:9, 1000 pos + 9000 neg):')
|
|
22
|
-
console.log(` TP=${result.tp} FN=${result.fn} FP=${result.fp} TN=${result.tn}`)
|
|
23
|
-
console.log(` missRate=${(result.missRate * 100).toFixed(2)}% (criterion <1.5%)`)
|
|
24
|
-
console.log(` FPR=${(result.fpr * 100).toFixed(2)}% (criterion <=5%)`)
|
|
25
|
-
console.log(` precision=${(result.precision * 100).toFixed(2)}% (criterion >=80%)`)
|
|
26
|
-
console.log(` report: dist/ci/secrets-baseline.json`)
|
|
27
|
-
if (!result.pass) {
|
|
28
|
-
console.log(` missByClass=${JSON.stringify(result.missByClass)}`)
|
|
29
|
-
console.log(` fpByClass=${JSON.stringify(result.fpByClass)}`)
|
|
30
|
-
console.error('E-5 CRITERIA VIOLATED')
|
|
31
|
-
process.exit(1)
|
|
32
|
-
}
|
|
33
|
-
console.log(' PASS')
|
package/scripts/secrets-scan.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* J-01 lint-scan-build · 仓库密钥扫描门禁(M2部署设计增补 §2.3 / ci.yml 幽灵注释兑现项)
|
|
3
|
-
*
|
|
4
|
-
* 口径:扫 git 跟踪文件(git ls-files),命中即 exit 1——补 CI 供应链第二道门(s7.spec 的
|
|
5
|
-
* 规则库是样本级断言,本脚本是仓库级常驻扫描;两者共用同一模式集口径)。
|
|
6
|
-
* 规则集(高置信,宁缺勿滥——低置信规则走人工评审,不进 fail-closed 门禁):
|
|
7
|
-
* GitHub PAT / npm token / AWS AKIA / PEM 私钥块 / 通用密钥赋值 / ≥60 位连续 base64
|
|
8
|
-
* 豁免:环境变量名引用(process.env.X / ${X})不算命中;文本行内示例占位不算命中。
|
|
9
|
-
*/
|
|
10
|
-
import { execFileSync } from 'node:child_process'
|
|
11
|
-
import { readFileSync } from 'node:fs'
|
|
12
|
-
|
|
13
|
-
const RULES: Array<[string, RegExp]> = [
|
|
14
|
-
['github-pat', /gh[pousr]_[A-Za-z0-9]{20,}/],
|
|
15
|
-
['npm-token', /npm_[A-Za-z0-9]{30,}/],
|
|
16
|
-
['aws-access-key', /AKIA[0-9A-Z]{16}/],
|
|
17
|
-
['pem-private-key', /-----BEGIN [A-Z ]*PRIVATE KEY-----/],
|
|
18
|
-
['generic-secret-assign', /(?:password|passwd|secret|api[_-]?key|access[_-]?token)\s*[:=]\s*['"][^'"]{12,}['"]/i],
|
|
19
|
-
['long-base64-blob', /[A-Za-z0-9+/]{80,}={0,2}/],
|
|
20
|
-
]
|
|
21
|
-
|
|
22
|
-
const tracked = execFileSync('git', ['ls-files'], { encoding: 'utf8' })
|
|
23
|
-
.split(/\r?\n/).filter(Boolean)
|
|
24
|
-
|
|
25
|
-
let findings = 0
|
|
26
|
-
for (const file of tracked) {
|
|
27
|
-
let content: string
|
|
28
|
-
try { content = readFileSync(file, 'utf8') } catch { continue /* 二进制/不可读:跳过(sha 类二进制物无密钥文本面) */ }
|
|
29
|
-
if (content.includes('\u0000')) continue // 二进制
|
|
30
|
-
content.split('\n').forEach((line, i) => {
|
|
31
|
-
for (const [name, re] of RULES) {
|
|
32
|
-
if (!re.test(line)) continue
|
|
33
|
-
// 豁免 1:环境变量引用(运行时注入,非硬编码)
|
|
34
|
-
if (name === 'generic-secret-assign' && /process\.env\.|\$\{[A-Z_]+\}/.test(line)) continue
|
|
35
|
-
// 豁免 2:锁文件 integrity 字段(sha512- 前缀的依赖指纹,非密钥)
|
|
36
|
-
if (name === 'long-base64-blob' && /"integrity":\s*"sha512-/.test(line)) continue
|
|
37
|
-
// 豁免 3:规则/样本/基准集定义文件自身(模式定义与基准集生成处必然"命中")
|
|
38
|
-
// - secrets.ts = 规则库;secrets-baseline.ts/s7b.spec.ts = E-5 万级基准集生成器(内含仿真密钥模板)
|
|
39
|
-
if (
|
|
40
|
-
file.endsWith('secrets-scan.ts') ||
|
|
41
|
-
/^src\/security\/secrets(\.ts|-baseline\.ts)$/.test(file) ||
|
|
42
|
-
/^test\/s7(b-baseline)?\.spec\.ts$/.test(file) ||
|
|
43
|
-
file === 'scripts/secrets-baseline.ts'
|
|
44
|
-
) continue
|
|
45
|
-
console.error(` [${name}] ${file}:${i + 1} ${line.trim().slice(0, 90)}`)
|
|
46
|
-
findings++
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const summary = JSON.stringify({ gate: 'J-01-secrets-scan', files: tracked.length, findings, ok: findings === 0, ts: Date.now() })
|
|
52
|
-
if (findings > 0) {
|
|
53
|
-
console.error(`FAIL ${summary}`)
|
|
54
|
-
process.exit(1)
|
|
55
|
-
}
|
|
56
|
-
console.log(`PASS ${summary}`)
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* win32 FFI 诊断矩阵(M5-S26 临时诊断,非门禁):定位 windows-2025 runner 上
|
|
3
|
-
* CreateProcessAsUserW 子进程 0xC0000142(STATUS_DLL_INIT_FAILED)的确切触发条件。
|
|
4
|
-
* 矩阵:API(AsUser/WithToken/normal)× 环境块(NULL/显式 Unicode)×
|
|
5
|
-
* 标志(CREATE_NO_WINDOW/DETACHED_PROCESS)× 程序(cmd/whoami/node)× token(原发/受限)。
|
|
6
|
-
* 本机(非提权交互会话)全绿为基线;runner 上首个失败行即回归点。
|
|
7
|
-
*/
|
|
8
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
|
9
|
-
import { tmpdir } from 'node:os'
|
|
10
|
-
import path from 'node:path'
|
|
11
|
-
|
|
12
|
-
const koffi = (await import('koffi')).default ?? (await import('koffi') as any)
|
|
13
|
-
const kernel32 = koffi.load('kernel32.dll')
|
|
14
|
-
const advapi32 = koffi.load('advapi32.dll')
|
|
15
|
-
|
|
16
|
-
const CREATE_NO_WINDOW = 0x08000000
|
|
17
|
-
const CREATE_SUSPENDED = 0x00000004
|
|
18
|
-
const CREATE_UNICODE_ENVIRONMENT = 0x00000400
|
|
19
|
-
const DETACHED_PROCESS = 0x00000008
|
|
20
|
-
const STILL_ACTIVE = 259
|
|
21
|
-
const PSEUDO_CURRENT_PROCESS = BigInt.asUintN(64, -1n)
|
|
22
|
-
const SystemRoot = process.env.SystemRoot ?? 'C:\\Windows'
|
|
23
|
-
|
|
24
|
-
const SID_AND_ATTRIBUTES = koffi.struct('SID_AND_ATTRIBUTES', { Sid: 'void *', Attributes: 'uint32' })
|
|
25
|
-
const CloseHandle = kernel32.func('int32 __stdcall CloseHandle(uint64 hObject)')
|
|
26
|
-
const WaitForSingleObject = kernel32.func('uint32 __stdcall WaitForSingleObject(uint64 hHandle, uint32 dwMilliseconds)')
|
|
27
|
-
const ResumeThread = kernel32.func('uint32 __stdcall ResumeThread(uint64 hThread)')
|
|
28
|
-
const GetExitCodeProcess = kernel32.func('int32 __stdcall GetExitCodeProcess(uint64 hProcess, void *lpExitCode)')
|
|
29
|
-
const OpenProcessToken = advapi32.func('int32 __stdcall OpenProcessToken(uint64 ProcessHandle, uint32 DesiredAccess, void *TokenHandle)')
|
|
30
|
-
const CreateRestrictedToken = advapi32.func('int32 __stdcall CreateRestrictedToken(uint64 ExistingTokenHandle, uint32 Flags, uint32 DisableSidCount, SID_AND_ATTRIBUTES *SidsToDisable, uint32 DeletePrivilegeCount, void *PrivilegesToDelete, uint32 RestrictedSidCount, void *SidsToRestrict, void *NewTokenHandle)')
|
|
31
|
-
const CreateProcessAsUserW = advapi32.func('int32 __stdcall CreateProcessAsUserW(uint64 hToken, void *lpApplicationName, void *lpCommandLine, void *pa, void *ta, int32 bInherit, uint32 flags, void *env, void *dir, void *si, void *pi)')
|
|
32
|
-
const CreateProcessW = kernel32.func('int32 __stdcall CreateProcessW(void *lpApplicationName, void *lpCommandLine, void *pa, void *ta, int32 bInherit, uint32 flags, void *env, void *dir, void *si, void *pi)')
|
|
33
|
-
const CreateProcessWithTokenW = advapi32.func('int32 __stdcall CreateProcessWithTokenW(uint64 hToken, uint32 logonFlags, void *lpApplicationName, void *lpCommandLine, uint32 flags, void *env, void *dir, void *si, void *pi)')
|
|
34
|
-
|
|
35
|
-
function buildEnvBlock(): Buffer {
|
|
36
|
-
const env: Record<string, string> = {}
|
|
37
|
-
for (const [k, v] of Object.entries(process.env)) if (v !== undefined) env[k] = v
|
|
38
|
-
if (env.SystemRoot === undefined) env.SystemRoot = SystemRoot
|
|
39
|
-
if (env.windir === undefined) env.windir = SystemRoot
|
|
40
|
-
if (env.SystemDrive === undefined) env.SystemDrive = 'C:'
|
|
41
|
-
return Buffer.from(Object.entries(env).map(([k, v]) => `${k}=${v}`).join('\0') + '\0\0', 'utf16le')
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function buildAdminSid(): Buffer {
|
|
45
|
-
const b = Buffer.alloc(16)
|
|
46
|
-
b[0] = 1; b[1] = 2; b.set([0, 0, 0, 0, 0, 5], 2)
|
|
47
|
-
b.writeUInt32LE(32, 8); b.writeUInt32LE(544, 12)
|
|
48
|
-
return b
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function hexExit(code: number): string {
|
|
52
|
-
return code === STILL_ACTIVE ? 'STILL_ACTIVE' : (code >= 0x80000000 ? `0x${code.toString(16).toUpperCase()}` : String(code))
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface Variant {
|
|
56
|
-
label: string
|
|
57
|
-
api: 'normal' | 'asUser' | 'withToken'
|
|
58
|
-
token: 'plain' | 'restricted'
|
|
59
|
-
env: 'null' | 'block'
|
|
60
|
-
flags: number
|
|
61
|
-
program: 'cmd' | 'whoami' | 'node'
|
|
62
|
-
explicitAppName?: boolean
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const results: string[] = []
|
|
66
|
-
async function runVariant(v: Variant, hToken: bigint, hRestricted: bigint, execPath: string): Promise<void> {
|
|
67
|
-
const dir = mkdtempSync(path.join(tmpdir(), 'car-dbg-'))
|
|
68
|
-
const outFile = path.join(dir, 'out.txt')
|
|
69
|
-
const program = v.program === 'cmd' ? path.join(SystemRoot, 'System32', 'cmd.exe')
|
|
70
|
-
: v.program === 'whoami' ? path.join(SystemRoot, 'System32', 'whoami.exe')
|
|
71
|
-
: execPath
|
|
72
|
-
const cmdline = v.program === 'cmd'
|
|
73
|
-
? `"${program}" /c exit 0`
|
|
74
|
-
: v.program === 'whoami'
|
|
75
|
-
? `"${program}" /priv /fo list`
|
|
76
|
-
: `"${program}" -e process.exit(0)`
|
|
77
|
-
const cmdBuf = Buffer.from(cmdline + '\0', 'utf16le')
|
|
78
|
-
const si = Buffer.alloc(104); si.writeUInt32LE(104, 0)
|
|
79
|
-
const pi = Buffer.alloc(24)
|
|
80
|
-
const envArg = v.env === 'block' ? buildEnvBlock() : null
|
|
81
|
-
const flags = v.env === 'block' ? (v.flags | CREATE_UNICODE_ENVIRONMENT) : v.flags
|
|
82
|
-
const tok = v.token === 'restricted' ? hRestricted : hToken
|
|
83
|
-
let ok = 0
|
|
84
|
-
try {
|
|
85
|
-
if (v.api === 'normal') {
|
|
86
|
-
ok = CreateProcessW(null, cmdBuf, null, null, 0, flags, envArg, null, si, pi)
|
|
87
|
-
} else if (v.api === 'asUser') {
|
|
88
|
-
ok = CreateProcessAsUserW(tok, null, cmdBuf, null, null, 0, flags, envArg, null, si, pi)
|
|
89
|
-
} else {
|
|
90
|
-
ok = CreateProcessWithTokenW(tok, 0, null, cmdBuf, flags, envArg, null, si, pi)
|
|
91
|
-
}
|
|
92
|
-
if (!ok) {
|
|
93
|
-
results.push(`${v.label}: CREATE_FAILED rc=0`)
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
const hProcess = pi.readBigUInt64LE(0)
|
|
97
|
-
const hThread = pi.readBigUInt64LE(8)
|
|
98
|
-
const wait = WaitForSingleObject(hProcess, 30000)
|
|
99
|
-
const codeBuf = Buffer.alloc(4)
|
|
100
|
-
GetExitCodeProcess(hProcess, codeBuf)
|
|
101
|
-
const code = codeBuf.readUInt32LE(0)
|
|
102
|
-
CloseHandle(hThread); CloseHandle(hProcess)
|
|
103
|
-
results.push(`${v.label}: created=1 wait=${wait === 0 ? 'signaled' : 'timeout'} exit=${hexExit(code)}`)
|
|
104
|
-
} catch (e) {
|
|
105
|
-
results.push(`${v.label}: JS_ERROR ${e instanceof Error ? e.message : String(e)}`)
|
|
106
|
-
} finally {
|
|
107
|
-
rmSync(dir, { recursive: true, force: true })
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async function main() {
|
|
112
|
-
console.log('=== win32 FFI debug matrix ===')
|
|
113
|
-
const hTokBuf = Buffer.alloc(8)
|
|
114
|
-
if (!OpenProcessToken(PSEUDO_CURRENT_PROCESS, 0x1 | 0x2 | 0x8, hTokBuf)) throw new Error('OpenProcessToken failed')
|
|
115
|
-
const hToken = hTokBuf.readBigUInt64LE(0)
|
|
116
|
-
const sidBuf = buildAdminSid()
|
|
117
|
-
const hRestrBuf = Buffer.alloc(8)
|
|
118
|
-
if (!CreateRestrictedToken(hToken, 0x1, 1, [{ Sid: sidBuf, Attributes: 0 }], 0, null, 0, null, hRestrBuf)) throw new Error('CreateRestrictedToken failed')
|
|
119
|
-
const hRestricted = hRestrBuf.readBigUInt64LE(0)
|
|
120
|
-
const execPath = process.execPath
|
|
121
|
-
|
|
122
|
-
const matrix: Variant[] = [
|
|
123
|
-
{ label: '01 normal cmd nowin env=null ', api: 'normal', token: 'plain', env: 'null', flags: CREATE_NO_WINDOW, program: 'cmd' },
|
|
124
|
-
{ label: '02 asUser plain cmd nowin env=null ', api: 'asUser', token: 'plain', env: 'null', flags: CREATE_NO_WINDOW, program: 'cmd' },
|
|
125
|
-
{ label: '03 asUser plain cmd nowin env=block ', api: 'asUser', token: 'plain', env: 'block', flags: CREATE_NO_WINDOW, program: 'cmd' },
|
|
126
|
-
{ label: '04 asUser plain cmd DETACH env=block ', api: 'asUser', token: 'plain', env: 'block', flags: DETACHED_PROCESS, program: 'cmd' },
|
|
127
|
-
{ label: '05 asUser plain whoami nowin env=block', api: 'asUser', token: 'plain', env: 'block', flags: CREATE_NO_WINDOW, program: 'whoami' },
|
|
128
|
-
{ label: '06 asUser restr cmd nowin env=null ', api: 'asUser', token: 'restricted', env: 'null', flags: CREATE_NO_WINDOW, program: 'cmd' },
|
|
129
|
-
{ label: '07 asUser restr cmd nowin env=block ', api: 'asUser', token: 'restricted', env: 'block', flags: CREATE_NO_WINDOW, program: 'cmd' },
|
|
130
|
-
{ label: '08 asUser restr cmd DETACH env=block ', api: 'asUser', token: 'restricted', env: 'block', flags: DETACHED_PROCESS, program: 'cmd' },
|
|
131
|
-
{ label: '09 asUser restr whoami nowin env=block', api: 'asUser', token: 'restricted', env: 'block', flags: CREATE_NO_WINDOW, program: 'whoami' },
|
|
132
|
-
{ label: '10 withToken plain cmd nowin env=block ', api: 'withToken', token: 'plain', env: 'block', flags: CREATE_NO_WINDOW, program: 'cmd' },
|
|
133
|
-
{ label: '11 withToken restr cmd nowin env=block ', api: 'withToken', token: 'restricted', env: 'block', flags: CREATE_NO_WINDOW, program: 'cmd' },
|
|
134
|
-
{ label: '12 normal node nowin env=null ', api: 'normal', token: 'plain', env: 'null', flags: CREATE_NO_WINDOW, program: 'node' },
|
|
135
|
-
{ label: '13 asUser restr node nowin env=block', api: 'asUser', token: 'restricted', env: 'block', flags: CREATE_NO_WINDOW, program: 'node' },
|
|
136
|
-
{ label: '14 asUser restr node DETACH env=block ', api: 'asUser', token: 'restricted', env: 'block', flags: DETACHED_PROCESS, program: 'node' },
|
|
137
|
-
]
|
|
138
|
-
for (const v of matrix) await runVariant(v, hToken, hRestricted, execPath)
|
|
139
|
-
console.log(results.join('\n'))
|
|
140
|
-
CloseHandle(hRestricted); CloseHandle(hToken)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
main().catch((e) => { console.error('FATAL:', e instanceof Error ? e.message : e); process.exitCode = 1 })
|