{"thread":{"id":"60148","subject":"Git credential-cache manager is not treating `path=` correctly","startedAt":"2023-08-23T16:23:53Z","lastAt":"2023-08-24T14:45:30Z","messageCount":4,"participants":["Gabriel Nützi","Jeff King","Ganriel Nützi"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"480960","messageId":"CAA9rTuccfyXO66YApDfss6JM9+JuUzZxDiYyfG_qShfehyAEsQ@mail.gmail.com","threadId":"60148","inReplyTo":null,"subject":"Git credential-cache manager is not treating `path=` correctly","fromName":"Gabriel Nützi","fromEmail":"gnuetzi@gmail.com","sentAt":"2023-08-23T16:23:36Z","receivedAt":"2023-08-23T16:23:53Z","isPatch":false,"sender":{"key":"gnuetzi@gmail.com","avatar":"https://gravatar.com/avatar/eda99c30173930819ae1c26c3dff249edd4b3c735c3886d268c0d6a71c6307c9?d=mp&s=160"},"body":"I report this potential bug:\nhttps://stackoverflow.com/questions/76963207/git-credential-cache-does-not-matchpath-correctly\n\nI really dont get Git `2.41.0` and its crude `git credential-cache` manager:\n\nWhen I test `git credential-cache` with the below script (`setsid test.sh`).\nif it can report 3 different passwords it will fail with\n\n```\nfatal: could not read Username for\n'https://www.server.com/repos/1.git': No such device or address\n```\non the first run,\nand when the script runs **a second time** it will pass happily.\n\nThe `git credential-store` on the other hand does not work at all and\non the first `git credential fill` prompt the user.\n\n**Question: What is going on here. This makes no sense and results in\npeculiar fails on CI**\n\n*Note: setsid is to detach from the controlling terminal that git does\nnot prompt or something.*\n*Note: We need `useHttpPath=true` to tell Git to use the `path` in\ncredential `fill` statements (AFAIK).\n\n\n```shell\n#!/bin/bash\nset -e\n# set -x\n# IMPORTANT: =======================================\n# Execute this script inside a container\n# and check if it reports all password correctly\n#\n# ==================================================\n\n# git credential-cache exit || true\n\ngit config --global --unset-all credential.helper || true\ngit config --global --add credential.helper \"\"\n# git config --global --add credential.helper \"store\" # Does not work at all.\ngit config --global --add credential.helper \"cache --timeout=7200\"\n\ngit config --global\n\"credential.https://www.server.com/repos/1.git.useHttpPath\" true\ngit config --global\n\"credential.https://www.server.com/repos/2.git.useHttpPath\" true\n\nexport GIT_TRACE=1\n\n# Add 3 credentials, 2 specifics with `path=`\n# 1 with general host.\n{\n    echo \"protocol=https\"\n    echo \"host=www.server.com\"\n    echo \"path=repos/1.git\"\n    echo \"username=banana\"\n    echo \"password=banana1\"\n} | git credential approve\n\n{\n    echo \"protocol=https\"\n    echo \"host=www.server.com\"\n    echo \"path=repos/2.git\"\n    echo \"username=banana\"\n    echo \"password=banana2\"\n} | git credential approve\n\n{\n    echo \"protocol=https\"\n    echo \"host=www.server.com\"\n    echo \"username=banana\"\n    echo \"password=general\"\n} | git credential approve\n\necho \"Check the credentials\"\n# Check it\n{\n    echo \"protocol=https\"\n    echo \"host=www.server.com\"\n    echo \"path=repos/1.git\"\n} | git credential fill | grep -q \"password=banana1\" || {\n    echo \"wrong pass banana1\"\n    exit 1\n}\n\n{\n    echo \"protocol=https\"\n    echo \"host=www.server.com\"\n    echo \"path=repos/2.git\"\n} | git credential fill | grep -q \"password=banana2\" || {\n    echo \"wrong pass banana2\"\n    exit 1\n}\n\n{\n    echo \"protocol=https\"\n    echo \"host=www.server.com\"\n} | git credential fill | grep -q \"password=general\" || {\n    echo \"wrong pass general\"\n    exit 1\n}\n\necho \"all passwords correctly reported\"\n```\n"},{"id":"480965","messageId":"20230823192207.GC2355007@coredump.intra.peff.net","threadId":"60148","inReplyTo":"CAA9rTuccfyXO66YApDfss6JM9+JuUzZxDiYyfG_qShfehyAEsQ@mail.gmail.com","subject":"Re: Git credential-cache manager is not treating `path=` correctly","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2023-08-23T19:22:07Z","receivedAt":"2023-08-23T19:23:18Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Aug 23, 2023 at 06:23:36PM +0200, Gabriel Nützi wrote:\n\n> # Add 3 credentials, 2 specifics with `path=`\n> # 1 with general host.\n> {\n>     echo \"protocol=https\"\n>     echo \"host=www.server.com\"\n>     echo \"path=repos/1.git\"\n>     echo \"username=banana\"\n>     echo \"password=banana1\"\n> } | git credential approve\n> \n> {\n>     echo \"protocol=https\"\n>     echo \"host=www.server.com\"\n>     echo \"path=repos/2.git\"\n>     echo \"username=banana\"\n>     echo \"password=banana2\"\n> } | git credential approve\n> \n> {\n>     echo \"protocol=https\"\n>     echo \"host=www.server.com\"\n>     echo \"username=banana\"\n>     echo \"password=general\"\n> } | git credential approve\n\nI don't think we ever planned around this kind of \"sometimes paths are\nimportant, and sometimes not\", and I'm not surprised it doesn't work.\n\nThe \"useHTTPPath\" flag is only read by the by the main Git side, and\njust tells it whether to pass a \"path\" entry. On the helper side, any\nomitted entry (\"path\" in the final case) means \"match anything\".\n\nSo that final command translates (from the helper's view) to \"store\nthis, and delete any other credentials we have stored for\nhttps://banana@www.server.com\".\n\nIt might be possible to change the handling on the helper side to\ndistinguish between entries with no path and entries with a path, and\nconsider them separately. I don't know what gotchas we might see in that\ncase, though. I suspect at least in credential-store's on-disk format,\nthere is not really room to distinguish the empty-string path from \"no\npath given\".\n\nIf you set credential.useHTTPPaths (so that it is applied consistently),\nI think path-matching should work. But then you'd have to independently\nstore credentials for each path that could match the \"generic\" case.\n\nAs a workaround, you could use two different stores/caches. Something\nlike:\n\n  [credential]\n  # store most stuff here\n  helper = store\n\n  [credential \"https://www.server.com/repos/1.git\"]\n  # turn off the main helper, and use a path-specific file instead\n  helper =\n  helper = store --file=$HOME/.git-credentials-path\n  useHttpPath = true\n\n-Peff\n"},{"id":"480974","messageId":"14F3D856-6A3B-45F8-B5FD-9CD5D0CB7C47@gmail.com","threadId":"60148","inReplyTo":"20230823192207.GC2355007@coredump.intra.peff.net","subject":"Re: Git credential-cache manager is not treating `path=` correctly","fromName":"Ganriel Nützi","fromEmail":"gnuetzi@gmail.com","sentAt":"2023-08-23T21:02:44Z","receivedAt":"2023-08-23T21:03:42Z","isPatch":false,"sender":{"key":"gnuetzi@gmail.com","avatar":"https://gravatar.com/avatar/eda99c30173930819ae1c26c3dff249edd4b3c735c3886d268c0d6a71c6307c9?d=mp&s=160"},"body":"Thanks a lot for this informations.\n\nWhat I did not get from the documentation ist that a “store” of a generic (without path) credential , e.g https://www.server.com will\njust overwrite the more specific ones already in there. (So that is the case in my test, right? And the prompt happens in the first fill because useHttpPath=true but there is nomore any credential entry in the cache which would match (only the generic but that does not match), right?)\n\nI selectively applied useHttpPath for certain urls such that any other url not using httpPath\nmatches the generic one (urls for submodules which I dont want to add credentials).\nSo I just have to make sure the generic ones are added first and then the more specific ones (with path)\n\nI am not sure but I thought that using cache is safer than store because i generally dont want to have credentials on a disk.\n\n- Gabriel\n\n\nVon meinem iPhone gesendet\n\n> Am 23.08.2023 um 21:22 schrieb Jeff King <peff@peff.net>:\n> \n> ﻿On Wed, Aug 23, 2023 at 06:23:36PM +0200, Gabriel Nützi wrote:\n> \n>> # Add 3 credentials, 2 specifics with `path=`\n>> # 1 with general host.\n>> {\n>>    echo \"protocol=https\"\n>>    echo \"host=www.server.com\"\n>>    echo \"path=repos/1.git\"\n>>    echo \"username=banana\"\n>>    echo \"password=banana1\"\n>> } | git credential approve\n>> \n>> {\n>>    echo \"protocol=https\"\n>>    echo \"host=www.server.com\"\n>>    echo \"path=repos/2.git\"\n>>    echo \"username=banana\"\n>>    echo \"password=banana2\"\n>> } | git credential approve\n>> \n>> {\n>>    echo \"protocol=https\"\n>>    echo \"host=www.server.com\"\n>>    echo \"username=banana\"\n>>    echo \"password=general\"\n>> } | git credential approve\n> \n> I don't think we ever planned around this kind of \"sometimes paths are\n> important, and sometimes not\", and I'm not surprised it doesn't work.\n> \n> The \"useHTTPPath\" flag is only read by the by the main Git side, and\n> just tells it whether to pass a \"path\" entry. On the helper side, any\n> omitted entry (\"path\" in the final case) means \"match anything\".\n> \n> So that final command translates (from the helper's view) to \"store\n> this, and delete any other credentials we have stored for\n> https://banana@www.server.com\".\n> \n> It might be possible to change the handling on the helper side to\n> distinguish between entries with no path and entries with a path, and\n> consider them separately. I don't know what gotchas we might see in that\n> case, though. I suspect at least in credential-store's on-disk format,\n> there is not really room to distinguish the empty-string path from \"no\n> path given\".\n> \n> If you set credential.useHTTPPaths (so that it is applied consistently),\n> I think path-matching should work. But then you'd have to independently\n> store credentials for each path that could match the \"generic\" case.\n> \n> As a workaround, you could use two different stores/caches. Something\n> like:\n> \n>  [credential]\n>  # store most stuff here\n>  helper = store\n> \n>  [credential \"https://www.server.com/repos/1.git\"]\n>  # turn off the main helper, and use a path-specific file instead\n>  helper =\n>  helper = store --file=$HOME/.git-credentials-path\n>  useHttpPath = true\n> \n> -Peff\n"},{"id":"480994","messageId":"20230824144439.GB3108100@coredump.intra.peff.net","threadId":"60148","inReplyTo":"14F3D856-6A3B-45F8-B5FD-9CD5D0CB7C47@gmail.com","subject":"Re: Git credential-cache manager is not treating `path=` correctly","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2023-08-24T14:44:39Z","receivedAt":"2023-08-24T14:45:30Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Aug 23, 2023 at 11:02:44PM +0200, Ganriel Nützi wrote:\n\n> What I did not get from the documentation ist that a “store” of a\n> generic (without path) credential , e.g https://www.server.com will\n> just overwrite the more specific ones already in there. (So that is\n> the case in my test, right? And the prompt happens in the first fill\n> because useHttpPath=true but there is nomore any credential entry in\n> the cache which would match (only the generic but that does not\n> match), right?)\n\nRight, exactly. It's possible that other helpers may behave differently\nhere, but certainly the scheme used by both \"store\" and \"cache\" is to\ndelete/replace anything that matches with the new credential.\n\n> I am not sure but I thought that using cache is safer than store\n> because i generally dont want to have credentials on a disk.\n\nIt is safer. I used the \"store\" one while testing because it is easy to\nlook at its internal state at any point with \"cat ~/.git-credentials\". :)\n\nBut the same logic should apply to \"cache\", and my \"use two helpers\"\ntrick should work by giving it a separate socket, like:\n\n  [credential]\n  helper = cache\n\n  [credential \"https://www.server.com/repos/1.git\"]\n  # separate socket/daemon for path-respecting entries\n  helper =\n  helper = credential --socket=$HOME/.git-credential-cache/socket-path\n  useHttpPath = true\n\n-Peff\n"}]}