(path string, cfg map[string]any)
| 351 | } |
| 352 | |
| 353 | func writeJSONConfig(path string, cfg map[string]any) error { |
| 354 | if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { |
| 355 | return fmt.Errorf("mkdir %s: %w", filepath.Dir(path), err) |
| 356 | } |
| 357 | b, err := json.MarshalIndent(cfg, "", " ") |
| 358 | if err != nil { |
| 359 | return fmt.Errorf("marshal config: %w", err) |
| 360 | } |
| 361 | // 0o600 — config files often hold credentials for OTHER MCP |
| 362 | // servers (postgres URIs, OpenAI keys, etc.); tighten perms even |
| 363 | // though pad's own entry has no secrets. |
| 364 | // |
| 365 | // os.WriteFile only honors the mode when CREATING the file — an |
| 366 | // existing 0644 config keeps 0644 after the write. Codex caught |
| 367 | // this on TASK-948 round 1, so we Chmod after writing. Best-effort: |
| 368 | // chmod failures don't fail the install (the data write |
| 369 | // succeeded; the user can re-tighten manually). |
| 370 | if err := os.WriteFile(path, append(b, '\n'), 0o600); err != nil { |
| 371 | return fmt.Errorf("write %s: %w", path, err) |
| 372 | } |
| 373 | tightenPerms(path) |
| 374 | return nil |
| 375 | } |
| 376 | |
| 377 | // jsonEqual compares two parsed-JSON maps for value equality. Cheaper |
| 378 | // than reflect.DeepEqual and accepts the float/string nuances of |
no test coverage detected