Skip to content
Astera Technology

Why can a contained-looking path still resolve outside the import root?

Resolve the supplied symbolic-link tree as well as the path’s lexical segments.

Download complete case notes and answer keys ↓

Read the source and its declared rule

The original in-memory tree declares /import/docs as a symbolic link to /outside. Entry docs/a.txt has no .. and passes a lexical root check, but its resolved destination is outside. This fixture follows the supplied nonoverlapping link prefixes, detects revisited links and then checks containment.

Filled synthetic input
RecordInput JSON
Base input{"name":"docs/a.txt","links":[{"path":"/import/docs","target":"/outside"}]}
Download editable CSV ↓

Compare the actual paths

Corrected output holds resolved-path-escape. The check operates on a synthetic tree, not an assumption that a real filesystem has no changing links or race conditions.

Misleading and corrected outputs
PathOutput JSON
Misleading path{"accepted":true,"source":"docs/a.txt","destination":"/import/docs/a.txt"}
Corrected path{"accepted":false,"error":"resolved-path-escape","source":"docs/a.txt"}
Download editable CSV ↓
Local Node.js program: 22.23.3 or 24.14.1
import {DatabaseSync} from 'node:sqlite';
import {TextDecoder} from 'node:util';
import {inflateSync} from 'node:zlib';
import {posix} from 'node:path';
import {createHash} from 'node:crypto';
const rows=(db,sql)=>db.prepare(sql).all().map(r=>({...r}));
const failed=(action)=>{try{action();return null;}catch(e){return {code:e.errcode??e.code??e.name};}};

import assert from 'node:assert/strict';
const run=function run(i,corrected=true){
    let destination=posix.resolve('/import',i.name);const seen=new Set();
    if(corrected){for(;;){const link=i.links.find(l=>destination===l.path||destination.startsWith(l.path+'/'));if(!link)break;
        if(seen.has(link.path))return {accepted:false,error:'symlink-cycle',source:i.name};seen.add(link.path);
        destination=posix.resolve(link.target)+destination.slice(link.path.length);}
      if(!destination.startsWith('/import/'))return {accepted:false,error:'resolved-path-escape',source:i.name};}
    return {accepted:true,source:i.name,destination};
  };
const cases=[{"label":"misleading base","input":{"name":"docs/a.txt","links":[{"path":"/import/docs","target":"/outside"}]},"expected":{"accepted":true,"source":"docs/a.txt","destination":"/import/docs/a.txt"},"corrected":false},{"label":"corrected base","input":{"name":"docs/a.txt","links":[{"path":"/import/docs","target":"/outside"}]},"expected":{"accepted":false,"error":"resolved-path-escape","source":"docs/a.txt"},"corrected":true},{"label":"ordinary directory tree","input":{"name":"docs/a.txt","links":[]},"expected":{"accepted":true,"source":"docs/a.txt","destination":"/import/docs/a.txt"},"corrected":true},{"label":"synthetic symlink cycle","input":{"name":"docs/a.txt","links":[{"path":"/import/docs","target":"/import/more"},{"path":"/import/more","target":"/import/docs"}]},"expected":{"accepted":false,"error":"symlink-cycle","source":"docs/a.txt"},"corrected":true}];
for(const test of cases){
  const actual=run(test.input,test.corrected);
  assert.deepEqual(actual,test.expected,test.label);
  console.log(test.label,actual);
}
Download runnable example ↓

Check boundaries before the next load

An ordinary directory path accepts. A docs↔more link cycle is held before a destination is accepted.

Editable boundary records and expected outputs
CaseInput JSONExpected output JSON
Ordinary directory tree{"name":"docs/a.txt","links":[]}{"accepted":true,"source":"docs/a.txt","destination":"/import/docs/a.txt"}
Symbolic-link cycle{"name":"docs/a.txt","links":[{"path":"/import/docs","target":"/import/more"},{"path":"/import/more","target":"/import/docs"}]}{"accepted":false,"error":"symlink-cycle","source":"docs/a.txt"}
Download editable CSV ↓

Does the absence of .. establish containment here?

Show the worked answer

No. The supplied docs link escapes to /outside. Resolve the link tree and hold the entry.

Related resources