Skip to content

Commit 960ebd9

Browse files
committed
fix: harden claude code settings and subagent editing
1 parent d3ec1d6 commit 960ebd9

31 files changed

Lines changed: 1025 additions & 724 deletions

app_mappers.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,7 @@ func mapClaudeCodeSubagentsSnapshot(result *wailsapp.ClaudeCodeSubagentsSnapshot
981981
Name: a.Name, Description: a.Description, Path: a.Path, Scope: a.Scope,
982982
FrontmatterValid: a.FrontmatterValid, FrontmatterError: a.FrontmatterError,
983983
ValidationErrors: a.ValidationErrors, KnownFields: a.KnownFields,
984-
UnknownFields: a.UnknownFields, BodyPreview: a.BodyPreview,
984+
UnknownFields: a.UnknownFields, Body: a.Body, BodyPreview: a.BodyPreview,
985985
IsPlugin: a.IsPlugin, IgnoredFields: a.IgnoredFields,
986986
})
987987
}
@@ -994,4 +994,3 @@ func mapClaudeCodeSubagentSaveResult(result *wailsapp.SaveClaudeCodeSubagentResu
994994
}
995995
return &SaveClaudeCodeSubagentResultDTO{Path: result.Path, Preview: result.Preview}
996996
}
997-

app_types.go

Lines changed: 98 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -373,15 +373,15 @@ type ClaudeCodeLocalApplyResult struct {
373373
}
374374

375375
type ClaudeCodeLocalApplyOptions struct {
376-
Model string `json:"model,omitempty"`
377-
DefaultHaikuModel string `json:"defaultHaikuModel,omitempty"`
378-
DefaultSonnetModel string `json:"defaultSonnetModel,omitempty"`
379-
DefaultOpusModel string `json:"defaultOpusModel,omitempty"`
380-
SmallFastModel string `json:"smallFastModel,omitempty"`
381-
MaxOutputTokens string `json:"maxOutputTokens,omitempty"`
382-
APITimeoutMS string `json:"apiTimeoutMs,omitempty"`
383-
DisableNonEssentialTraffic bool `json:"disableNonEssentialTraffic,omitempty"`
384-
ClaudeCodeAttributionHeader bool `json:"claudeCodeAttributionHeader,omitempty"`
376+
Model string `json:"model,omitempty"`
377+
DefaultHaikuModel string `json:"defaultHaikuModel,omitempty"`
378+
DefaultSonnetModel string `json:"defaultSonnetModel,omitempty"`
379+
DefaultOpusModel string `json:"defaultOpusModel,omitempty"`
380+
SmallFastModel string `json:"smallFastModel,omitempty"`
381+
MaxOutputTokens string `json:"maxOutputTokens,omitempty"`
382+
APITimeoutMS string `json:"apiTimeoutMs,omitempty"`
383+
DisableNonEssentialTraffic bool `json:"disableNonEssentialTraffic,omitempty"`
384+
ClaudeCodeAttributionHeader bool `json:"claudeCodeAttributionHeader,omitempty"`
385385
}
386386

