MCPcopy Create free account
hub / github.com/WJX20/claude-code / checkEditableInternalPath

Function checkEditableInternalPath

src/utils/permissions/filesystem.ts:1480–1606  ·  view source on GitHub ↗
(
  absolutePath: string,
  input: { [key: string]: unknown },
)

Source from the content-addressed store, hash-verified

1478 * Returns a PermissionResult - either 'allow' if matched, or 'passthrough' to continue checking.
1479 */
1480export function checkEditableInternalPath(
1481 absolutePath: string,
1482 input: { [key: string]: unknown },
1483): PermissionResult {
1484 // SECURITY: Normalize path to prevent traversal bypasses via .. segments
1485 // This is defense-in-depth; individual helper functions also normalize
1486 const normalizedPath = normalize(absolutePath)
1487
1488 // Plan files for current session
1489 if (isSessionPlanFile(normalizedPath)) {
1490 return {
1491 behavior: 'allow',
1492 updatedInput: input,
1493 decisionReason: {
1494 type: 'other',
1495 reason: 'Plan files for current session are allowed for writing',
1496 },
1497 }
1498 }
1499
1500 // Scratchpad directory for current session
1501 if (isScratchpadPath(normalizedPath)) {
1502 return {
1503 behavior: 'allow',
1504 updatedInput: input,
1505 decisionReason: {
1506 type: 'other',
1507 reason: 'Scratchpad files for current session are allowed for writing',
1508 },
1509 }
1510 }
1511
1512 // Template job's own directory. Env key hardcoded (vs importing JOB_ENV_KEY
1513 // from jobs/state) so tree-shaking eliminates the string from external
1514 // builds — spawn.test.ts asserts the string matches. Hijack guard: the env
1515 // var value must itself resolve under ~/.claude/jobs/. Symlink guard: every
1516 // resolved form of the target (lexical + symlink chain) must fall under some
1517 // resolved form of the job dir, so a symlink inside the job dir pointing at
1518 // e.g. ~/.ssh/authorized_keys does not get a free write. Resolving both
1519 // sides handles the macOS /tmp → /private/tmp case where the config dir
1520 // lives under a symlinked root.
1521 if (feature('TEMPLATES')) {
1522 const jobDir = process.env.CLAUDE_JOB_DIR
1523 if (jobDir) {
1524 const jobsRoot = join(getClaudeConfigHomeDir(), 'jobs')
1525 const jobDirForms = getPathsForPermissionCheck(jobDir).map(normalize)
1526 const jobsRootForms = getPathsForPermissionCheck(jobsRoot).map(normalize)
1527 // Hijack guard: every resolved form of the job dir must sit under
1528 // some resolved form of the jobs root. Resolving both sides handles
1529 // the case where ~/.claude is a symlink (e.g. to /data/claude-config).
1530 const isUnderJobsRoot = jobDirForms.every(jd =>
1531 jobsRootForms.some(jr => jd.startsWith(jr + sep)),
1532 )
1533 if (isUnderJobsRoot) {
1534 const targetForms = getPathsForPermissionCheck(absolutePath)
1535 const allInsideJobDir = targetForms.every(p => {
1536 const np = normalize(p)
1537 return jobDirForms.some(jd => np === jd || np.startsWith(jd + sep))

Callers 3

isPathAllowedFunction · 0.85
isPathAllowedFunction · 0.85

Calls 10

isSessionPlanFileFunction · 0.85
isScratchpadPathFunction · 0.85
featureFunction · 0.85
isAgentMemoryPathFunction · 0.85
hasAutoMemPathOverrideFunction · 0.85
isAutoMemPathFunction · 0.85
getOriginalCwdFunction · 0.85
normalizeFunction · 0.50

Tested by

no test coverage detected