source dump of claude code
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 296 lines 8.7 kB view raw
1import { readFileSync } from '../fileRead.js' 2import { getFsImplementation, safeResolvePath } from '../fsOperations.js' 3import { safeParseJSON } from '../json.js' 4import { logError } from '../log.js' 5import { 6 type EditableSettingSource, 7 getEnabledSettingSources, 8 type SettingSource, 9} from '../settings/constants.js' 10import { 11 getSettingsFilePathForSource, 12 getSettingsForSource, 13 updateSettingsForSource, 14} from '../settings/settings.js' 15import type { SettingsJson } from '../settings/types.js' 16import type { 17 PermissionBehavior, 18 PermissionRule, 19 PermissionRuleSource, 20 PermissionRuleValue, 21} from './PermissionRule.js' 22import { 23 permissionRuleValueFromString, 24 permissionRuleValueToString, 25} from './permissionRuleParser.js' 26 27/** 28 * Returns true if allowManagedPermissionRulesOnly is enabled in managed settings (policySettings). 29 * When enabled, only permission rules from managed settings are respected. 30 */ 31export function shouldAllowManagedPermissionRulesOnly(): boolean { 32 return ( 33 getSettingsForSource('policySettings')?.allowManagedPermissionRulesOnly === 34 true 35 ) 36} 37 38/** 39 * Returns true if "always allow" options should be shown in permission prompts. 40 * When allowManagedPermissionRulesOnly is enabled, these options are hidden. 41 */ 42export function shouldShowAlwaysAllowOptions(): boolean { 43 return !shouldAllowManagedPermissionRulesOnly() 44} 45 46const SUPPORTED_RULE_BEHAVIORS = [ 47 'allow', 48 'deny', 49 'ask', 50] as const satisfies PermissionBehavior[] 51 52/** 53 * Lenient version of getSettingsForSource that doesn't fail on ANY validation errors. 54 * Simply parses the JSON and returns it as-is without schema validation. 55 * 56 * Used when loading settings to append new rules (avoids losing existing rules 57 * due to validation failures in unrelated fields like hooks). 58 * 59 * FOR EDITING ONLY - do not use this for reading settings for execution. 60 */ 61function getSettingsForSourceLenient_FOR_EDITING_ONLY_NOT_FOR_READING( 62 source: SettingSource, 63): SettingsJson | null { 64 const filePath = getSettingsFilePathForSource(source) 65 if (!filePath) { 66 return null 67 } 68 69 try { 70 const { resolvedPath } = safeResolvePath(getFsImplementation(), filePath) 71 const content = readFileSync(resolvedPath) 72 if (content.trim() === '') { 73 return {} 74 } 75 76 const data = safeParseJSON(content, false) 77 // Return raw parsed JSON without validation to preserve all existing settings 78 // This is safe because we're only using this for reading/appending, not for execution 79 return data && typeof data === 'object' ? (data as SettingsJson) : null 80 } catch { 81 return null 82 } 83} 84 85/** 86 * Converts permissions JSON to an array of PermissionRule objects 87 * @param data The parsed permissions data 88 * @param source The source of these rules 89 * @returns Array of PermissionRule objects 90 */ 91function settingsJsonToRules( 92 data: SettingsJson | null, 93 source: PermissionRuleSource, 94): PermissionRule[] { 95 if (!data || !data.permissions) { 96 return [] 97 } 98 99 const { permissions } = data 100 const rules: PermissionRule[] = [] 101 for (const behavior of SUPPORTED_RULE_BEHAVIORS) { 102 const behaviorArray = permissions[behavior] 103 if (behaviorArray) { 104 for (const ruleString of behaviorArray) { 105 rules.push({ 106 source, 107 ruleBehavior: behavior, 108 ruleValue: permissionRuleValueFromString(ruleString), 109 }) 110 } 111 } 112 } 113 return rules 114} 115 116/** 117 * Loads all permission rules from all relevant sources (managed and project settings) 118 * @returns Array of all permission rules 119 */ 120export function loadAllPermissionRulesFromDisk(): PermissionRule[] { 121 // If allowManagedPermissionRulesOnly is set, only use managed permission rules 122 if (shouldAllowManagedPermissionRulesOnly()) { 123 return getPermissionRulesForSource('policySettings') 124 } 125 126 // Otherwise, load from all enabled sources (backwards compatible) 127 const rules: PermissionRule[] = [] 128 129 for (const source of getEnabledSettingSources()) { 130 rules.push(...getPermissionRulesForSource(source)) 131 } 132 return rules 133} 134 135/** 136 * Loads permission rules from a specific source 137 * @param source The source to load from 138 * @returns Array of permission rules from that source 139 */ 140export function getPermissionRulesForSource( 141 source: SettingSource, 142): PermissionRule[] { 143 const settingsData = getSettingsForSource(source) 144 return settingsJsonToRules(settingsData, source) 145} 146 147export type PermissionRuleFromEditableSettings = PermissionRule & { 148 source: EditableSettingSource 149} 150 151// Editable sources that can be modified (excludes policySettings and flagSettings) 152const EDITABLE_SOURCES: EditableSettingSource[] = [ 153 'userSettings', 154 'projectSettings', 155 'localSettings', 156] 157 158/** 159 * Deletes a rule from the project permissions file 160 * @param rule The rule to delete 161 * @returns Promise resolving to a boolean indicating success 162 */ 163export function deletePermissionRuleFromSettings( 164 rule: PermissionRuleFromEditableSettings, 165): boolean { 166 // Runtime check to ensure source is actually editable 167 if (!EDITABLE_SOURCES.includes(rule.source as EditableSettingSource)) { 168 return false 169 } 170 171 const ruleString = permissionRuleValueToString(rule.ruleValue) 172 const settingsData = getSettingsForSource(rule.source) 173 174 // If there's no settings data or permissions, nothing to do 175 if (!settingsData || !settingsData.permissions) { 176 return false 177 } 178 179 const behaviorArray = settingsData.permissions[rule.ruleBehavior] 180 if (!behaviorArray) { 181 return false 182 } 183 184 // Normalize raw settings entries via roundtrip parse→serialize so legacy 185 // names (e.g. "KillShell") match their canonical form ("TaskStop"). 186 const normalizeEntry = (raw: string): string => 187 permissionRuleValueToString(permissionRuleValueFromString(raw)) 188 189 if (!behaviorArray.some(raw => normalizeEntry(raw) === ruleString)) { 190 return false 191 } 192 193 try { 194 // Keep a copy of the original permissions data to preserve unrecognized keys 195 const updatedSettingsData = { 196 ...settingsData, 197 permissions: { 198 ...settingsData.permissions, 199 [rule.ruleBehavior]: behaviorArray.filter( 200 raw => normalizeEntry(raw) !== ruleString, 201 ), 202 }, 203 } 204 205 const { error } = updateSettingsForSource(rule.source, updatedSettingsData) 206 if (error) { 207 // Error already logged inside updateSettingsForSource 208 return false 209 } 210 211 return true 212 } catch (error) { 213 logError(error) 214 return false 215 } 216} 217 218function getEmptyPermissionSettingsJson(): SettingsJson { 219 return { 220 permissions: {}, 221 } 222} 223 224/** 225 * Adds rules to the project permissions file 226 * @param ruleValues The rule values to add 227 * @returns Promise resolving to a boolean indicating success 228 */ 229export function addPermissionRulesToSettings( 230 { 231 ruleValues, 232 ruleBehavior, 233 }: { 234 ruleValues: PermissionRuleValue[] 235 ruleBehavior: PermissionBehavior 236 }, 237 source: EditableSettingSource, 238): boolean { 239 // When allowManagedPermissionRulesOnly is enabled, don't persist new permission rules 240 if (shouldAllowManagedPermissionRulesOnly()) { 241 return false 242 } 243 244 if (ruleValues.length < 1) { 245 // No rules to add 246 return true 247 } 248 249 const ruleStrings = ruleValues.map(permissionRuleValueToString) 250 // First try the normal settings loader which validates the schema 251 // If validation fails, fall back to lenient loading to preserve existing rules 252 // even if some fields (like hooks) have validation errors 253 const settingsData = 254 getSettingsForSource(source) || 255 getSettingsForSourceLenient_FOR_EDITING_ONLY_NOT_FOR_READING(source) || 256 getEmptyPermissionSettingsJson() 257 258 try { 259 // Ensure permissions object exists 260 const existingPermissions = settingsData.permissions || {} 261 const existingRules = existingPermissions[ruleBehavior] || [] 262 263 // Filter out duplicates - normalize existing entries via roundtrip 264 // parse→serialize so legacy names match their canonical form. 265 const existingRulesSet = new Set( 266 existingRules.map(raw => 267 permissionRuleValueToString(permissionRuleValueFromString(raw)), 268 ), 269 ) 270 const newRules = ruleStrings.filter(rule => !existingRulesSet.has(rule)) 271 272 // If no new rules to add, return success 273 if (newRules.length === 0) { 274 return true 275 } 276 277 // Keep a copy of the original settings data to preserve unrecognized keys 278 const updatedSettingsData = { 279 ...settingsData, 280 permissions: { 281 ...existingPermissions, 282 [ruleBehavior]: [...existingRules, ...newRules], 283 }, 284 } 285 const result = updateSettingsForSource(source, updatedSettingsData) 286 287 if (result.error) { 288 throw result.error 289 } 290 291 return true 292 } catch (error) { 293 logError(error) 294 return false 295 } 296}