@lqc123qwe/car-runtime 1.8.0 → 1.10.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 +29 -1
- package/dist/SHA256SUMS +1 -0
- package/dist/authz/authz.d.ts +44 -0
- package/dist/authz/authz.js +65 -0
- package/dist/car-runtime-1.10.0.tgz +0 -0
- package/dist/car-runtime-1.10.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 +79 -0
- package/dist/loop/stop.js +151 -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 +40 -0
- package/dist/runtime-core/chatStep.js +104 -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 +553 -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 +84 -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 +21 -0
- package/dist/seam.js +20 -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/mcp/gateway.ts +10 -2
- package/src/mcp/httpClient.ts +23 -5
- package/src/runtime-core/trace.ts +5 -0
- package/src/seam.ts +21 -0
- 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/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
package/test/s6.spec.ts
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import { generateKeyPairSync, sign, createHash } from 'node:crypto'
|
|
4
|
-
import { enforceSignature, verifyMinisig, type SignatureBundle } from '../src/load/verifier.ts'
|
|
5
|
-
import { GoalDriver } from '../src/loop/goal.ts'
|
|
6
|
-
import { recoverInterrupted } from '../src/loop/recover.ts'
|
|
7
|
-
import { runTurn } from '../src/loop/stop.ts'
|
|
8
|
-
import { SessionLog } from '../src/session/log.ts'
|
|
9
|
-
|
|
10
|
-
// ==================== 验签器(ADR-003 双轨 / D-2 enforce 分级) ====================
|
|
11
|
-
|
|
12
|
-
function makeTrustRoot() {
|
|
13
|
-
const { publicKey, privateKey } = generateKeyPairSync('ed25519')
|
|
14
|
-
const pubB64 = publicKey.export({ type: 'spki', format: 'der' }).toString('base64')
|
|
15
|
-
const signHash = (hash: string) => sign(null, Buffer.from(hash), privateKey).toString('base64')
|
|
16
|
-
return { pubB64, signHash }
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const HASH = createHash('sha256').update('manifest-body').digest('hex')
|
|
20
|
-
|
|
21
|
-
test('S6: minisig 验证——正确签名放行,篡改硬拒绝(不可配置放行)', () => {
|
|
22
|
-
const { pubB64, signHash } = makeTrustRoot()
|
|
23
|
-
const sig: SignatureBundle = { minisig: signHash(HASH) }
|
|
24
|
-
const good = enforceSignature(HASH, sig, { trustRootPublicKey: pubB64, mode: 'warn' })
|
|
25
|
-
assert.equal(good.allowed, true)
|
|
26
|
-
assert.equal(good.warning, undefined)
|
|
27
|
-
// 篡改哈希后验签失败——即使 warn 模式也硬拒绝
|
|
28
|
-
const bad = enforceSignature(HASH + 'x', sig, { trustRootPublicKey: pubB64, mode: 'warn' })
|
|
29
|
-
assert.equal(bad.allowed, false)
|
|
30
|
-
assert.match(bad.error!, /verification FAILED.*不可配置放行/)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
test('S6 D-2: 签名缺失——warn 告警放行(指纹兜底),enforce 硬拒绝', () => {
|
|
34
|
-
const { pubB64 } = makeTrustRoot()
|
|
35
|
-
const warn = enforceSignature(HASH, undefined, { trustRootPublicKey: pubB64, mode: 'warn' })
|
|
36
|
-
assert.equal(warn.allowed, true)
|
|
37
|
-
assert.match(warn.warning!, /CAR-W-SIG.*enforce 切换见 D-2 终局条件/)
|
|
38
|
-
const enforce = enforceSignature(HASH, undefined, { trustRootPublicKey: pubB64, mode: 'enforce' })
|
|
39
|
-
assert.equal(enforce.allowed, false)
|
|
40
|
-
assert.match(enforce.error!, /signature missing/)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test('S6 ADR-003: Sigstore 不可达 → 回退静态轨(双轨回退语义)', () => {
|
|
44
|
-
const { pubB64, signHash } = makeTrustRoot()
|
|
45
|
-
const sig: SignatureBundle = { minisig: signHash(HASH), sigstoreBundle: '<bundled>' }
|
|
46
|
-
const r = enforceSignature(HASH, sig, {
|
|
47
|
-
trustRootPublicKey: pubB64, mode: 'enforce',
|
|
48
|
-
sigstoreAvailable: () => true, // 主轨探针可用但骨架期 bundle 校验不可达 → 回退静态轨
|
|
49
|
-
})
|
|
50
|
-
assert.equal(r.allowed, true) // 静态轨兜底成功
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test('S6: verifyMinisig 对非法 base64 不崩溃(fail-closed)', () => {
|
|
54
|
-
const { pubB64 } = makeTrustRoot()
|
|
55
|
-
const r = verifyMinisig(HASH, 'not-base64!!!', pubB64)
|
|
56
|
-
assert.equal(r.ok, false)
|
|
57
|
-
assert.ok(r.error)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
// ==================== F13 Goal 层 ====================
|
|
61
|
-
|
|
62
|
-
test('F13: Goal phase 持久化(goalUpdate 事件)+ 恢复层重放(activation 不恢复=disarmed)', () => {
|
|
63
|
-
const log = new SessionLog()
|
|
64
|
-
const goal = new GoalDriver(log)
|
|
65
|
-
goal.arm()
|
|
66
|
-
assert.equal(goal.activation, 'armed')
|
|
67
|
-
goal.setPhase('paused', 'waiting for external event')
|
|
68
|
-
goal.setPhase('complete', 'done')
|
|
69
|
-
const events = log.events as any[]
|
|
70
|
-
assert.equal(events.filter(e => e.kind === 'goalUpdate').length, 2)
|
|
71
|
-
const replay = GoalDriver.replay(log.events)
|
|
72
|
-
assert.equal(replay.phase, 'complete') // phase 从日志重放
|
|
73
|
-
assert.equal(replay.activation, 'disarmed') // activation 永不持久化(AC-4.5)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
test('F13: 新实例默认 disarmed(重启不会把历史任务叫醒,AC-4.5)', () => {
|
|
77
|
-
const goal = new GoalDriver(new SessionLog())
|
|
78
|
-
assert.equal(goal.activation, 'disarmed')
|
|
79
|
-
assert.equal(goal.phase, 'active')
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
test('F13: 非 completed 收口自动 disarm(D5 §9:撞限/错误都会 disarm)', () => {
|
|
83
|
-
const goal = new GoalDriver(new SessionLog())
|
|
84
|
-
goal.arm()
|
|
85
|
-
goal.autoDisarmOnTurnEnd('max-tokens')
|
|
86
|
-
assert.equal(goal.activation, 'disarmed')
|
|
87
|
-
goal.arm()
|
|
88
|
-
goal.autoDisarmOnTurnEnd('completed')
|
|
89
|
-
assert.equal(goal.activation, 'armed') // completed 不 disarm
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
// ==================== F13 aggregate 可配置 + blockOnDeny ====================
|
|
93
|
-
|
|
94
|
-
test('F13: aggregate.terminate=any——单个 terminate 即 aborted(对照默认 AND)', async () => {
|
|
95
|
-
const log = new SessionLog()
|
|
96
|
-
const tools = new Map([
|
|
97
|
-
['killer', { declaredSideEffect: 'readonly', terminate: true, run: async () => 'k' } as any],
|
|
98
|
-
['probe', { declaredSideEffect: 'readonly', run: async () => 'p' } as any],
|
|
99
|
-
])
|
|
100
|
-
const r = await runTurn({
|
|
101
|
-
log, turnId: 'T', tools,
|
|
102
|
-
preset: { aggregate: { terminate: 'any' } },
|
|
103
|
-
model: async (): Promise<any> => ({ stopReason: 'toolUse', toolCalls: [
|
|
104
|
-
{ id: 'c1', tool: 'probe', args: {} }, { id: 'c2', tool: 'killer', args: {} },
|
|
105
|
-
] }),
|
|
106
|
-
})
|
|
107
|
-
assert.equal(r.reason, 'aborted') // any:killer 投票即整批终止(对照 M1 默认 AND 需全部)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
test('F13: aggregate.concludesTurn=all——单工具收口不再立即 completed(对照默认 OR)', async () => {
|
|
111
|
-
const log = new SessionLog()
|
|
112
|
-
let n = 0
|
|
113
|
-
const tools = new Map([
|
|
114
|
-
['probe', { declaredSideEffect: 'readonly', run: async () => 'p' } as any],
|
|
115
|
-
['commit', { declaredSideEffect: 'write', concludesTurn: true, run: async () => 'c' } as any],
|
|
116
|
-
])
|
|
117
|
-
const r = await runTurn({
|
|
118
|
-
log, turnId: 'T', tools, preset: { mode: 'full', aggregate: { concludesTurn: 'all' } },
|
|
119
|
-
model: async (): Promise<any> => n++ === 0
|
|
120
|
-
? { stopReason: 'toolUse', toolCalls: [
|
|
121
|
-
{ id: 'c1', tool: 'probe', args: {} }, { id: 'c2', tool: 'commit', args: {} },
|
|
122
|
-
] }
|
|
123
|
-
: { stopReason: 'stop', text: 'final' },
|
|
124
|
-
})
|
|
125
|
-
// all 模式:批次 2 个工具仅 1 个 conclude 投票 → concludesAll 不满足 → 模型继续 → stop 收口
|
|
126
|
-
// (对照默认 OR:同批次会立即 completed——见 s2/s4 用例)
|
|
127
|
-
assert.equal(r.reason, 'completed')
|
|
128
|
-
assert.equal(r.steps, 2)
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
test('F13: blockOnDeny——授权拒绝即收口 blocked(产出点新增,枚举不增)', async () => {
|
|
132
|
-
const log = new SessionLog()
|
|
133
|
-
const tools = new Map([['danger', { declaredSideEffect: 'write', run: async () => 'x' } as any]])
|
|
134
|
-
const r = await runTurn({
|
|
135
|
-
log, turnId: 'T', tools,
|
|
136
|
-
preset: { mode: 'confirm', blockOnDeny: true, authorize: async () => false },
|
|
137
|
-
model: async (): Promise<any> => ({ stopReason: 'toolUse', toolCalls: [{ id: 'c1', tool: 'danger', args: {} }] }),
|
|
138
|
-
})
|
|
139
|
-
assert.equal(r.reason, 'blocked')
|
|
140
|
-
const end = log.events.find(e => e.kind === 'turnEnd') as any
|
|
141
|
-
assert.equal(end.meta.reason, 'blocked')
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
// ==================== 恢复层(interrupted) ====================
|
|
145
|
-
|
|
146
|
-
test('F13: recoverInterrupted——未配对 toolCall 补合成结果 + 未闭合 turn 补记 interrupted', () => {
|
|
147
|
-
const log = new SessionLog()
|
|
148
|
-
log.append('user', 'user', 'T1', 'go')
|
|
149
|
-
log.append('model', 'toolCall', 'T1', { id: 'c1', tool: 't', args: {} })
|
|
150
|
-
// 模拟进程消失:c1 无 toolResult、T1 无 turnEnd
|
|
151
|
-
const report = recoverInterrupted(log)
|
|
152
|
-
assert.equal(report.pairedCalls, 1)
|
|
153
|
-
assert.equal(report.turnsClosed, 1)
|
|
154
|
-
const synthetic = log.events.find(e => e.kind === 'toolResult' && (e.payload as any).id === 'c1') as any
|
|
155
|
-
assert.equal(synthetic.payload.recovered, true) // 副作用状态未知显式声明
|
|
156
|
-
const end = log.events.find(e => e.kind === 'turnEnd' && e.turnId === 'T1') as any
|
|
157
|
-
assert.equal(end.meta.reason, 'interrupted') // 与 aborted 不混用
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
test('F13: 已配对/已闭合日志幂等(重复恢复零补记)', () => {
|
|
161
|
-
const log = new SessionLog()
|
|
162
|
-
log.append('user', 'user', 'T1', 'go')
|
|
163
|
-
log.append('model', 'toolCall', 'T1', { id: 'c1', tool: 't', args: {} })
|
|
164
|
-
log.append('plugin', 'toolResult', 'T1', { id: 'c1', result: 'ok' })
|
|
165
|
-
log.append('runtime', 'turnEnd', 'T1', null, { reason: 'completed' })
|
|
166
|
-
const report = recoverInterrupted(log)
|
|
167
|
-
assert.equal(report.pairedCalls, 0)
|
|
168
|
-
assert.equal(report.turnsClosed, 0)
|
|
169
|
-
})
|
package/test/s7.spec.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import { scanSecrets, redactSecrets, shannonEntropy, PATTERN_LIBRARY } from '../src/security/secrets.ts'
|
|
4
|
-
import { buildSeatbeltProfile } from '../src/sandbox/sandbox.ts'
|
|
5
|
-
import { execFileSync } from 'node:child_process'
|
|
6
|
-
|
|
7
|
-
// ==================== F12 secrets 检测(T-6 + D-3) ====================
|
|
8
|
-
|
|
9
|
-
test('F12: API Key 前缀族命中(OpenAI/GitHub/AWS)', () => {
|
|
10
|
-
const t = 'const key = "sk-proj-abcdefghij0123456789abcd" // OPENAI_API_KEY'
|
|
11
|
-
const { hits } = scanSecrets(t)
|
|
12
|
-
assert.ok(hits.some(h => h.name === 'OpenAI'), 'OpenAI sk- 命中')
|
|
13
|
-
const aws = scanSecrets('aws_secret_access_key = "AKIAIOSFODNN7EXAMPLE"').hits
|
|
14
|
-
assert.ok(aws.some(h => h.name === 'AWS Access Key'), 'AWS AccessKey 命中')
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test('F12: L2 熵过滤——低熵假 token 不命中(误报控制)', () => {
|
|
18
|
-
// sk- 前缀但内容是重复字符(低熵 <3.5)→ 不应命中
|
|
19
|
-
const t = 'sk-aaaaaaaaaaaaaaaaaaaaaaaa // 测试占位符,非真实 key'
|
|
20
|
-
const { hits, candidates } = scanSecrets(t, { returnCandidates: true })
|
|
21
|
-
assert.equal(hits.length, 0, '低熵候选不进命中')
|
|
22
|
-
assert.ok(candidates.length >= 0) // 观察面存在
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
test('F12: L3 上下文打分——裸 sk- 串(无键名/赋值语境)分数不足则降级候选', () => {
|
|
26
|
-
const t = 'random text sk-Xy3kP9mQ2vB7nR4tW8zC for demo' // 有熵但无键名共现、非赋值语境
|
|
27
|
-
const { hits, candidates } = scanSecrets(t, { returnCandidates: true })
|
|
28
|
-
assert.ok(!hits.some(h => h.name === 'OpenAI') || hits.length >= 0) // 依打分而定,但机制须工作
|
|
29
|
-
assert.ok(hits.length + candidates.length >= 1, 'L1 前缀层必须先捕获候选')
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('F12: 私钥块与连接串结构化模式豁免熵检查', () => {
|
|
33
|
-
const pem = '-----BEGIN RSA PRIVATE KEY-----\nMIIB\n-----END RSA PRIVATE KEY-----'
|
|
34
|
-
assert.ok(scanSecrets(pem).hits.some(h => h.category === 'private-key'))
|
|
35
|
-
const cs = 'mongodb://admin:S3cretPw@cluster0.abc.mongodb.net/db'
|
|
36
|
-
assert.ok(scanSecrets(cs).hits.some(h => h.category === 'connection-string'))
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
test('F12: 连接串误报防护——无凭据连接串不命中', () => {
|
|
40
|
-
const t = 'mongodb://localhost:27017/mydb' // 无用户:密码@ 结构
|
|
41
|
-
const { hits } = scanSecrets(t)
|
|
42
|
-
assert.equal(hits.length, 0)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
test('F12: redact 全遮蔽(保留首尾 ≤4)+ 原文不可逆', () => {
|
|
46
|
-
const key = 'sk-proj-abcdefghij0123456789abcd'
|
|
47
|
-
const t = `const key = "${key}" // OPENAI_API_KEY`
|
|
48
|
-
const { text, redacted } = redactSecrets(t)
|
|
49
|
-
assert.equal(redacted, 1)
|
|
50
|
-
assert.ok(!text.includes(key), '原文 key 不可见于脱敏后文本')
|
|
51
|
-
assert.ok(text.includes('sk-p****abcd'), '遮蔽格式 = 首 4 + **** + 尾 4')
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('F12: 熵函数基准', () => {
|
|
55
|
-
assert.ok(shannonEntropy('aaaaaaaa') < 0.1)
|
|
56
|
-
assert.ok(shannonEntropy('Xy3kP9mQ2vB7') > 3.2)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
test('F12: 模式库 6 类齐备且可枚举(治理视图数据源)', () => {
|
|
60
|
-
const cats = new Set(PATTERN_LIBRARY.map(p => p.category))
|
|
61
|
-
for (const c of ['api-key', 'private-key', 'connection-string', 'cloud-credential', 'bearer-token']) {
|
|
62
|
-
assert.ok(cats.has(c as any), `类别 ${c} 存在`)
|
|
63
|
-
}
|
|
64
|
-
assert.ok(PATTERN_LIBRARY.length >= 20, '模式 ≥20 条(v1 目标 30 内精选)')
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test('F12: D-3 标定——正负 1:9 基准集粗标(FPR ≤5% 门禁可执行)', () => {
|
|
68
|
-
// 负样本 90 条:常见代码片段(不含真实凭据)
|
|
69
|
-
const negatives: string[] = []
|
|
70
|
-
for (let i = 0; i < 90; i++) {
|
|
71
|
-
negatives.push([
|
|
72
|
-
`const cfg = { port: ${3000 + i}, debug: true }`,
|
|
73
|
-
`await fetch('https://api.example.com/v1/items?page=${i}')`,
|
|
74
|
-
`// TODO: handle error case ${i}`,
|
|
75
|
-
`const xs = [${i}, ${i + 1}, ${i + 2}].map(x => x * 2)`,
|
|
76
|
-
][i % 4])
|
|
77
|
-
}
|
|
78
|
-
let fp = 0
|
|
79
|
-
for (const n of negatives) if (scanSecrets(n).hits.length > 0) fp++
|
|
80
|
-
const fpr = fp / negatives.length
|
|
81
|
-
assert.ok(fpr <= 0.05, `FPR=${(fpr * 100).toFixed(1)}% ≤5%(D-3)`)
|
|
82
|
-
// 正样本 10 条
|
|
83
|
-
const positives = [
|
|
84
|
-
'const k = "sk-proj-abcdefghij0123456789abcd" // openai key',
|
|
85
|
-
'const g = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"',
|
|
86
|
-
'xoxb-123456789-abcdefgh',
|
|
87
|
-
'AKIAIOSFODNN7EXAMPLE',
|
|
88
|
-
'-----BEGIN RSA PRIVATE KEY-----',
|
|
89
|
-
'postgres://u:pX9k2m@db.internal:5432/app',
|
|
90
|
-
'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.abc',
|
|
91
|
-
'api_key = "a1B2c3D4e5F6g7H8i9J0"',
|
|
92
|
-
'password: "Sup3rS3cret!"',
|
|
93
|
-
'client_secret = "cs_live_abcdefghijklmnop"',
|
|
94
|
-
]
|
|
95
|
-
let tp = 0
|
|
96
|
-
for (const p of positives) if (scanSecrets(p).hits.length > 0) tp++
|
|
97
|
-
const recall = tp / positives.length
|
|
98
|
-
assert.ok(recall >= 0.85, `正样本召回 ${(recall * 100).toFixed(0)}% ≥85%(正式 <1.5% 漏报为万级基准集目标)`)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
// ==================== Seatbelt profile(darwin 后端,纯生成器测试) ====================
|
|
102
|
-
|
|
103
|
-
test('Seatbelt: profile fail-closed 结构(deny default + 网络拒绝 + .git 保护)', () => {
|
|
104
|
-
const p = buildSeatbeltProfile('workspace-write', '/home/dev/proj')
|
|
105
|
-
assert.ok(p.includes('(deny default)'), 'fail-closed 顶层')
|
|
106
|
-
assert.ok(p.includes('(deny network*)'), 'deny-by-default 网络')
|
|
107
|
-
assert.ok(p.includes('(allow file-write* (subpath "/home/dev/proj"))'))
|
|
108
|
-
assert.ok(p.includes(`(deny file-write* (subpath "/home/dev/proj/.git"))`), '.git 受保护路径')
|
|
109
|
-
assert.ok(p.includes('(subpath "/usr")'), '系统只读面')
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
test('Seatbelt: read-only 不含 file-write*;danger-full-access 不生成 profile', () => {
|
|
113
|
-
const ro = buildSeatbeltProfile('read-only', '/w')
|
|
114
|
-
assert.ok(!ro.includes('file-write*'), 'read-only 无写授权')
|
|
115
|
-
assert.throws(() => buildSeatbeltProfile('danger-full-access', '/w'), /danger-full-access/)
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
test('Seatbelt: workspace 引号转义(注入防护)', () => {
|
|
119
|
-
const p = buildSeatbeltProfile('workspace-write', '/tmp/x"y')
|
|
120
|
-
assert.ok(!p.includes('"/tmp/x"y"'), '恶意引号被转义')
|
|
121
|
-
assert.ok(p.includes('\\"'))
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
// ==================== win32 spike 准备(S8 结论门输入) ====================
|
|
125
|
-
|
|
126
|
-
test('win32 spike: 脚本存在且 win32 外环境输出 SKIP(判据清单可达)', () => {
|
|
127
|
-
const out = execFileSync(process.execPath, ['--experimental-transform-types', 'scripts/win32-koffi-spike.ts'], {
|
|
128
|
-
encoding: 'utf-8', timeout: 30_000,
|
|
129
|
-
})
|
|
130
|
-
if (process.platform !== 'win32') {
|
|
131
|
-
assert.ok(out.includes('SKIP: spike requires win32'))
|
|
132
|
-
} else {
|
|
133
|
-
// M5-S26 起四判据自动断言整合,win32 上以 VERDICT 收口(四判据全 PASS 或逐条列明)
|
|
134
|
-
assert.ok(out.includes('VERDICT:'), '四判据报告以 VERDICT 收口')
|
|
135
|
-
}
|
|
136
|
-
})
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* S7b · E-5 万级 secrets 基准集常驻回归(M2 退出标准 #4;D-3 判据)
|
|
3
|
-
*
|
|
4
|
-
* 判据(M2 安全设计增补 D-3):漏报率 <1.5%、FPR ≤5%、precision ≥80%,1:9 正负比。
|
|
5
|
-
* 基准集确定性生成(mulberry32 固定种子),任何 PATTERN_LIBRARY / 三层检测改动
|
|
6
|
-
* 触碰判据即本 spec 红——D-3「CI 标定」承诺的载体。
|
|
7
|
-
*/
|
|
8
|
-
import { test } from 'node:test'
|
|
9
|
-
import assert from 'node:assert/strict'
|
|
10
|
-
import { runBaseline } from '../src/security/secrets-baseline.ts'
|
|
11
|
-
|
|
12
|
-
test('S7b-E5: 10k 基准集(1000 正 + 9000 负)满足 D-3 判据', () => {
|
|
13
|
-
const r = runBaseline(1000, 9000)
|
|
14
|
-
assert.equal(r.posTotal, 1000, '正样本数')
|
|
15
|
-
assert.equal(r.negTotal, 9000, '负样本数')
|
|
16
|
-
assert.ok(r.missRate < r.criteria.missRateLt, `漏报率 ${(r.missRate * 100).toFixed(2)}% 应 <1.5%`)
|
|
17
|
-
assert.ok(r.fpr <= r.criteria.fprLe, `FPR ${(r.fpr * 100).toFixed(2)}% 应 ≤5%`)
|
|
18
|
-
assert.ok(r.precision >= r.criteria.precisionGe, `precision ${(r.precision * 100).toFixed(2)}% 应 ≥80%`)
|
|
19
|
-
assert.equal(r.pass, true)
|
|
20
|
-
})
|
package/test/s9.spec.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import { Context } from '../src/kernel/context.ts'
|
|
4
|
-
import { declareEvent, EventBus } from '../src/kernel/events.ts'
|
|
5
|
-
import { dumpAssembly, dumpEventGraph } from '../src/governance/dump.ts'
|
|
6
|
-
import { exportForensicsBundle, verifyBundle, complianceStatement, FIELD_STANDARD_MAP, AUDIT_FIELDS } from '../src/session/export.ts'
|
|
7
|
-
import { SessionLog } from '../src/session/log.ts'
|
|
8
|
-
|
|
9
|
-
// ==================== F14 治理视图 ====================
|
|
10
|
-
|
|
11
|
-
test('F14: 配置树 dump——插件/依赖/服务/副作用计数完整', () => {
|
|
12
|
-
const ctx = new Context()
|
|
13
|
-
ctx.plugin({ name: 'db', apply: (c) => { c.provide('conn', { url: 'x' }); c.effect(() => () => {}, 'timer') } })
|
|
14
|
-
ctx.plugin({ name: 'api', inject: ['conn'], apply: () => {} })
|
|
15
|
-
const tree = dumpAssembly(ctx)
|
|
16
|
-
const db = tree.plugins.find(p => p.name === 'db')!
|
|
17
|
-
assert.equal(db.state, 'ACTIVE')
|
|
18
|
-
assert.deepEqual(db.provides, ['conn'])
|
|
19
|
-
assert.equal(db.effects, 1)
|
|
20
|
-
assert.deepEqual(db.effectLabels, ['timer'])
|
|
21
|
-
const api = tree.plugins.find(p => p.name === 'api')!
|
|
22
|
-
assert.deepEqual(api.inject, ['conn'])
|
|
23
|
-
assert.ok(tree.services.some(s => s.name === 'conn' && s.provider === 'db'))
|
|
24
|
-
assert.equal(tree.pending.length, 0)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test('F14: 产消图——目录 × handlerChain,priority 执行序', () => {
|
|
28
|
-
declareEvent('gov/e', 'serial')
|
|
29
|
-
const bus = new EventBus()
|
|
30
|
-
bus.on('gov/e', () => {}, { label: 'low', priority: 10 })
|
|
31
|
-
bus.on('gov/e', () => {}, { label: 'high', priority: -1 })
|
|
32
|
-
const { events, edges } = dumpEventGraph(bus, new Map([['gov/e', { mode: 'serial' }]]))
|
|
33
|
-
assert.equal(events[0].mode, 'serial')
|
|
34
|
-
assert.deepEqual(events[0].consumers.map(c => c.label), ['high', 'low']) // 实际执行序
|
|
35
|
-
assert.equal(edges.filter(e => e.direction === 'producer').length, 1)
|
|
36
|
-
assert.equal(edges.filter(e => e.direction === 'consumer').length, 2)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
// ==================== F15 合规导出增强 ====================
|
|
40
|
-
|
|
41
|
-
function sampleLog(): SessionLog {
|
|
42
|
-
const log = new SessionLog()
|
|
43
|
-
log.append('user', 'user', 'T1', { text: 'hi' })
|
|
44
|
-
log.append('model', 'toolCall', 'T1', { id: 'c1', tool: 't' })
|
|
45
|
-
log.append('model', 'toolResult', 'T1', { id: 'c1', result: 'ok' })
|
|
46
|
-
log.append('runtime', 'turnEnd', 'T1', null, { reason: 'completed' })
|
|
47
|
-
return log
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
test('F15: 取证包——逐文件 SHA-256 + 链锚点 + 审计元数据', () => {
|
|
51
|
-
const log = sampleLog()
|
|
52
|
-
const bundle = exportForensicsBundle(log, {
|
|
53
|
-
sessionId: 'S1', exportedBy: 'alice', exportedAt: '2026-09-04T14:00:00Z',
|
|
54
|
-
runtimeVersion: '0.1.0', sessionRange: { fromSeq: 0, toSeq: 3 },
|
|
55
|
-
})
|
|
56
|
-
assert.equal(bundle.manifest.format, 'car-forensics/1')
|
|
57
|
-
assert.equal(bundle.manifest.verifyChainAtExport, 'PASS')
|
|
58
|
-
assert.equal(bundle.manifest.eventCount, 4)
|
|
59
|
-
assert.ok(bundle.manifest.chainTail)
|
|
60
|
-
assert.equal(bundle.files.length, 2)
|
|
61
|
-
assert.ok(bundle.files.some(f => f.name === 'manifest.json'))
|
|
62
|
-
assert.equal(verifyBundle(bundle).ok, true)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
test('F15: 篡改文件 → verifyBundle 拒绝;断链 → 导出中止', () => {
|
|
66
|
-
const log = sampleLog()
|
|
67
|
-
const bundle = exportForensicsBundle(log, { sessionId: 'S', exportedBy: 'a', exportedAt: 'x', runtimeVersion: 'v', sessionRange: { fromSeq: 0, toSeq: 3 } })
|
|
68
|
-
bundle.files[0].content = bundle.files[0].content.replace('"hi"', '"HACKED"')
|
|
69
|
-
const v = verifyBundle(bundle)
|
|
70
|
-
assert.equal(v.ok, false)
|
|
71
|
-
assert.equal(v.brokenFile, bundle.files[0].name)
|
|
72
|
-
// 断链导出中止
|
|
73
|
-
const log2 = sampleLog()
|
|
74
|
-
;(log2.events as any)[1].payload = { tampered: true }
|
|
75
|
-
assert.throws(() => exportForensicsBundle(log2, { sessionId: 'S', exportedBy: 'a', exportedAt: 'x', runtimeVersion: 'v', sessionRange: { fromSeq: 0, toSeq: 3 } }), /CAR-E-EXPORT/)
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
test('T-7: 三标分层——字段映射与核心字段不可裁剪', () => {
|
|
79
|
-
assert.equal(AUDIT_FIELDS.length, 13)
|
|
80
|
-
for (const std of ['soc2', 'gdpr', 'mlps'] as const) {
|
|
81
|
-
const s = complianceStatement(std)
|
|
82
|
-
assert.ok(s.fields.length >= 6, `${std} 字段集非空`)
|
|
83
|
-
assert.equal(s.coreMissing.length, 0, `${std} 不可裁剪核心字段必须全含(9 核心字段三标并集设计保证)`)
|
|
84
|
-
assert.ok(s.statement.length > 40)
|
|
85
|
-
}
|
|
86
|
-
// 等保 b) 条四要素:日期时间/用户/事件类型/是否成功 均在 mlps 字段集
|
|
87
|
-
const mlps = complianceStatement('mlps').fields
|
|
88
|
-
for (const f of ['ts', 'actor', 'eventType', 'result/status']) assert.ok(mlps.includes(f), `等保 b) 要素 ${f}`)
|
|
89
|
-
})
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# J-17 escape-wsl2 六步编排器(M3部署设计增补 §2.1.1)——在 WSL 发行版内执行
|
|
3
|
-
#
|
|
4
|
-
# 用法: j17-orchestrate.sh <repo 的 WSL 路径> <landlock-run 二进制的 WSL 路径>
|
|
5
|
-
# 步骤映射:1 WSL 启用(CI 前置步骤)/ 2 发行版导入(CI 前置步骤)/ 3 环境自检 / 4 工作区迁移 / 5 实跑 / 6 证据回传(CI 后置步骤)
|
|
6
|
-
set -euo pipefail
|
|
7
|
-
|
|
8
|
-
REPO="${1:?usage: j17-orchestrate.sh <repo-wsl-path> <landlock-run-wsl-path>}"
|
|
9
|
-
LR_SRC="${2:?missing landlock-run path}"
|
|
10
|
-
|
|
11
|
-
# ── 步骤 4:工作区迁移到原生 ext4(强约束:/mnt 9p 语义不完整 → 假阳性/假阴性) ──
|
|
12
|
-
case "$REPO" in /mnt/*) : ;; *)
|
|
13
|
-
echo "note: repo path $REPO 不在 /mnt——若本机为原生 Linux(J-05 场景)请直接用 run-matrix.sh" >&2 ;;
|
|
14
|
-
esac
|
|
15
|
-
rm -rf ~/car-matrix
|
|
16
|
-
mkdir -p ~/car-matrix/artifacts
|
|
17
|
-
cp -r "$REPO/native" "$REPO/test" ~/car-matrix/
|
|
18
|
-
cp "$LR_SRC" ~/car-matrix/artifacts/landlock-run
|
|
19
|
-
chmod +x ~/car-matrix/artifacts/landlock-run
|
|
20
|
-
|
|
21
|
-
# ── G-06 第 4 断言点:WSL 发行版 node 版本(发行版默认无 node → informational,不阻塞逃逸门禁) ──
|
|
22
|
-
if command -v node >/dev/null 2>&1; then
|
|
23
|
-
echo "G06-WSL-NODE: $(node -v)"
|
|
24
|
-
else
|
|
25
|
-
echo "G06-WSL-NODE: not installed (informational — 逃逸矩阵不依赖 node)"
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
|
-
# ── 步骤 3+5:环境自检(失败 exit 2 = 环境失败,§2.1.3 维护者 rerun 语义) + 20 条实跑 ──
|
|
29
|
-
cd ~/car-matrix
|
|
30
|
-
bash test/sandbox-escape/run-matrix.sh ~/car-matrix/artifacts/landlock-run ~/car-matrix/audit
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# CAR E-1 逃逸回归矩阵(20 条 × 5 类)—— J-05 escape-linux / J-17 escape-wsl2 共用面
|
|
3
|
-
#
|
|
4
|
-
# 口径(M3部署设计增补 §2.1):
|
|
5
|
-
# - 20 条 POC-4 移植用例常驻化;逐条串行(M2 工作目录并发护栏);禁止 rerun 掩盖;
|
|
6
|
-
# - 审计 JSONL 无论成败必须产出(§2.1.1 步骤 6 常驻上传,非失败时才传);
|
|
7
|
-
# - 运行位置强约束:必须在原生 ext4 路径(WSL 内 $HOME),禁止 /mnt 挂载路径(9p 语义不完整→假阳性/假阴性);
|
|
8
|
-
# - 环境自检失败(内核/Landlock 不可用)= 环境失败(exit 2),不构成用例失败(§2.1.3);
|
|
9
|
-
# - 用例失败 exit 1;全绿 exit 0。
|
|
10
|
-
#
|
|
11
|
-
# 用法: run-matrix.sh <landlock-run 绝对路径> [审计 JSONL 输出目录](默认 ./audit)
|
|
12
|
-
set -u
|
|
13
|
-
|
|
14
|
-
LR="$1"
|
|
15
|
-
OUTDIR="${2:-./audit}"
|
|
16
|
-
mkdir -p "$OUTDIR"
|
|
17
|
-
JSONL="$OUTDIR/escape-audit.jsonl"
|
|
18
|
-
: > "$JSONL"
|
|
19
|
-
|
|
20
|
-
PASS=0; FAIL=0; TOTAL=0
|
|
21
|
-
TS() { date +%s%3N 2>/dev/null || python3 -c 'import time;print(int(time.time()*1000))' 2>/dev/null || echo 0; }
|
|
22
|
-
|
|
23
|
-
# ── 环境自检(§2.1.1 步骤 3):内核 ≥5.15 + Landlock 可探测;失败=环境失败 ──
|
|
24
|
-
KVER="$(uname -r)"
|
|
25
|
-
KMAJOR="$(echo "$KVER" | cut -d. -f1)"; KMINOR="$(echo "$KVER" | cut -d. -f2)"
|
|
26
|
-
if [ "$KMAJOR" -lt 5 ] || { [ "$KMAJOR" -eq 5 ] && [ "$KMINOR" -lt 15 ]; }; then
|
|
27
|
-
echo "ENV-FAIL: kernel $KVER < 5.15" >&2; exit 2
|
|
28
|
-
fi
|
|
29
|
-
if ! "$LR" --probe; then
|
|
30
|
-
echo "ENV-FAIL: landlock probe failed (LSM disabled or kernel <5.13)" >&2; exit 2
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
WORK="$(mktemp -d)"
|
|
34
|
-
trap 'rm -rf "$WORK"' EXIT
|
|
35
|
-
cd "$WORK"
|
|
36
|
-
# §2.1.1 步骤 4 强约束:用例必须在原生 ext4 工作区执行(/mnt 9p 语义不完整 → 假阳性/假阴性)
|
|
37
|
-
case "$(pwd)" in /mnt/*|/cygdrive/*)
|
|
38
|
-
echo "ENV-FAIL: workspace on drvfs mount ($(pwd)) — move to native ext4 path" >&2; exit 2 ;;
|
|
39
|
-
esac
|
|
40
|
-
mkdir -p ws ro-target && echo secret > ro-target/secret.txt && echo local > ws/local.txt
|
|
41
|
-
# shellcheck disable=SC2164
|
|
42
|
-
ABS_RO="$(cd ro-target && pwd)"
|
|
43
|
-
ABS_WS="$(cd ws && pwd)"
|
|
44
|
-
|
|
45
|
-
# run_case <编号> <类> <模式> <期望exit> <描述> -- <bash -c 脚本>
|
|
46
|
-
run_case() {
|
|
47
|
-
local id="$1" cls="$2" mode="$3" expect="$4" desc="$5"; shift 5
|
|
48
|
-
[ "$1" = "--" ] && shift
|
|
49
|
-
TOTAL=$((TOTAL+1))
|
|
50
|
-
local out rc=0
|
|
51
|
-
( cd "ws" && "$LR" "$mode" -- bash -c "$*" ) >"$WORK/.out" 2>&1 || rc=$?
|
|
52
|
-
local verdict=FAIL
|
|
53
|
-
[ "$rc" -eq "$expect" ] && { verdict=PASS; PASS=$((PASS+1)); } || { verdict=FAIL; FAIL=$((FAIL+1)); }
|
|
54
|
-
printf '{"case":"E-%s","class":"%s","mode":"%s","expect":%s,"actual":%s,"verdict":"%s","desc":"%s","ts":%s}\n' \
|
|
55
|
-
"$id" "$cls" "$mode" "$expect" "$rc" "$verdict" "$desc" "$(TS)" >> "$JSONL"
|
|
56
|
-
printf '[%s] E-%-2s (%s) expect=%s actual=%s %s\n' "$verdict" "$id" "$cls" "$expect" "$rc" "$desc"
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
# ════ 类 A:工作区外写拒绝(--rw 下,写位仅授予 cwd) ════
|
|
60
|
-
run_case 01 A --rw 1 "写 /tmp 拒绝" -- 'echo x > /tmp/car-escape-a && rm -f /tmp/car-escape-a'
|
|
61
|
-
run_case 02 A --rw 1 "写 \$HOME 拒绝" -- 'echo x > "$HOME/car-escape-b" && rm -f "$HOME/car-escape-b"'
|
|
62
|
-
run_case 03 A --rw 1 "写父目录拒绝" -- 'echo x > ../car-escape-c'
|
|
63
|
-
run_case 04 A --rw 1 "写绝对外部路径拒绝" -- "echo x > $ABS_RO/overrun"
|
|
64
|
-
|
|
65
|
-
# ════ 类 B:写位转换拒绝(删除/建目/改名/截断,跨出 cwd 一律 EACCES) ════
|
|
66
|
-
run_case 05 B --rw 1 "unlink 工作区外拒绝" -- "rm $ABS_RO/secret.txt"
|
|
67
|
-
run_case 06 B --rw 1 "mkdir 工作区外拒绝" -- 'mkdir /tmp/car-escape-d'
|
|
68
|
-
run_case 07 B --rw 1 "rename 跨出 cwd 拒绝" -- 'mv local.txt ../car-escape-e'
|
|
69
|
-
run_case 08 B --rw 1 "截断外部文件拒绝" -- ": > $ABS_RO/secret.txt"
|
|
70
|
-
|
|
71
|
-
# ════ 类 C:读/执行面放行验证(allowlist 正确放行运行必需面) ════
|
|
72
|
-
run_case 09 C --rw 0 "读 /etc 放行" -- 'cat /etc/hostname >/dev/null'
|
|
73
|
-
run_case 10 C --rw 0 "读工作区放行" -- 'cat local.txt >/dev/null'
|
|
74
|
-
run_case 11 C --rw 0 "exec /bin/true 放行" -- '/bin/true'
|
|
75
|
-
run_case 12 C --ro 0 "只读态读工作区放行" -- 'cat local.txt >/dev/null'
|
|
76
|
-
|
|
77
|
-
# ════ 类 D:提权与特殊文件逃逸面 ════
|
|
78
|
-
run_case 13 D --ro 1 "只读态 cwd 写拒绝" -- 'echo x > newfile'
|
|
79
|
-
run_case 14 D --rw 1 "symlink 逃逸拒绝" -- 'ln -s /tmp/car-escape-f link && echo x > link'
|
|
80
|
-
run_case 15 D --ro 1 "只读态建目拒绝(MAKE_DIR)" -- 'mkdir subdir'
|
|
81
|
-
run_case 16 D --rw 1 "hardlink 外联拒绝" -- 'ln local.txt ../car-escape-h'
|
|
82
|
-
|
|
83
|
-
# ════ 类 E:probe 与契约语义 ════
|
|
84
|
-
run_case 17 E --rw 0 "--rw cwd 写放行(正对照)" -- 'echo ok > w.txt && grep -q ok w.txt'
|
|
85
|
-
run_case 19 E --ro 0 "只读态 exec 放行" -- '/bin/true'
|
|
86
|
-
run_case 20 E --rw 0 "子进程继承沙箱(嵌套写仍拒)" -- 'bash -c "echo x > /tmp/car-escape-i"; test $? -ne 0'
|
|
87
|
-
|
|
88
|
-
# E-18 特殊:非法用法不走 bash -c 包装,直接断言二进制契约
|
|
89
|
-
TOTAL=$((TOTAL+1))
|
|
90
|
-
( cd ws && "$LR" --bogus -- true ) >/dev/null 2>&1; rc=$?
|
|
91
|
-
if [ "$rc" -eq 64 ]; then PASS=$((PASS+1)); v=PASS; else FAIL=$((FAIL+1)); v=FAIL; fi
|
|
92
|
-
printf '{"case":"E-18","class":"E","mode":"contract","expect":64,"actual":%s,"verdict":"%s","desc":"非法用法固定 exit 64","ts":%s}\n' "$rc" "$v" "$(TS)" >> "$JSONL"
|
|
93
|
-
printf '[%s] E-18 (E) expect=64 actual=%s 非法用法固定 exit 64\n' "$v" "$rc"
|
|
94
|
-
|
|
95
|
-
echo "----"
|
|
96
|
-
echo "matrix: $PASS/$TOTAL PASS, $FAIL FAIL (audit: $JSONL)"
|
|
97
|
-
[ "$FAIL" -eq 0 ] && [ "$PASS" -eq 20 ] || exit 1
|
|
98
|
-
exit 0
|
package/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"allowImportingTsExtensions": true,
|
|
7
|
-
"noEmit": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"strict": true,
|
|
10
|
-
"types": [
|
|
11
|
-
"node"
|
|
12
|
-
]
|
|
13
|
-
},
|
|
14
|
-
"include": [
|
|
15
|
-
"src/**/*",
|
|
16
|
-
"scripts/**/*"
|
|
17
|
-
]
|
|
18
|
-
}
|