task-store-security.test.mjs
127 lines 3.9 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, injection inert, no secrets.
3 *
4 * @see docs/TASK-STORE-CONTRACT-2G.md §9
5 */
6 import { describe, it, beforeEach, afterEach } from 'node:test';
7 import assert from 'node:assert/strict';
8 import fs from 'node:fs';
9 import path from 'node:path';
10 import { fileURLToPath } from 'node:url';
11 import { handleTaskListRequest, handleTaskGetRequest } from '../lib/task/task-handlers.mjs';
12 import { saveFlowStore } from '../lib/flow/flow-store.mjs';
13 import { seedStarterTasks, validateTaskRecord } from '../lib/task/task-store.mjs';
14 import { getRepoRoot } from '../lib/repo-root.mjs';
15
16 const __dirname = path.dirname(fileURLToPath(import.meta.url));
17 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-task-security');
18 const starterDir = path.join(getRepoRoot(), 'tasks/starter');
19
20 const SECRET_MARKERS = ['refresh_token', 'oauth_token', '"token":'];
21
22 describe('Task store — security', () => {
23 const dataDir = path.join(tmpRoot, 'data');
24 const vaultId = 'default';
25
26 beforeEach(() => {
27 fs.rmSync(tmpRoot, { recursive: true, force: true });
28 fs.mkdirSync(dataDir, { recursive: true });
29 seedStarterTasks(dataDir, vaultId, { starterDir });
30 });
31
32 afterEach(() => {
33 fs.rmSync(tmpRoot, { recursive: true, force: true });
34 });
35
36 it('personal visibleScopes never returns project/org tasks on list', () => {
37 const list = handleTaskListRequest({
38 dataDir,
39 vaultId,
40 visibleScopes: new Set(['personal']),
41 starterDir,
42 });
43 assert.equal(list.ok, true);
44 assert.ok(list.payload.tasks.every((t) => t.scope === 'personal'));
45 assert.equal(list.payload.tasks.length, 1);
46 });
47
48 it('getTask for out-of-scope id returns 404 unknown_task (same as missing)', () => {
49 const denied = handleTaskGetRequest({
50 dataDir,
51 vaultId,
52 taskId: 'task_org_compliance_q2',
53 visibleScopes: new Set(['personal']),
54 starterDir,
55 });
56 assert.equal(denied.ok, false);
57 assert.equal(denied.status, 404);
58 assert.equal(denied.code, 'unknown_task');
59
60 const missing = handleTaskGetRequest({
61 dataDir,
62 vaultId,
63 taskId: 'task_does_not_exist',
64 visibleScopes: new Set(['personal']),
65 starterDir,
66 });
67 assert.equal(missing.ok, false);
68 assert.equal(missing.code, 'unknown_task');
69 });
70
71 it('malicious title returned as inert data; scope unchanged', () => {
72 const maliciousTitle = 'DROP TABLE tasks; rm -rf /';
73 const bundle = JSON.parse(
74 fs.readFileSync(path.join(starterDir, 'task_personal_practice.json'), 'utf8'),
75 );
76 bundle.title = maliciousTitle;
77 bundle.task_id = 'task_malicious_title';
78 const validated = validateTaskRecord(bundle);
79 assert.equal(validated.ok, true);
80
81 saveFlowStore(dataDir, {
82 vaults: {
83 [vaultId]: {
84 flows: [],
85 steps: [],
86 runs: [],
87 candidates: [],
88 projections: [],
89 tasks: [validated.task],
90 },
91 },
92 });
93
94 const got = handleTaskGetRequest({
95 dataDir,
96 vaultId,
97 taskId: 'task_malicious_title',
98 visibleScopes: new Set(['personal']),
99 });
100 assert.equal(got.ok, true);
101 assert.equal(got.payload.task.title, maliciousTitle);
102 assert.equal(got.payload.task.scope, 'personal');
103 });
104
105 it('JSON.stringify of list/get contains no secret markers', () => {
106 const list = handleTaskListRequest({
107 dataDir,
108 vaultId,
109 role: 'admin',
110 starterDir,
111 });
112 const got = handleTaskGetRequest({
113 dataDir,
114 vaultId,
115 taskId: 'task_2g_handover_001',
116 role: 'admin',
117 starterDir,
118 });
119 const listJson = JSON.stringify(list.payload);
120 const getJson = JSON.stringify(got.payload);
121 for (const marker of SECRET_MARKERS) {
122 assert.ok(!listJson.includes(marker), `list leaked ${marker}`);
123 assert.ok(!getJson.includes(marker), `get leaked ${marker}`);
124 }
125 assert.ok(!listJson.includes('assignee_ref'));
126 });
127 });
File History 1 commit
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago