[{"data":1,"prerenderedAt":1585},["ShallowReactive",2],{"db-company":3,"page-building-articles-vscode-ghost-files-remote-server-en":24},{"id":4,"extension":5,"meta":6,"stem":22,"__hash__":23},"db/db/company.json","json",{"name":7,"registrationNumber":8,"businessNumber":8,"dateOfIncorporation":9,"type":8,"publicationManager":7,"registeredOffice":10,"corporateOffice":14,"emails":19,"social":20,"url":21},"Nicolas Bages","","1986",{"phone":11,"address":12},"none",{"unit":8,"street":8,"postalCode":13,"city":8,"country":8},false,{"phone":15,"address":16},"+00/00000000",{"unit":8,"street":8,"postalCode":8,"city":8,"country":8,"gps":17},{"latitude":18,"longitude":18},0,{"sales":8},{"facebook":8,"linkedin":8,"x":8,"instagram":8,"teams":8,"gmaps":8},{"website":8},"db/company","0ivZqUmrLRJhhXT1FJE9r0Yfyu61ogQvu0tIXACVB04",{"id":25,"title":26,"body":27,"description":1573,"extension":1574,"meta":1575,"navigation":279,"path":1580,"seo":1581,"sitemap":1582,"stem":1583,"__hash__":1584},"pages_en/building/articles/vscode-ghost-files-remote-server.md","VSCode ghost files in remote server",{"type":28,"value":29,"toc":1566},"minimark",[30,39,120,123,205,215,237,240,245,252,259,304,311,324,329,332,625,635,653,669,676,681,709,719,722,735,742,746,753,962,971,987,1486,1489,1522,1525,1532,1536,1543,1559,1562],[31,32,33,34,38],"p",{},"Today I opened a remote SSH workspace on a server with VSCode. As an engineer, few things trigger your immediate response faster than running ",[35,36,37],"code",{},"git status"," and finding a random, cryptic file I did not create right in the root directory of a projet.",[40,41,45],"pre",{"className":42,"code":43,"language":44,"meta":8,"style":8},"language-bash shiki shiki-themes plastic","Untracked files:\n  (use \"git add \u003Cfile>...\" to include in what will be committed)\n    ''$'\\001''4'$'\\253\\006''@W@8'\n\n","bash",[35,46,47,60,103],{"__ignoreMap":8},[48,49,52,56],"span",{"class":50,"line":51},"line",1,[48,53,55],{"class":54},"sJix2","Untracked",[48,57,59],{"class":58},"subq3"," files:\n",[48,61,63,67,70,73,76,79,82,85,88,91,94,97,100],{"class":50,"line":62},2,[48,64,66],{"class":65},"sGSqi","  (",[48,68,69],{"class":54},"use",[48,71,72],{"class":65}," \"",[48,74,75],{"class":58},"git add \u003Cfile>...",[48,77,78],{"class":65},"\"",[48,80,81],{"class":58}," to",[48,83,84],{"class":58}," include",[48,86,87],{"class":58}," in",[48,89,90],{"class":58}," what",[48,92,93],{"class":58}," will",[48,95,96],{"class":58}," be",[48,98,99],{"class":58}," committed",[48,101,102],{"class":65},")\n",[48,104,106,109,112,115,117],{"class":50,"line":105},3,[48,107,108],{"class":54},"    ''",[48,110,111],{"class":54},"$",[48,113,114],{"class":54},"'\\001''4'",[48,116,111],{"class":54},[48,118,119],{"class":54},"'\\253\\006''@W@8'\n",[31,121,122],{},"What t** h*** is this?",[40,124,126],{"className":42,"code":125,"language":44,"meta":8,"style":8},"ls -li\ntotal 20\n130323 -rw-r--r--  1 nicolasbages nicolasbages    0 Jun 21 10:15 ''$'\\001''4'$'\\253\\006''@W@8'\n[...]\n",[35,127,128,137,145,199],{"__ignoreMap":8},[48,129,130,133],{"class":50,"line":51},[48,131,132],{"class":54},"ls",[48,134,136],{"class":135},"sjrmR"," -li\n",[48,138,139,142],{"class":50,"line":62},[48,140,141],{"class":54},"total",[48,143,144],{"class":135}," 20\n",[48,146,147,150,153,156,159,161,164,167,170,173,176,179,182,185,188,191,193,196],{"class":50,"line":105},[48,148,149],{"class":54},"130323",[48,151,152],{"class":135}," -rw-r--r--",[48,154,155],{"class":135},"  1",[48,157,158],{"class":58}," nicolasbages",[48,160,158],{"class":58},[48,162,163],{"class":135},"    0",[48,165,166],{"class":58}," Jun",[48,168,169],{"class":135}," 21",[48,171,172],{"class":58}," 10:15",[48,174,175],{"class":65}," ''$'",[48,177,178],{"class":58},"\\001",[48,180,181],{"class":65},"''",[48,183,184],{"class":58},"4",[48,186,187],{"class":65},"'$'",[48,189,190],{"class":58},"\\253\\006",[48,192,181],{"class":65},[48,194,195],{"class":58},"@W@8",[48,197,198],{"class":65},"'\n",[48,200,202],{"class":50,"line":201},4,[48,203,204],{"class":65},"[...]\n",[31,206,207,208,210,211,214],{},"The file with the inode ",[35,209,149],{}," has a very strange name and is ",[35,212,213],{},"0 octet",". Empty, ok, maybe just a file created by a script. I deleted it.",[40,216,218],{"className":42,"code":217,"language":44,"meta":8,"style":8},"find . -inum 130323 -delete\n",[35,219,220],{"__ignoreMap":8},[48,221,222,225,228,231,234],{"class":50,"line":51},[48,223,224],{"class":54},"find",[48,226,227],{"class":58}," .",[48,229,230],{"class":135}," -inum",[48,232,233],{"class":135}," 130323",[48,235,236],{"class":135}," -delete\n",[31,238,239],{},"But then after reopening this project, it was back! The cybersecurity landscape being so noisy lately following supply chain attacks on NPM packages and VSCode extensions, it couldn't be left unchecked.",[241,242,244],"h2",{"id":243},"phase-1-catching-the-ghost","Phase 1: Catching the ghost",[31,246,247,248,251],{},"The filesystems ",[35,249,250],{},"ext4"," does not store the Creator PID inside a file's metadata, we cannot read who made it.",[31,253,254,255,258],{},"To catch it, we can use the Linux Audit Framework ",[35,256,257],{},"auditd"," on the remote host. By setting a filesystem watch rule on the workspace directory.",[40,260,262],{"className":42,"code":261,"language":44,"meta":8,"style":8},"apt install auditd\n\nauditctl -w /home/nicolasbages/playbook/ -p wa -k file_creation_tracker\n",[35,263,264,275,281],{"__ignoreMap":8},[48,265,266,269,272],{"class":50,"line":51},[48,267,268],{"class":54},"apt",[48,270,271],{"class":58}," install",[48,273,274],{"class":58}," auditd\n",[48,276,277],{"class":50,"line":62},[48,278,280],{"emptyLinePlaceholder":279},true,"\n",[48,282,283,286,289,292,295,298,301],{"class":50,"line":105},[48,284,285],{"class":54},"auditctl",[48,287,288],{"class":135}," -w",[48,290,291],{"class":58}," /home/nicolasbages/playbook/",[48,293,294],{"class":135}," -p",[48,296,297],{"class":58}," wa",[48,299,300],{"class":135}," -k",[48,302,303],{"class":58}," file_creation_tracker\n",[31,305,306,307,310],{},"After triggering the file creation by reloading VS Code, we query the audit logs using ",[35,308,309],{},"ausearch",":",[40,312,314],{"className":42,"code":313,"language":44,"meta":8,"style":8},"ausearch -k file_creation_tracker\n",[35,315,316],{"__ignoreMap":8},[48,317,318,320,322],{"class":50,"line":51},[48,319,309],{"class":54},[48,321,300],{"class":135},[48,323,303],{"class":58},[325,326,328],"h3",{"id":327},"the-footprints-left-by-the-process","The footprints left by the process",[31,330,331],{},"The kernel returns the following block:",[40,333,335],{"className":42,"code":334,"language":44,"meta":8,"style":8},"time->Sun Jun 21 10:30:42 2026\ntype=PROCTITLE msg=audit(1782030642.033:23): proctitle=2F62696E2F7368002F686F6D652F6E69636F6C617362616765732F2E7673636F64652D7365727665722F657874656E73696F6E732F6769746C61622E6769746C61622D776F726B666C6F772D362E38332E322F6173736574732F6C616E67756167652D7365727665722F7267002D2D76657273696F6E\ntype=PATH msg=audit(1782030642.033:23): item=1 name=0134AB0640574038 inode=130323 dev=08:02 mode=0100644 ouid=1000 ogid=1000 rdev=00:00 nametype=CREATE\ntype=CWD msg=audit(1782030642.033:23): cwd=\"/home/nicolasbages/playbook\"\ntype=SYSCALL msg=audit(1782030642.033:23): arch=c00000b7 syscall=56 success=yes exit=3 ppid=415372 pid=415409 comm=\"sh\" exe=\"/usr/bin/dash\" key=\"file_creation_tracker\"\n\n",[35,336,337,349,388,481,517],{"__ignoreMap":8},[48,338,339,342,346],{"class":50,"line":51},[48,340,341],{"class":65},"time-",[48,343,345],{"class":344},"sVyAn",">",[48,347,348],{"class":65},"Sun Jun 21 10:30:42 2026\n",[48,350,351,355,358,361,364,366,369,372,375,378,380,383,385],{"class":50,"line":62},[48,352,354],{"class":353},"sVs6v","type",[48,356,357],{"class":344},"=",[48,359,360],{"class":58},"PROCTITLE",[48,362,363],{"class":353}," msg",[48,365,357],{"class":344},[48,367,368],{"class":58},"audit",[48,370,371],{"class":65},"(",[48,373,374],{"class":54},"1782030642.033:23",[48,376,377],{"class":65},")",[48,379,310],{"class":58},[48,381,382],{"class":353}," proctitle",[48,384,357],{"class":344},[48,386,387],{"class":58},"2F62696E2F7368002F686F6D652F6E69636F6C617362616765732F2E7673636F64652D7365727665722F657874656E73696F6E732F6769746C61622E6769746C61622D776F726B666C6F772D362E38332E322F6173736574732F6C616E67756167652D7365727665722F7267002D2D76657273696F6E\n",[48,389,390,392,394,397,399,401,403,405,407,409,411,414,416,419,422,424,427,430,432,434,437,439,442,445,447,450,453,455,458,461,463,465,468,470,473,476,478],{"class":50,"line":105},[48,391,354],{"class":353},[48,393,357],{"class":344},[48,395,396],{"class":58},"PATH",[48,398,363],{"class":353},[48,400,357],{"class":344},[48,402,368],{"class":58},[48,404,371],{"class":65},[48,406,374],{"class":54},[48,408,377],{"class":65},[48,410,310],{"class":58},[48,412,413],{"class":353}," item",[48,415,357],{"class":344},[48,417,418],{"class":58},"1",[48,420,421],{"class":353}," name",[48,423,357],{"class":344},[48,425,426],{"class":58},"0134AB0640574038",[48,428,429],{"class":353}," inode",[48,431,357],{"class":344},[48,433,149],{"class":58},[48,435,436],{"class":353}," dev",[48,438,357],{"class":344},[48,440,441],{"class":58},"08:02",[48,443,444],{"class":353}," mode",[48,446,357],{"class":344},[48,448,449],{"class":58},"0100644",[48,451,452],{"class":353}," ouid",[48,454,357],{"class":344},[48,456,457],{"class":58},"1000",[48,459,460],{"class":353}," ogid",[48,462,357],{"class":344},[48,464,457],{"class":58},[48,466,467],{"class":353}," rdev",[48,469,357],{"class":344},[48,471,472],{"class":58},"00:00",[48,474,475],{"class":353}," nametype",[48,477,357],{"class":344},[48,479,480],{"class":58},"CREATE\n",[48,482,483,485,487,490,492,494,496,498,500,502,504,507,509,511,514],{"class":50,"line":201},[48,484,354],{"class":353},[48,486,357],{"class":344},[48,488,489],{"class":58},"CWD",[48,491,363],{"class":353},[48,493,357],{"class":344},[48,495,368],{"class":58},[48,497,371],{"class":65},[48,499,374],{"class":54},[48,501,377],{"class":65},[48,503,310],{"class":58},[48,505,506],{"class":353}," cwd",[48,508,357],{"class":344},[48,510,78],{"class":65},[48,512,513],{"class":58},"/home/nicolasbages/playbook",[48,515,516],{"class":65},"\"\n",[48,518,520,522,524,527,529,531,533,535,537,539,541,544,546,549,552,554,557,560,562,565,568,570,573,576,578,581,584,586,589,592,594,596,599,601,604,606,608,611,613,616,618,620,623],{"class":50,"line":519},5,[48,521,354],{"class":353},[48,523,357],{"class":344},[48,525,526],{"class":58},"SYSCALL",[48,528,363],{"class":353},[48,530,357],{"class":344},[48,532,368],{"class":58},[48,534,371],{"class":65},[48,536,374],{"class":54},[48,538,377],{"class":65},[48,540,310],{"class":58},[48,542,543],{"class":353}," arch",[48,545,357],{"class":344},[48,547,548],{"class":58},"c00000b7",[48,550,551],{"class":353}," syscall",[48,553,357],{"class":344},[48,555,556],{"class":58},"56",[48,558,559],{"class":353}," success",[48,561,357],{"class":344},[48,563,564],{"class":58},"yes",[48,566,567],{"class":353}," exit",[48,569,357],{"class":344},[48,571,572],{"class":58},"3",[48,574,575],{"class":353}," ppid",[48,577,357],{"class":344},[48,579,580],{"class":58},"415372",[48,582,583],{"class":353}," pid",[48,585,357],{"class":344},[48,587,588],{"class":58},"415409",[48,590,591],{"class":353}," comm",[48,593,357],{"class":344},[48,595,78],{"class":65},[48,597,598],{"class":58},"sh",[48,600,78],{"class":65},[48,602,603],{"class":353}," exe",[48,605,357],{"class":344},[48,607,78],{"class":65},[48,609,610],{"class":58},"/usr/bin/dash",[48,612,78],{"class":65},[48,614,615],{"class":353}," key",[48,617,357],{"class":344},[48,619,78],{"class":65},[48,621,622],{"class":58},"file_creation_tracker",[48,624,516],{"class":65},[31,626,627,628,630,631,634],{},"At first glance, we see ",[35,629,610],{}," but we need to check for arguments because ",[35,632,633],{},"dash"," alone, while being the \"culprit\", doesn't help much.",[31,636,637,638,641,642,310],{},"The ",[35,639,640],{},"proctitle"," field contains the raw hex of the command. Let's ",[643,644,648,649,652],"a",{"href":645,"rel":646},"https://codebeautify.org/hex-string-converter",[647],"nofollow","convert that string (",[35,650,651],{},"2F62696E2F736800...",") back into human-readable ASCII text",[40,654,656],{"className":42,"code":655,"language":44,"meta":8,"style":8},"/bin/sh /home/nicolasbages/.vscode-server/extensions/gitlab.gitlab-workflow-6.83.2/assets/language-server/rg --version\n",[35,657,658],{"__ignoreMap":8},[48,659,660,663,666],{"class":50,"line":51},[48,661,662],{"class":54},"/bin/sh",[48,664,665],{"class":58}," /home/nicolasbages/.vscode-server/extensions/gitlab.gitlab-workflow-6.83.2/assets/language-server/rg",[48,667,668],{"class":135}," --version\n",[31,670,671,672,675],{},"Alright, it executes ",[35,673,674],{},"rg",". RipGrep is a tool bundled in IDEs and extensions to parse code syntax. We will see later that it is buddled with the extension.",[31,677,637,678,680],{},[35,679,526],{}," line tells us:",[682,683,684,695],"ul",{},[685,686,687,690,691,694],"li",{},[35,688,689],{},"syscall=56",": This maps to the ",[35,692,693],{},"openat"," system call.",[685,696,697,700,701,704,705,708],{},[35,698,699],{},"a2=241",": Translated from hex ",[35,702,703],{},"0x241"," into POSIX file system it means ",[35,706,707],{},"O_CREAT | O_WRONLY | O_TRUNC",".",[31,710,711,712,715,716,708],{},"So, the process requested the OS to create a new ",[35,713,714],{},"WO"," file inside its current working directory ",[35,717,718],{},"cwd=\"/home/nicolasbages/playbook\"",[31,720,721],{},"At that moment in time I started to think it looked more like a bug than an attack.",[682,723,724,727,730],{},[685,725,726],{},"0 byte footprint.",[685,728,729],{},"file visible in the root directory of the project.",[685,731,732,734],{},[35,733,674],{}," is known.",[31,736,737,738,741],{},"But, why would a simple version check ",[35,739,740],{},"rg --version"," create an empty file inside my project workspace?",[241,743,745],{"id":744},"phase-2-looking-for-the-upstream-bug","Phase 2: Looking for the upstream bug",[31,747,748,749,752],{},"Time to investigate the ",[35,750,751],{},"gitlab.gitlab-workflow-6.83.2"," extension.",[40,754,756],{"className":42,"code":755,"language":44,"meta":8,"style":8},"cat /home/nicolasbages/.vscode-server/extensions/gitlab.gitlab-workflow-6.83.2/assets/language-server/main-bundle-node.js | grep \"rg --version\"\n# nothing\n\ncat /home/nicolasbages/.vscode-server/extensions/gitlab.gitlab-workflow-6.83.2/assets/language-server/main-bundle-node.js | grep \"rg \"\n[...]\n(`rg already extracted at ${s}`),s;(0,AD.mkdirSync)(n,{recursive:!0});let a=`${s}.tmp.${process.pid}`;return(0,AD.writeFileSync)(a,t),process.platform!==\"win32\"&&(0,AD.chmodSync)(a,493),(0,AD.renameSync)(a,s),this.#e.info(`rg extracted to ${s}`),s}static{Lgi()}});\n[...]\n# Ah !\n",[35,757,758,778,784,788,805,809,951,956],{"__ignoreMap":8},[48,759,760,763,766,769,772,774,776],{"class":50,"line":51},[48,761,762],{"class":54},"cat",[48,764,765],{"class":58}," /home/nicolasbages/.vscode-server/extensions/gitlab.gitlab-workflow-6.83.2/assets/language-server/main-bundle-node.js",[48,767,768],{"class":344}," |",[48,770,771],{"class":54}," grep",[48,773,72],{"class":65},[48,775,740],{"class":58},[48,777,516],{"class":65},[48,779,780],{"class":50,"line":62},[48,781,783],{"class":782},"ssUfO","# nothing\n",[48,785,786],{"class":50,"line":105},[48,787,280],{"emptyLinePlaceholder":279},[48,789,790,792,794,796,798,800,803],{"class":50,"line":201},[48,791,762],{"class":54},[48,793,765],{"class":58},[48,795,768],{"class":344},[48,797,771],{"class":54},[48,799,72],{"class":65},[48,801,802],{"class":58},"rg ",[48,804,516],{"class":65},[48,806,807],{"class":50,"line":519},[48,808,204],{"class":65},[48,810,812,814,817,819,822,825,828,831,833,836,839,842,845,848,851,854,857,860,863,865,868,871,873,876,878,881,883,886,889,891,894,896,899,902,905,908,911,914,917,919,922,924,927,929,932,935,937,939,942,944,946,948],{"class":50,"line":811},6,[48,813,371],{"class":65},[48,815,816],{"class":65},"`",[48,818,674],{"class":54},[48,820,821],{"class":58}," already extracted at ",[48,823,824],{"class":65},"${",[48,826,827],{"class":353},"s",[48,829,830],{"class":65},"}`",[48,832,377],{"class":65},[48,834,835],{"class":54},",s",[48,837,838],{"class":65},";(",[48,840,841],{"class":54},"0,AD.mkdirSync",[48,843,844],{"class":65},")(",[48,846,847],{"class":54},"n,",[48,849,850],{"class":58},"{recursive:!0}",[48,852,853],{"class":65},");",[48,855,856],{"class":54},"let",[48,858,859],{"class":58}," a=",[48,861,862],{"class":65},"`${",[48,864,827],{"class":353},[48,866,867],{"class":65},"}",[48,869,870],{"class":58},".tmp.",[48,872,824],{"class":65},[48,874,875],{"class":353},"process",[48,877,708],{"class":58},[48,879,880],{"class":353},"pid",[48,882,830],{"class":65},[48,884,885],{"class":65},";",[48,887,888],{"class":344},"return",[48,890,371],{"class":65},[48,892,893],{"class":58},"0,AD.writeFileSync",[48,895,844],{"class":65},[48,897,898],{"class":54},"a,t",[48,900,901],{"class":65},"),process.platform",[48,903,904],{"class":344},"!",[48,906,907],{"class":58},"==",[48,909,910],{"class":54},"\"win32\"",[48,912,913],{"class":65},"&&(",[48,915,916],{"class":54},"0,AD.chmodSync",[48,918,844],{"class":65},[48,920,921],{"class":54},"a,493",[48,923,377],{"class":65},[48,925,926],{"class":54},",(0,AD.renameSync",[48,928,844],{"class":65},[48,930,931],{"class":54},"a,s",[48,933,934],{"class":65},"),this.#e.info(",[48,936,816],{"class":65},[48,938,674],{"class":54},[48,940,941],{"class":58}," extracted to ",[48,943,824],{"class":65},[48,945,827],{"class":353},[48,947,830],{"class":65},[48,949,950],{"class":65},"),s}static{Lgi()}});\n",[48,952,954],{"class":50,"line":953},7,[48,955,204],{"class":65},[48,957,959],{"class":50,"line":958},8,[48,960,961],{"class":782},"# Ah !\n",[31,963,964,965,970],{},"Let's download Gitlab workflow language server from ",[643,966,969],{"href":967,"rel":968},"https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp",[647],"Gitlab's official repository"," and look at the original source code.",[31,972,973,974,981,982,708],{},"Turns out the piece of code found with grep in the minified code comes from ",[643,975,978],{"href":976,"rel":977},"https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/blob/5af81841a354ade0d329acaca09b38b12709f52a/packages/lib_workflow_executor/src/services/default_rg_binary_provider.ts",[647],[35,979,980],{},"packages/lib_workflow_executor/src/services/default_rg_binary_provider.ts","\nadded ",[643,983,986],{"href":984,"rel":985},"https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/commit/5af81841a354ade0d329acaca09b38b12709f52a",[647],"2 months ago",[40,988,992],{"className":989,"code":990,"language":991,"meta":8,"style":8},"language-ts shiki shiki-themes plastic","// packages/lib_workflow_executor/src/services/default_rg_binary_provider.ts\n\n  async #extractBinary(): Promise\u003Cstring> {\n    // Use Node fs to read the embedded file (works in both Bun and Node runtimes)\n    const binaryData = readFileSync(this.#embeddedPath);\n\n    const hash = createHash(\"sha256\")\n      .update(binaryData)\n      .digest(\"hex\")\n      .slice(0, 8);\n\n    const binaryName = process.platform === \"win32\" ? \"rg.exe\" : \"rg\";\n    const extractDir = join(tmpdir(), \"gitlab-duo-cli\", \"bin\", hash);\n    const extractedPath = join(extractDir, binaryName);\n\n    if (existsSync(extractedPath)) {\n      this.#logger.info(`rg already extracted at ${extractedPath}`);\n      return extractedPath;\n    }\n\n    mkdirSync(extractDir, { recursive: true });\n\n    // Write to temp file first, then atomically rename to avoid corrupted binary\n    // if process is killed mid-write\n    const tmpPath = `${extractedPath}.tmp.${process.pid}`;\n    writeFileSync(tmpPath, binaryData);\n\n    if (process.platform !== \"win32\") {\n      chmodSync(tmpPath, 0o755);\n    }\n\n    renameSync(tmpPath, extractedPath);\n\n    this.#logger.info(`rg extracted to ${extractedPath}`);\n    return extractedPath;\n  }\n","ts",[35,993,994,999,1003,1032,1037,1064,1068,1091,1096,1111,1117,1122,1157,1202,1226,1231,1251,1270,1276,1282,1287,1299,1304,1310,1316,1347,1364,1369,1395,1408,1413,1418,1434,1439,1470,1480],{"__ignoreMap":8},[48,995,996],{"class":50,"line":51},[48,997,998],{"class":782},"// packages/lib_workflow_executor/src/services/default_rg_binary_provider.ts\n",[48,1000,1001],{"class":50,"line":62},[48,1002,280],{"emptyLinePlaceholder":279},[48,1004,1005,1008,1011,1014,1017,1021,1024,1027,1029],{"class":50,"line":105},[48,1006,1007],{"class":353},"  async",[48,1009,1010],{"class":65}," #",[48,1012,1013],{"class":54},"extractBinary",[48,1015,1016],{"class":65},"(): ",[48,1018,1020],{"class":1019},"sVbv2","Promise",[48,1022,1023],{"class":344},"\u003C",[48,1025,1026],{"class":353},"string",[48,1028,345],{"class":344},[48,1030,1031],{"class":65}," {\n",[48,1033,1034],{"class":50,"line":201},[48,1035,1036],{"class":782},"    // Use Node fs to read the embedded file (works in both Bun and Node runtimes)\n",[48,1038,1039,1042,1045,1048,1051,1053,1056,1058,1061],{"class":50,"line":519},[48,1040,1041],{"class":65},"    const ",[48,1043,1044],{"class":353},"binaryData",[48,1046,1047],{"class":344}," =",[48,1049,1050],{"class":54}," readFileSync",[48,1052,371],{"class":65},[48,1054,1055],{"class":344},"this",[48,1057,708],{"class":65},[48,1059,1060],{"class":353},"#embeddedPath",[48,1062,1063],{"class":65},");\n",[48,1065,1066],{"class":50,"line":811},[48,1067,280],{"emptyLinePlaceholder":279},[48,1069,1070,1072,1075,1077,1080,1082,1084,1087,1089],{"class":50,"line":953},[48,1071,1041],{"class":65},[48,1073,1074],{"class":353},"hash",[48,1076,1047],{"class":344},[48,1078,1079],{"class":54}," createHash",[48,1081,371],{"class":65},[48,1083,78],{"class":65},[48,1085,1086],{"class":58},"sha256",[48,1088,78],{"class":65},[48,1090,102],{"class":65},[48,1092,1093],{"class":50,"line":958},[48,1094,1095],{"class":65},"      .update(binaryData)\n",[48,1097,1099,1102,1104,1107,1109],{"class":50,"line":1098},9,[48,1100,1101],{"class":65},"      .digest(",[48,1103,78],{"class":65},[48,1105,1106],{"class":58},"hex",[48,1108,78],{"class":65},[48,1110,102],{"class":65},[48,1112,1114],{"class":50,"line":1113},10,[48,1115,1116],{"class":65},"      .slice(0, 8);\n",[48,1118,1120],{"class":50,"line":1119},11,[48,1121,280],{"emptyLinePlaceholder":279},[48,1123,1125,1128,1130,1133,1135,1138,1140,1143,1145,1148,1150,1152,1154],{"class":50,"line":1124},12,[48,1126,1127],{"class":65},"    const binaryName = process.platform === ",[48,1129,78],{"class":65},[48,1131,1132],{"class":58},"win32",[48,1134,78],{"class":65},[48,1136,1137],{"class":65}," ? ",[48,1139,78],{"class":65},[48,1141,1142],{"class":58},"rg.exe",[48,1144,78],{"class":65},[48,1146,1147],{"class":65}," : ",[48,1149,78],{"class":65},[48,1151,674],{"class":58},[48,1153,78],{"class":65},[48,1155,1156],{"class":65},";\n",[48,1158,1160,1163,1166,1168,1171,1173,1176,1179,1181,1184,1186,1189,1191,1194,1196,1198,1200],{"class":50,"line":1159},13,[48,1161,1162],{"class":353},"    const",[48,1164,1165],{"class":353}," extractDir",[48,1167,1047],{"class":344},[48,1169,1170],{"class":54}," join",[48,1172,371],{"class":65},[48,1174,1175],{"class":54},"tmpdir",[48,1177,1178],{"class":65},"(), ",[48,1180,78],{"class":65},[48,1182,1183],{"class":58},"gitlab-duo-cli",[48,1185,78],{"class":65},[48,1187,1188],{"class":65},", ",[48,1190,78],{"class":65},[48,1192,1193],{"class":58},"bin",[48,1195,78],{"class":65},[48,1197,1188],{"class":65},[48,1199,1074],{"class":353},[48,1201,1063],{"class":65},[48,1203,1205,1207,1210,1212,1214,1216,1219,1221,1224],{"class":50,"line":1204},14,[48,1206,1162],{"class":353},[48,1208,1209],{"class":353}," extractedPath",[48,1211,1047],{"class":344},[48,1213,1170],{"class":54},[48,1215,371],{"class":65},[48,1217,1218],{"class":353},"extractDir",[48,1220,1188],{"class":65},[48,1222,1223],{"class":353},"binaryName",[48,1225,1063],{"class":65},[48,1227,1229],{"class":50,"line":1228},15,[48,1230,280],{"emptyLinePlaceholder":279},[48,1232,1234,1237,1240,1243,1245,1248],{"class":50,"line":1233},16,[48,1235,1236],{"class":54},"    if",[48,1238,1239],{"class":65}," (",[48,1241,1242],{"class":54},"existsSync",[48,1244,371],{"class":65},[48,1246,1247],{"class":353},"extractedPath",[48,1249,1250],{"class":65},")) {\n",[48,1252,1254,1257,1259,1262,1264,1266,1268],{"class":50,"line":1253},17,[48,1255,1256],{"class":65},"      this.#logger.info(",[48,1258,816],{"class":65},[48,1260,1261],{"class":58},"rg already extracted at ",[48,1263,824],{"class":65},[48,1265,1247],{"class":353},[48,1267,830],{"class":65},[48,1269,1063],{"class":65},[48,1271,1273],{"class":50,"line":1272},18,[48,1274,1275],{"class":65},"      return extractedPath;\n",[48,1277,1279],{"class":50,"line":1278},19,[48,1280,1281],{"class":65},"    }\n",[48,1283,1285],{"class":50,"line":1284},20,[48,1286,280],{"emptyLinePlaceholder":279},[48,1288,1290,1293,1296],{"class":50,"line":1289},21,[48,1291,1292],{"class":65},"    mkdirSync(extractDir, { recursive: ",[48,1294,1295],{"class":135},"true",[48,1297,1298],{"class":65}," });\n",[48,1300,1302],{"class":50,"line":1301},22,[48,1303,280],{"emptyLinePlaceholder":279},[48,1305,1307],{"class":50,"line":1306},23,[48,1308,1309],{"class":782},"    // Write to temp file first, then atomically rename to avoid corrupted binary\n",[48,1311,1313],{"class":50,"line":1312},24,[48,1314,1315],{"class":782},"    // if process is killed mid-write\n",[48,1317,1319,1321,1324,1326,1329,1331,1333,1335,1337,1339,1341,1343,1345],{"class":50,"line":1318},25,[48,1320,1162],{"class":353},[48,1322,1323],{"class":353}," tmpPath",[48,1325,1047],{"class":344},[48,1327,1328],{"class":65}," `${",[48,1330,1247],{"class":353},[48,1332,867],{"class":65},[48,1334,870],{"class":58},[48,1336,824],{"class":65},[48,1338,875],{"class":353},[48,1340,708],{"class":65},[48,1342,880],{"class":353},[48,1344,830],{"class":65},[48,1346,1156],{"class":65},[48,1348,1350,1353,1355,1358,1360,1362],{"class":50,"line":1349},26,[48,1351,1352],{"class":54},"    writeFileSync",[48,1354,371],{"class":65},[48,1356,1357],{"class":353},"tmpPath",[48,1359,1188],{"class":65},[48,1361,1044],{"class":353},[48,1363,1063],{"class":65},[48,1365,1367],{"class":50,"line":1366},27,[48,1368,280],{"emptyLinePlaceholder":279},[48,1370,1372,1374,1376,1378,1380,1383,1386,1388,1390,1392],{"class":50,"line":1371},28,[48,1373,1236],{"class":54},[48,1375,1239],{"class":65},[48,1377,875],{"class":353},[48,1379,708],{"class":65},[48,1381,1382],{"class":353},"platform",[48,1384,1385],{"class":344}," !==",[48,1387,72],{"class":65},[48,1389,1132],{"class":58},[48,1391,78],{"class":65},[48,1393,1394],{"class":65},") {\n",[48,1396,1398,1401,1403,1405],{"class":50,"line":1397},29,[48,1399,1400],{"class":54},"      chmodSync",[48,1402,371],{"class":65},[48,1404,1357],{"class":353},[48,1406,1407],{"class":65},", 0o755);\n",[48,1409,1411],{"class":50,"line":1410},30,[48,1412,1281],{"class":65},[48,1414,1416],{"class":50,"line":1415},31,[48,1417,280],{"emptyLinePlaceholder":279},[48,1419,1421,1424,1426,1428,1430,1432],{"class":50,"line":1420},32,[48,1422,1423],{"class":54},"    renameSync",[48,1425,371],{"class":65},[48,1427,1357],{"class":353},[48,1429,1188],{"class":65},[48,1431,1247],{"class":353},[48,1433,1063],{"class":65},[48,1435,1437],{"class":50,"line":1436},33,[48,1438,280],{"emptyLinePlaceholder":279},[48,1440,1442,1445,1447,1450,1452,1455,1457,1459,1462,1464,1466,1468],{"class":50,"line":1441},34,[48,1443,1444],{"class":344},"    this",[48,1446,708],{"class":65},[48,1448,1449],{"class":353},"#logger",[48,1451,708],{"class":65},[48,1453,1454],{"class":54},"info",[48,1456,371],{"class":65},[48,1458,816],{"class":65},[48,1460,1461],{"class":58},"rg extracted to ",[48,1463,824],{"class":65},[48,1465,1247],{"class":353},[48,1467,830],{"class":65},[48,1469,1063],{"class":65},[48,1471,1473,1476,1478],{"class":50,"line":1472},35,[48,1474,1475],{"class":353},"    return",[48,1477,1209],{"class":353},[48,1479,1156],{"class":65},[48,1481,1483],{"class":50,"line":1482},36,[48,1484,1485],{"class":65},"  }\n",[31,1487,1488],{},"This class shows:",[1490,1491,1492,1503,1509],"ol",{},[685,1493,1494,1495,1498,1499,1502],{},"The code relies on Node's ",[35,1496,1497],{},"tmpdir()"," in line 50 to find a safe directory. Usually it should be ",[35,1500,1501],{},"/tmp"," but in the context of a remote SSH environment, the directory is the workspace.",[685,1504,1505,1506,708],{},"So it writes a temprary file in the workspace directory with ",[35,1507,1508],{},"writeFileSync(tmpPath)",[685,1510,1511,1512,1514,1515,1518,1519,708],{},"The kernel receives the system call ",[35,1513,689],{}," with the ",[35,1516,1517],{},"O_CREAT flag",". It creates the inode on disk, 0-byte, with the raw-byte name ",[35,1520,1521],{},"''$'\\001''4'$'\\253\\006''@W@8'",[31,1523,1524],{},"However, during initialization, the extension multiple spawns background shell checks. If the extension or VSCode's remote extension host decides to restart the language server, it kills the process.",[31,1526,1527,1528,1531],{},"Because Node is killed mid-operation, it never reaches the ",[35,1529,1530],{},"renameSync()"," line 69 which would move and clean up the file. And because it's a hard process termination, Node's internal garbage collection and exception handlers never run.",[241,1533,1535],{"id":1534},"clean-up-and-mitigation","Clean-up and mitigation",[31,1537,1538,1539,1542],{},"Because the filename contains raw characters, ",[35,1540,1541],{},"rm"," doesn't work.",[40,1544,1545],{"className":42,"code":217,"language":44,"meta":8,"style":8},[35,1546,1547],{"__ignoreMap":8},[48,1548,1549,1551,1553,1555,1557],{"class":50,"line":51},[48,1550,224],{"class":54},[48,1552,227],{"class":58},[48,1554,230],{"class":135},[48,1556,233],{"class":135},[48,1558,236],{"class":135},[31,1560,1561],{},"At the time of writing, there is no update available for this extension so I decided to disable it for now.",[1563,1564,1565],"style",{},"html pre.shiki code .sJix2, html code.shiki .sJix2{--shiki-default:#B57EDC}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html pre.shiki code .sGSqi, html code.shiki .sGSqi{--shiki-default:#A9B2C3}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sjrmR, html code.shiki .sjrmR{--shiki-default:#56B6C2}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sVs6v, html code.shiki .sVs6v{--shiki-default:#C6CCD7}html pre.shiki code .ssUfO, html code.shiki .ssUfO{--shiki-default:#5F6672;--shiki-default-font-style:italic}html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}",{"title":8,"searchDepth":62,"depth":62,"links":1567},[1568,1571,1572],{"id":243,"depth":62,"text":244,"children":1569},[1570],{"id":327,"depth":105,"text":328},{"id":744,"depth":62,"text":745},{"id":1534,"depth":62,"text":1535},"VSCode creates ghost files during its initialization process on remote servers. Spoiler, it was Gitlab Workflow.","md",{"publication_date":1576,"views":1577,"status":1579},"2026-06-21",[1578],"builder","published","/building/articles/vscode-ghost-files-remote-server",{"title":26,"description":1573},{"loc":1580},"building/articles/vscode-ghost-files-remote-server","Ic8TTOK41jeYA-Gv-YBtXXAHNmfTJmW37ghdpKCb_qw",1782716746022]