Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
8.7 kB · 296 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297import { readFileSync } from '../fileRead.js'import { getFsImplementation, safeResolvePath } from '../fsOperations.js'import { safeParseJSON } from '../json.js'import { logError } from '../log.js'import { type EditableSettingSource, getEnabledSettingSources, type SettingSource,} from '../settings/constants.js'import { getSettingsFilePathForSource, getSettingsForSource, updateSettingsForSource,} from '../settings/settings.js'import type { SettingsJson } from '../settings/types.js'import type { PermissionBehavior, PermissionRule, PermissionRuleSource, PermissionRuleValue,} from './PermissionRule.js'import { permissionRuleValueFromString, permissionRuleValueToString,} from './permissionRuleParser.js'
/** * Returns true if allowManagedPermissionRulesOnly is enabled in managed settings (policySettings). * When enabled, only permission rules from managed settings are respected. */export function shouldAllowManagedPermissionRulesOnly(): boolean { return ( getSettingsForSource('policySettings')?.allowManagedPermissionRulesOnly === true )}
/** * Returns true if "always allow" options should be shown in permission prompts. * When allowManagedPermissionRulesOnly is enabled, these options are hidden. */export function shouldShowAlwaysAllowOptions(): boolean { return !shouldAllowManagedPermissionRulesOnly()}
const SUPPORTED_RULE_BEHAVIORS = [ 'allow', 'deny', 'ask',] as const satisfies PermissionBehavior[]
/** * Lenient version of getSettingsForSource that doesn't fail on ANY validation errors. * Simply parses the JSON and returns it as-is without schema validation. * * Used when loading settings to append new rules (avoids losing existing rules * due to validation failures in unrelated fields like hooks). * * FOR EDITING ONLY - do not use this for reading settings for execution. */function getSettingsForSourceLenient_FOR_EDITING_ONLY_NOT_FOR_READING( source: SettingSource,): SettingsJson | null { const filePath = getSettingsFilePathForSource(source) if (!filePath) { return null }
try { const { resolvedPath } = safeResolvePath(getFsImplementation(), filePath) const content = readFileSync(resolvedPath) if (content.trim() === '') { return {} }
const data = safeParseJSON(content, false) // Return raw parsed JSON without validation to preserve all existing settings // This is safe because we're only using this for reading/appending, not for execution return data && typeof data === 'object' ? (data as SettingsJson) : null } catch { return null }}
/** * Converts permissions JSON to an array of PermissionRule objects * @param data The parsed permissions data * @param source The source of these rules * @returns Array of PermissionRule objects */function settingsJsonToRules( data: SettingsJson | null, source: PermissionRuleSource,): PermissionRule[] { if (!data || !data.permissions) { return [] }
const { permissions } = data const rules: PermissionRule[] = [] for (const behavior of SUPPORTED_RULE_BEHAVIORS) { const behaviorArray = permissions[behavior] if (behaviorArray) { for (const ruleString of behaviorArray) { rules.push({ source, ruleBehavior: behavior, ruleValue: permissionRuleValueFromString(ruleString), }) } } } return rules}
/** * Loads all permission rules from all relevant sources (managed and project settings) * @returns Array of all permission rules */export function loadAllPermissionRulesFromDisk(): PermissionRule[] { // If allowManagedPermissionRulesOnly is set, only use managed permission rules if (shouldAllowManagedPermissionRulesOnly()) { return getPermissionRulesForSource('policySettings') }
// Otherwise, load from all enabled sources (backwards compatible) const rules: PermissionRule[] = []
for (const source of getEnabledSettingSources()) { rules.push(...getPermissionRulesForSource(source)) } return rules}
/** * Loads permission rules from a specific source * @param source The source to load from * @returns Array of permission rules from that source */export function getPermissionRulesForSource( source: SettingSource,): PermissionRule[] { const settingsData = getSettingsForSource(source) return settingsJsonToRules(settingsData, source)}
export type PermissionRuleFromEditableSettings = PermissionRule & { source: EditableSettingSource}
// Editable sources that can be modified (excludes policySettings and flagSettings)const EDITABLE_SOURCES: EditableSettingSource[] = [ 'userSettings', 'projectSettings', 'localSettings',]
/** * Deletes a rule from the project permissions file * @param rule The rule to delete * @returns Promise resolving to a boolean indicating success */export function deletePermissionRuleFromSettings( rule: PermissionRuleFromEditableSettings,): boolean { // Runtime check to ensure source is actually editable if (!EDITABLE_SOURCES.includes(rule.source as EditableSettingSource)) { return false }
const ruleString = permissionRuleValueToString(rule.ruleValue) const settingsData = getSettingsForSource(rule.source)
// If there's no settings data or permissions, nothing to do if (!settingsData || !settingsData.permissions) { return false }
const behaviorArray = settingsData.permissions[rule.ruleBehavior] if (!behaviorArray) { return false }
// Normalize raw settings entries via roundtrip parse→serialize so legacy // names (e.g. "KillShell") match their canonical form ("TaskStop"). const normalizeEntry = (raw: string): string => permissionRuleValueToString(permissionRuleValueFromString(raw))
if (!behaviorArray.some(raw => normalizeEntry(raw) === ruleString)) { return false }
try { // Keep a copy of the original permissions data to preserve unrecognized keys const updatedSettingsData = { ...settingsData, permissions: { ...settingsData.permissions, [rule.ruleBehavior]: behaviorArray.filter( raw => normalizeEntry(raw) !== ruleString, ), }, }
const { error } = updateSettingsForSource(rule.source, updatedSettingsData) if (error) { // Error already logged inside updateSettingsForSource return false }
return true } catch (error) { logError(error) return false }}
function getEmptyPermissionSettingsJson(): SettingsJson { return { permissions: {}, }}
/** * Adds rules to the project permissions file * @param ruleValues The rule values to add * @returns Promise resolving to a boolean indicating success */export function addPermissionRulesToSettings( { ruleValues, ruleBehavior, }: { ruleValues: PermissionRuleValue[] ruleBehavior: PermissionBehavior }, source: EditableSettingSource,): boolean { // When allowManagedPermissionRulesOnly is enabled, don't persist new permission rules if (shouldAllowManagedPermissionRulesOnly()) { return false }
if (ruleValues.length < 1) { // No rules to add return true }
const ruleStrings = ruleValues.map(permissionRuleValueToString) // First try the normal settings loader which validates the schema // If validation fails, fall back to lenient loading to preserve existing rules // even if some fields (like hooks) have validation errors const settingsData = getSettingsForSource(source) || getSettingsForSourceLenient_FOR_EDITING_ONLY_NOT_FOR_READING(source) || getEmptyPermissionSettingsJson()
try { // Ensure permissions object exists const existingPermissions = settingsData.permissions || {} const existingRules = existingPermissions[ruleBehavior] || []
// Filter out duplicates - normalize existing entries via roundtrip // parse→serialize so legacy names match their canonical form. const existingRulesSet = new Set( existingRules.map(raw => permissionRuleValueToString(permissionRuleValueFromString(raw)), ), ) const newRules = ruleStrings.filter(rule => !existingRulesSet.has(rule))
// If no new rules to add, return success if (newRules.length === 0) { return true }
// Keep a copy of the original settings data to preserve unrecognized keys const updatedSettingsData = { ...settingsData, permissions: { ...existingPermissions, [ruleBehavior]: [...existingRules, ...newRules], }, } const result = updateSettingsForSource(source, updatedSettingsData)
if (result.error) { throw result.error }
return true } catch (error) { logError(error) return false }}