387387
type UsageStatisticsResponse struct {
@@ -694,17 +694,17 @@ type SaveCodexSkillEnabledInput struct {
694694
}
695695

696696
type ClaudeCodeSettingsSnapshotDTO struct {
697-
ProjectPath string `json:"projectPath"`
698-
Layers []ClaudeCodeSettingsLayer `json:"layers"`
699-
Warnings []string `json:"warnings,omitempty"`
697+
ProjectPath string `json:"projectPath"`
698+
Layers []ClaudeCodeSettingsLayer `json:"layers"`
699+
Warnings []string `json:"warnings,omitempty"`
700700
}
701701

702702
type ClaudeCodeSettingsLayer struct {
703-
Scope string `json:"scope"`
704-
Path string `json:"path"`
705-
Exists bool `json:"exists"`
706-
ParseError string `json:"parseError,omitempty"`
707-
KnownFields *ClaudeCodeSettingsFieldsDTO `json:"knownFields,omitempty"`
703+
Scope string `json:"scope"`
704+
Path string `json:"path"`
705+
Exists bool `json:"exists"`
706+
ParseError string `json:"parseError,omitempty"`
707+
KnownFields *ClaudeCodeSettingsFieldsDTO `json:"knownFields,omitempty"`
708708
}
709709

710710
type ClaudeCodeSettingsFieldsDTO struct {
@@ -727,8 +727,8 @@ type ClaudeCodeSettingsChangeDTO struct {
727727
}
728728

729729
type PatchClaudeCodeSettingsResultDTO struct {
730-
ConfigPath string `json:"configPath"`
731-
Preview string `json:"preview"`
730+
ConfigPath string `json:"configPath"`
731+
Preview string `json:"preview"`
732732
Changes []ClaudeCodeSettingsChangeDTO `json:"changes"`
733733
}
734734

@@ -929,83 +929,84 @@ type SessionManagementMessageRecord struct {
929929
Summary string `json:"summary"`
930930
Content string `json:"content"`
931931
Truncated bool `json:"truncated,omitempty"`
932+
}
933+
934+
// CLAUDE.md Memory File types
935+
type ClaudeCodeMemoryFilesSnapshotDTO struct {
936+
ProjectPath string `json:"projectPath"`
937+
Files []ClaudeCodeMemoryFileRecordDTO `json:"files"`
938+
Warnings []string `json:"warnings,omitempty"`
939+
}
940+
941+
type ClaudeCodeMemoryFileRecordDTO struct {
942+
Scope string `json:"scope"`
943+
Path string `json:"path"`
944+
Exists bool `json:"exists"`
945+
GitIgnored bool `json:"gitIgnored,omitempty"`
946+
Imports []ClaudeCodeMemoryFileImportDTO `json:"imports,omitempty"`
947+
Content string `json:"content,omitempty"`
948+
ContentTruncated bool `json:"contentTruncated,omitempty"`
949+
Size int64 `json:"size"`
950+
}
951+
952+
type ClaudeCodeMemoryFileImportDTO struct {
953+
Raw string `json:"raw"`
954+
Resolved string `json:"resolved"`
955+
Exists bool `json:"exists"`
956+
Depth int `json:"depth"`
957+
}
958+
959+
type SaveClaudeCodeMemoryFileInputDTO struct {
960+
Path string `json:"path"`
961+
Content string `json:"content"`
962+
}
963+
964+
type SaveClaudeCodeMemoryFileResultDTO struct {
965+
Path string `json:"path"`
966+
Size int64 `json:"size"`
967+
Warning string `json:"warning,omitempty"`
968+
}
969+
970+
// Subagents types
971+
type ClaudeCodeSubagentsSnapshotDTO struct {
972+
UserPath string `json:"userPath"`
973+
ProjectPath string `json:"projectPath"`
974+
Agents []ClaudeCodeSubagentRecordDTO `json:"agents"`
975+
Warnings []string `json:"warnings,omitempty"`
976+
}
977+
978+
type ClaudeCodeSubagentRecordDTO struct {
979+
Name string `json:"name"`
980+
Description string `json:"description"`
981+
Path string `json:"path"`
982+
Scope string `json:"scope"`
983+
FrontmatterValid bool `json:"frontmatterValid"`
984+
FrontmatterError string `json:"frontmatterError,omitempty"`
985+
ValidationErrors []string `json:"validationErrors,omitempty"`
986+
KnownFields map[string]any `json:"knownFields,omitempty"`
987+
UnknownFields map[string]any `json:"unknownFields,omitempty"`
988+
Body string `json:"body,omitempty"`
989+
BodyPreview string `json:"bodyPreview,omitempty"`
990+
IsPlugin bool `json:"isPlugin,omitempty"`
991+
IgnoredFields []string `json:"ignoredFields,omitempty"`
992+
}
993+
994+
type SaveClaudeCodeSubagentInputDTO struct {
995+
Scope string `json:"scope"`
996+
Path string `json:"path"`
997+
Name string `json:"name"`
998+
Description string `json:"description"`
999+
KnownFields map[string]any `json:"knownFields,omitempty"`
1000+
UnknownFields map[string]any `json:"unknownFields,omitempty"`
1001+
Body string `json:"body"`
1002+
}
1003+
1004+
type SaveClaudeCodeSubagentResultDTO struct {
1005+
Path string `json:"path"`
1006+
Preview string `json:"preview"`
1007+
}
9321008

933-
// CLAUDE.md Memory File types
934-
type ClaudeCodeMemoryFilesSnapshotDTO struct {
935-
ProjectPath string `json:"projectPath"`
936-
Files []ClaudeCodeMemoryFileRecordDTO `json:"files"`
937-
Warnings []string `json:"warnings,omitempty"`
938-
}
939-
940-
type ClaudeCodeMemoryFileRecordDTO struct {
941-
Scope string `json:"scope"`
942-
Path string `json:"path"`
943-
Exists bool `json:"exists"`
944-
GitIgnored bool `json:"gitIgnored,omitempty"`
945-
Imports []ClaudeCodeMemoryFileImportDTO `json:"imports,omitempty"`
946-
Content string `json:"content,omitempty"`
947-
ContentTruncated bool `json:"contentTruncated,omitempty"`
948-
Size int64 `json:"size"`
949-
}
950-
951-
type ClaudeCodeMemoryFileImportDTO struct {
952-
Raw string `json:"raw"`
953-
Resolved string `json:"resolved"`
954-
Exists bool `json:"exists"`
955-
Depth int `json:"depth"`
956-
}
957-
958-
type SaveClaudeCodeMemoryFileInputDTO struct {
959-
Path string `json:"path"`
960-
Content string `json:"content"`
961-
}
962-
963-
type SaveClaudeCodeMemoryFileResultDTO struct {
964-
Path string `json:"path"`
965-
Size int64 `json:"size"`
966-
Warning string `json:"warning,omitempty"`
967-
}
968-
969-
// Subagents types
970-
type ClaudeCodeSubagentsSnapshotDTO struct {
971-
UserPath string `json:"userPath"`
972-
ProjectPath string `json:"projectPath"`
973-
Agents []ClaudeCodeSubagentRecordDTO `json:"agents"`
974-
Warnings []string `json:"warnings,omitempty"`
975-
}
976-
977-
type ClaudeCodeSubagentRecordDTO struct {
978-
Name string `json:"name"`
979-
Description string `json:"description"`
980-
Path string `json:"path"`
981-
Scope string `json:"scope"`
982-
FrontmatterValid bool `json:"frontmatterValid"`
983-
FrontmatterError string `json:"frontmatterError,omitempty"`
984-
ValidationErrors []string `json:"validationErrors,omitempty"`
985-
KnownFields map[string]any `json:"knownFields,omitempty"`
986-
UnknownFields map[string]any `json:"unknownFields,omitempty"`
987-
BodyPreview string `json:"bodyPreview,omitempty"`
988-
IsPlugin bool `json:"isPlugin,omitempty"`
989-
IgnoredFields []string `json:"ignoredFields,omitempty"`
990-
}
991-
992-
type SaveClaudeCodeSubagentInputDTO struct {
993-
Scope string `json:"scope"`
994-
Path string `json:"path"`
995-
Name string `json:"name"`
996-
Description string `json:"description"`
997-
KnownFields map[string]any `json:"knownFields,omitempty"`
998-
UnknownFields map[string]any `json:"unknownFields,omitempty"`
999-
Body string `json:"body"`
1000-
}
1001-
1002-
type SaveClaudeCodeSubagentResultDTO struct {
1003-
Path string `json:"path"`
1004-
Preview string `json:"preview"`
1005-
}
1006-
1007-
type DeleteClaudeCodeSubagentInputDTO struct {
1008-
Scope string `json:"scope"`
1009-
Path string `json:"path"`
1010-
}
1009+
type DeleteClaudeCodeSubagentInputDTO struct {
1010+
Scope string `json:"scope"`
1011+
Path string `json:"path"`
10111012
}

docs-linhay/memory/2026-05-22.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,9 @@
159159
- 已推送 `master` 与 tag `v1.0.20`;GitHub Release 地址:https://github.com/AxApp/GetTokens/releases/tag/v1.0.20。
160160
- Release workflow `26282156016` 成功完成,产物包含 Apple Silicon / Intel 的 DMG、tar.gz 与 `checksums.txt`,并更新分架构 Sparkle appcast。
161161
- 分发校验已通过:`shasum -a 256 -c checksums.txt`、两个 DMG 的 Gatekeeper `spctl`、两个 DMG 的 `xcrun stapler validate`、包内 app `codesign -dv --verbose=4`、主程序与 `cli-proxy-api` 架构、`CFBundleShortVersionString=1.0.20``CFBundleVersion=1.0.20`、分架构 `SUFeedURL` 与远端 appcast 指向均通过。
162+
163+
## Claude Code settings/subagents 审计修复
164+
- 根因:`PatchClaudeCodeSettings` 信任前端传入 path,可写入 scope 外 JSON;Subagents 后端只返回截断 `bodyPreview`,前端编辑会把截断内容当完整 body 保存并丢失 frontmatter;Subagents 新建按钮把空字符串 `editingPath` 误判为编辑态;Settings 通用 Edit/Cancel/Save 控件未接线。
165+
- 修复:settings patch 统一按 scope 解析默认路径并拒绝 scope 外 path;subagent snapshot 返回完整 `body`,保存时保留 known/unknown frontmatter;前端编辑使用完整 body,并修正新建按钮与 settings 编辑按钮回调。
166+
- 补充:修复 `app_types.go` 中 Claude Code Memory/Subagents DTO 被误嵌入 `SessionManagementMessageRecord` 的结构问题,并同步 Wails 绑定到 `*DTO` 类型;Claude Code Memory/Subagents/Settings 三个 story 已补齐设计系统 Overview 和 admitted manifest。
167+
- 验证:`go test ./...``npm --prefix frontend run typecheck``npm --prefix frontend run test:unit` 通过;新增 settings scope 越界拒绝、subagent 完整 body 暴露、frontmatter 保留和 settings 编辑回调回归测试。

frontend/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "gettokens-frontend",
33
"private": true,
4-
"version": "1.0.20",
4+
"version": "1.0.21",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

frontend/package.json.md5

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
8e2666598a55cc5bf1d44dd15af321a6
1+
f6c6e0669fd3f49877ad376c85a2186d

frontend/src/features/claude-code/claude-md/ClaudeMdFeature.test.mjs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ test('Generated Wails bindings expose CLAUDE.md DTOs', () => {
6464

6565
assert.match(appSource, /export function GetClaudeCodeMemoryFilesSnapshot\(\)/);
6666
assert.match(appSource, /export function SaveClaudeCodeMemoryFile\(arg1\)/);
67-
assert.match(appTypes, /GetClaudeCodeMemoryFilesSnapshot\(\):Promise<main\.ClaudeCodeMemoryFilesSnapshot>/);
68-
assert.match(appTypes, /SaveClaudeCodeMemoryFile\(arg1:main\.SaveClaudeCodeMemoryFileInput\):Promise<main\.SaveClaudeCodeMemoryFileResult>/);
69-
assert.match(modelSource, /export class ClaudeCodeMemoryFilesSnapshot/);
70-
assert.match(modelSource, /export class ClaudeCodeMemoryFileRecord/);
71-
assert.match(modelSource, /export class ClaudeCodeMemoryFileImport/);
72-
assert.match(modelSource, /export class SaveClaudeCodeMemoryFileInput/);
73-
assert.match(modelSource, /export class SaveClaudeCodeMemoryFileResult/);
67+
assert.match(appTypes, /GetClaudeCodeMemoryFilesSnapshot\(\):Promise<main\.ClaudeCodeMemoryFilesSnapshotDTO>/);
68+
assert.match(appTypes, /SaveClaudeCodeMemoryFile\(arg1:main\.SaveClaudeCodeMemoryFileInputDTO\):Promise<main\.SaveClaudeCodeMemoryFileResultDTO>/);
69+
assert.match(modelSource, /export class ClaudeCodeMemoryFilesSnapshotDTO/);
70+
assert.match(modelSource, /export class ClaudeCodeMemoryFileRecordDTO/);
71+
assert.match(modelSource, /export class ClaudeCodeMemoryFileImportDTO/);
72+
assert.match(modelSource, /export class SaveClaudeCodeMemoryFileInputDTO/);
73+
assert.match(modelSource, /export class SaveClaudeCodeMemoryFileResultDTO/);
7474
});
7575

7676
test('Local file not gitignored shows warning', () => {

frontend/src/features/claude-code/claude-md/ClaudeMdFeature.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import type { MemoryFilesPanelState } from '../components/ClaudeCodeMemoryFilesP
77
import { previewAllFilesSnapshot, previewEmptySnapshot } from './previewData';
88

99
export default function ClaudeMdFeature() {
10-
const [snapshot, setSnapshot] = useState<main.ClaudeCodeMemoryFilesSnapshot>(previewAllFilesSnapshot);
10+
const [snapshot, setSnapshot] = useState<main.ClaudeCodeMemoryFilesSnapshotDTO>(previewAllFilesSnapshot);
1111
const [loadError, setLoadError] = useState('');
1212
const [editingPath, setEditingPath] = useState('');
1313
const [editContent, setEditContent] = useState('');
1414
const [savePreview, setSavePreview] = useState('');
1515
const [saveError, setSaveError] = useState('');
1616
const [saving, setSaving] = useState(false);
1717

18-
const { files, warnings } = snapshot;
18+
const { files, warnings = [] } = snapshot;
1919

2020
const state = useMemo<MemoryFilesPanelState>(() => {
2121
const existing = files.filter((f) => f.exists);

0 commit comments

Comments
 (0)