Skip to content

Commit 5cab8ae

Browse files
author
catlog22
committed
fix: CSRF token accessibility and hook installation status
- Remove HttpOnly from XSRF-TOKEN cookie for JavaScript readability - Add hook installation status detection in system settings API - Update InjectionControlTab to show installed hooks status - Add brace expansion support in globToRegex utility
1 parent ffe3b42 commit 5cab8ae

11 files changed

Lines changed: 80 additions & 21 deletions

File tree

ccw/frontend/src/components/specs/InjectionControlTab.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import {
4646
Layers,
4747
Filter,
4848
} from 'lucide-react';
49-
import { useInstallRecommendedHooks } from '@/hooks/useSystemSettings';
49+
import { useInstallRecommendedHooks, useSystemSettings } from '@/hooks/useSystemSettings';
5050
import type { InjectionPreviewFile, InjectionPreviewResponse } from '@/lib/api';
5151
import { getInjectionPreview, COMMAND_PREVIEWS, type CommandPreviewConfig } from '@/lib/api';
5252

@@ -197,6 +197,9 @@ export function InjectionControlTab({ className }: InjectionControlTabProps) {
197197
// State for hooks installation
198198
const [installingHookIds, setInstallingHookIds] = useState<string[]>([]);
199199

200+
// Fetch system settings (for hooks installation status)
201+
const systemSettingsQuery = useSystemSettings();
202+
200203
// State for injection preview
201204
const [previewMode, setPreviewMode] = useState<'required' | 'all'>('required');
202205
const [categoryFilter, setCategoryFilter] = useState<SpecCategory | 'all'>('all');
@@ -349,10 +352,18 @@ export function InjectionControlTab({ className }: InjectionControlTabProps) {
349352

350353
const installedHookIds = useMemo(() => {
351354
const installed = new Set<string>();
355+
const hooks = systemSettingsQuery.data?.recommendedHooks;
356+
if (hooks) {
357+
hooks.forEach(hook => {
358+
if (hook.installed) {
359+
installed.add(hook.id);
360+
}
361+
});
362+
}
352363
return installed;
353-
}, []);
364+
}, [systemSettingsQuery.data?.recommendedHooks]);
354365

355-
const installedCount = 0;
366+
const installedCount = installedHookIds.size;
356367
const allHooksInstalled = installedCount === RECOMMENDED_HOOKS.length;
357368

358369
const handleInstallHook = useCallback(async (hookId: string) => {

ccw/frontend/src/lib/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7555,6 +7555,7 @@ export interface SystemSettings {
75557555
description: string;
75567556
scope: 'global' | 'project';
75577557
autoInstall: boolean;
7558+
installed: boolean;
75587559
}>;
75597560
}
75607561

ccw/frontend/src/locales/en/common.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{
2+
"cancel": "Cancel",
3+
"save": "Save",
24
"aria": {
35
"toggleNavigation": "Toggle navigation menu",
46
"refreshWorkspace": "Refresh workspace",

ccw/frontend/src/locales/en/specs.json

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -303,13 +303,16 @@
303303
"hookCommand": "Command",
304304
"hookScope": "Scope",
305305
"hookTimeout": "Timeout (ms)",
306-
"hookFailMode": "Fail Mode"
306+
"hookFailMode": "Fail Mode",
307+
"editTitle": "Edit Spec: {title}",
308+
"editDescription": "Modify spec metadata and settings."
307309
},
308310

309311
"form": {
310312
"readMode": "Read Mode",
311313
"priority": "Priority",
312314
"keywords": "Keywords",
315+
"keywordsPlaceholder": "Enter keywords, press Enter or comma to add",
313316
"title": "Title",
314317
"titlePlaceholder": "Enter spec title",
315318
"addKeyword": "Add Keyword",
@@ -322,11 +325,6 @@
322325
"titleRequired": "Title is required"
323326
},
324327

325-
"dialog": {
326-
"editTitle": "Edit Spec: {title}",
327-
"editDescription": "Modify spec metadata and settings."
328-
},
329-
330328
"hooks": {
331329
"installSuccess": "Hook installed successfully",
332330
"installError": "Failed to install hook",

ccw/frontend/src/locales/zh/common.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{
2+
"cancel": "取消",
3+
"save": "保存",
24
"aria": {
35
"toggleNavigation": "切换导航菜单",
46
"refreshWorkspace": "刷新工作区",

ccw/frontend/src/locales/zh/specs.json

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -310,13 +310,16 @@
310310
"hookCommand": "执行命令",
311311
"hookScope": "作用域",
312312
"hookTimeout": "超时时间(ms)",
313-
"hookFailMode": "失败模式"
313+
"hookFailMode": "失败模式",
314+
"editTitle": "编辑规范:{title}",
315+
"editDescription": "修改规范元数据和设置。"
314316
},
315317

316318
"form": {
317319
"readMode": "读取模式",
318320
"priority": "优先级",
319321
"keywords": "关键词",
322+
"keywordsPlaceholder": "输入关键词,按回车或逗号添加",
320323
"title": "标题",
321324
"titlePlaceholder": "输入规范标题",
322325
"addKeyword": "添加关键词",
@@ -329,11 +332,6 @@
329332
"titleRequired": "标题为必填项"
330333
},
331334

332-
"dialog": {
333-
"editTitle": "编辑规范:{title}",
334-
"editDescription": "修改规范元数据和设置。"
335-
},
336-
337335
"hooks": {
338336
"installSuccess": "钩子安装成功",
339337
"installError": "钩子安装失败",

ccw/src/core/auth/csrf-middleware.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ function setCsrfCookie(res: ServerResponse, token: string, maxAgeSeconds: number
5151
const attributes = [
5252
`XSRF-TOKEN=${encodeURIComponent(token)}`,
5353
'Path=/',
54-
'HttpOnly',
54+
// Note: XSRF-TOKEN must be readable by JavaScript for CSRF protection to work
55+
// The token is also sent via X-CSRF-Token header, so not having HttpOnly is safe
5556
'SameSite=Strict',
5657
`Max-Age=${maxAgeSeconds}`,
5758
];

ccw/src/core/routes/auth-routes.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ function setCsrfCookie(res: ServerResponse, token: string, maxAgeSeconds: number
7171
const attributes = [
7272
`XSRF-TOKEN=${encodeURIComponent(token)}`,
7373
'Path=/',
74-
'HttpOnly',
74+
// Note: XSRF-TOKEN must be readable by JavaScript for CSRF protection to work
75+
// The token is also sent via X-CSRF-Token header, so not having HttpOnly is safe
7576
'SameSite=Strict',
7677
`Max-Age=${maxAgeSeconds}`,
7778
];

ccw/src/core/routes/system-routes.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,46 @@ function readSettingsFile(filePath: string): Record<string, unknown> {
9090
}
9191
}
9292

93+
/**
94+
* Check if a recommended hook is installed in settings
95+
*/
96+
function isHookInstalled(
97+
settings: Record<string, unknown> & { hooks?: Record<string, unknown[]> },
98+
hook: typeof RECOMMENDED_HOOKS[number]
99+
): boolean {
100+
const hooks = settings.hooks;
101+
if (!hooks) return false;
102+
103+
const eventHooks = hooks[hook.event];
104+
if (!eventHooks || !Array.isArray(eventHooks)) return false;
105+
106+
// Check if hook exists in nested hooks array (by command)
107+
return eventHooks.some((entry) => {
108+
const entryHooks = (entry as Record<string, unknown>).hooks as Array<Record<string, unknown>> | undefined;
109+
if (!entryHooks || !Array.isArray(entryHooks)) return false;
110+
return entryHooks.some((h) => (h as Record<string, unknown>).command === hook.command);
111+
});
112+
}
113+
93114
/**
94115
* Get system settings from global settings file
95116
*/
96117
function getSystemSettings(): {
97118
injectionControl: typeof DEFAULT_INJECTION_CONTROL;
98119
personalSpecDefaults: typeof DEFAULT_PERSONAL_SPEC_DEFAULTS;
99120
devProgressInjection: typeof DEFAULT_DEV_PROGRESS_INJECTION;
100-
recommendedHooks: typeof RECOMMENDED_HOOKS;
121+
recommendedHooks: Array<typeof RECOMMENDED_HOOKS[number] & { installed: boolean }>;
101122
} {
102-
const settings = readSettingsFile(GLOBAL_SETTINGS_PATH) as Record<string, unknown>;
123+
const settings = readSettingsFile(GLOBAL_SETTINGS_PATH) as Record<string, unknown> & { hooks?: Record<string, unknown[]> };
103124
const system = (settings.system || {}) as Record<string, unknown>;
104125
const user = (settings.user || {}) as Record<string, unknown>;
105126

127+
// Check installation status for each recommended hook
128+
const recommendedHooksWithStatus = RECOMMENDED_HOOKS.map(hook => ({
129+
...hook,
130+
installed: isHookInstalled(settings, hook)
131+
}));
132+
106133
return {
107134
injectionControl: {
108135
...DEFAULT_INJECTION_CONTROL,
@@ -116,7 +143,7 @@ function getSystemSettings(): {
116143
...DEFAULT_DEV_PROGRESS_INJECTION,
117144
...((system.devProgressInjection || {}) as Record<string, unknown>)
118145
} as typeof DEFAULT_DEV_PROGRESS_INJECTION,
119-
recommendedHooks: RECOMMENDED_HOOKS
146+
recommendedHooks: recommendedHooksWithStatus
120147
};
121148
}
122149

ccw/src/core/server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,8 @@ function setCsrfCookie(res: http.ServerResponse, token: string, maxAgeSeconds: n
257257
const attributes = [
258258
`XSRF-TOKEN=${encodeURIComponent(token)}`,
259259
'Path=/',
260-
'HttpOnly',
260+
// Note: XSRF-TOKEN must be readable by JavaScript for CSRF protection to work
261+
// The token is also sent via X-CSRF-Token header, so not having HttpOnly is safe
261262
'SameSite=Strict',
262263
`Max-Age=${maxAgeSeconds}`,
263264
];

0 commit comments

Comments
 (0)