flow-run-store-security.test.mjs
101 lines 3.2 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 10 days ago
1 /**
2 * Tier 7 — SECURITY: scope denial, no existence leak, no secrets in run payloads (P-FLOW).
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
10 import {
11 handleFlowRunGetRequest,
12 } from '../lib/flow/flow-execution.mjs';
13 import {
14 getFlowRun,
15 listFlowRuns,
16 OVERSEER_FIXTURE_RUN_REF,
17 } from '../lib/flow/flow-store.mjs';
18
19 const __dirname = path.dirname(fileURLToPath(import.meta.url));
20 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-flow-run-store-security');
21
22 const SECRET_MARKERS = ['token', 'oauth', 'refresh_token', 'password', 'secret'];
23
24 describe('Flow run store — security', () => {
25 beforeEach(() => {
26 fs.rmSync(tmpRoot, { recursive: true, force: true });
27 fs.mkdirSync(tmpRoot, { recursive: true });
28 });
29
30 afterEach(() => {
31 fs.rmSync(tmpRoot, { recursive: true, force: true });
32 });
33
34 it('personal scope never returns project overseer run', () => {
35 const dataDir = path.join(tmpRoot, 'scope');
36 fs.mkdirSync(dataDir);
37 const personal = new Set(['personal']);
38 const list = listFlowRuns(dataDir, 'default', {
39 visibleScopes: personal,
40 filterScopes: personal,
41 effectiveScope: 'personal',
42 });
43 assert.ok(list.runs.every((r) => r.scope === 'personal'));
44 assert.equal(getFlowRun(dataDir, 'default', OVERSEER_FIXTURE_RUN_REF, {
45 visibleScopes: personal,
46 }), null);
47 });
48
49 it('no existence leak: scope-invisible equals missing (404 unknown_run)', () => {
50 const dataDir = path.join(tmpRoot, 'leak');
51 fs.mkdirSync(dataDir);
52 const storeResult = handleFlowRunGetRequest({
53 dataDir,
54 vaultId: 'default',
55 cliScopes: ['personal'],
56 runId: OVERSEER_FIXTURE_RUN_REF,
57 });
58 assert.equal(storeResult.ok, false);
59 assert.equal(storeResult.status, 404);
60 assert.equal(storeResult.code, 'unknown_run');
61
62 const missing = handleFlowRunGetRequest({
63 dataDir,
64 vaultId: 'default',
65 cliScopes: ['personal'],
66 runId: 'run_does_not_exist',
67 });
68 assert.equal(missing.code, 'unknown_run');
69 });
70
71 it('serialized list/get payloads contain no secret markers', () => {
72 const dataDir = path.join(tmpRoot, 'secrets');
73 fs.mkdirSync(dataDir);
74 const scopes = new Set(['project', 'org']);
75 const list = listFlowRuns(dataDir, 'default', {
76 visibleScopes: scopes,
77 filterScopes: scopes,
78 effectiveScope: 'project',
79 });
80 const got = getFlowRun(dataDir, 'default', OVERSEER_FIXTURE_RUN_REF, {
81 visibleScopes: scopes,
82 });
83 const blob = JSON.stringify({ list, got }).toLowerCase();
84 for (const marker of SECRET_MARKERS) {
85 assert.ok(!blob.includes(marker), `found forbidden marker: ${marker}`);
86 }
87 });
88
89 it('invalid lookup keys fail closed at handler boundary', () => {
90 const dataDir = path.join(tmpRoot, 'bad');
91 fs.mkdirSync(dataDir);
92 const bad = handleFlowRunGetRequest({
93 dataDir,
94 vaultId: 'default',
95 cliScopes: ['project'],
96 runId: '../../../etc/passwd',
97 });
98 assert.equal(bad.ok, false);
99 assert.equal(bad.status, 400);
100 });
101 });
File History 1 commit
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago