flow-store-versioned-step-keying-security.test.mjs
59 lines 2.5 KB
Raw
sha256:cfe8c8cf68336f6d46318bd40610c18d9ff7df231df2fb190af1f5a9c4f4f93b fix(flow-store): versioned step keying for multi-version fl… Human minor ⚠ breaking 7 hours ago
1 /**
2 * Tier 7 — SECURITY: flow_version never leaks on wire; no cross-version bleed (7A-10c).
3 */
4 import { describe, it, beforeEach, afterEach } from 'node:test';
5 import assert from 'node:assert/strict';
6 import fs from 'node:fs';
7 import path from 'node:path';
8 import { fileURLToPath } from 'node:url';
9 import { upsertFlowVersion, getFlow, listFlows } from '../lib/flow/flow-store.mjs';
10 import { makeFlowBundle, emptyStarterDir } from './fixtures/flow/authoring-helpers.mjs';
11
12 const __dirname = path.dirname(fileURLToPath(import.meta.url));
13 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-flow-versioned-step-keying-sec');
14 const visible = new Set(['personal', 'project', 'org']);
15
16 describe('Flow store — versioned step keying (security)', () => {
17 const dataDir = path.join(tmpRoot, 'data');
18 const vaultId = 'default';
19 let starterDir;
20
21 beforeEach(() => {
22 fs.rmSync(tmpRoot, { recursive: true, force: true });
23 fs.mkdirSync(dataDir, { recursive: true });
24 starterDir = emptyStarterDir(dataDir);
25 });
26
27 afterEach(() => {
28 fs.rmSync(tmpRoot, { recursive: true, force: true });
29 });
30
31 it('getFlow and listFlows responses omit store-internal flow_version', () => {
32 const v1 = makeFlowBundle({ flowId: 'flow_10c_sec', version: '1.0.0', steps: 1 });
33 const v2 = structuredClone(makeFlowBundle({ flowId: 'flow_10c_sec', version: '2.0.0', steps: 1 }));
34 upsertFlowVersion(dataDir, vaultId, v1.flow, v1.steps);
35 upsertFlowVersion(dataDir, vaultId, v2.flow, v2.steps);
36
37 const got = getFlow(dataDir, vaultId, 'flow_10c_sec', { filterScopes: visible, starterDir });
38 const listed = listFlows(dataDir, vaultId, {
39 filterScopes: visible, effectiveScope: 'personal', starterDir,
40 });
41 const serialized = JSON.stringify({ got, listed });
42 assert.ok(!serialized.includes('flow_version'));
43 });
44
45 it('pinned read cannot return a newer version step body', () => {
46 const v1 = makeFlowBundle({ flowId: 'flow_10c_bleed', version: '1.0.0', steps: 1 });
47 const v2 = structuredClone(makeFlowBundle({ flowId: 'flow_10c_bleed', version: '2.0.0', steps: 1 }));
48 v1.steps[0].instruction = 'SECRET_V1_ONLY';
49 v2.steps[0].instruction = 'SECRET_V2_ONLY';
50 upsertFlowVersion(dataDir, vaultId, v1.flow, v1.steps);
51 upsertFlowVersion(dataDir, vaultId, v2.flow, v2.steps);
52
53 const pinned = getFlow(dataDir, vaultId, 'flow_10c_bleed', {
54 filterScopes: visible, version: '1.0.0', starterDir,
55 });
56 assert.equal(pinned.steps[0].instruction, 'SECRET_V1_ONLY');
57 assert.ok(!JSON.stringify(pinned).includes('SECRET_V2_ONLY'));
58 });
59 });
File History 1 commit
sha256:cfe8c8cf68336f6d46318bd40610c18d9ff7df231df2fb190af1f5a9c4f4f93b fix(flow-store): versioned step keying for multi-version fl… Human minor 7 hours ago