MCPcopy Create free account
hub / github.com/Noumena-Network/code / checkEditableInternalPath

Function checkEditableInternalPath

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

Source from the content-addressed store, hash-verified

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

Callers 3

isPathAllowedFunction · 0.85
isPathAllowedFunction · 0.85

Calls 9

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

Tested by

no test coverage detected