mirror of
https://github.com/tiktok/sparo.git
synced 2024-11-30 02:55:35 -05:00
1 line
No EOL
12 KiB
JavaScript
1 line
No EOL
12 KiB
JavaScript
"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[545],{6746:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>n,metadata:()=>a,toc:()=>l});var r=t(678),i=t(4738);const n={title:"Security"},o=void 0,a={id:"pages/reference/security",title:"Security",description:"Because the Sparo tool acts as a wrapper for Git, our goal is to provide comparable security expectations as the git command.",source:"@site/docs/pages/reference/security.md",sourceDirName:"pages/reference",slug:"/pages/reference/security",permalink:"/sparo/pages/reference/security",draft:!1,unlisted:!1,editUrl:"https://github.com/tiktok/sparo/tree/main/apps/website/docs/pages/reference/security.md",tags:[],version:"current",frontMatter:{title:"Security"},sidebar:"docsSidebar",previous:{title:"Skeleton folders",permalink:"/sparo/pages/reference/skeleton_folders"},next:{title:"<profile-name>.json",permalink:"/sparo/pages/configs/profile_json"}},c={},l=[{value:"Security scenarios",id:"security-scenarios",level:2},{value:"SS1: Safely clone an untrusted repo",id:"ss1-safely-clone-an-untrusted-repo",level:3},{value:"SS2: Safely clone an untrusted repository parameter",id:"ss2-safely-clone-an-untrusted-repository-parameter",level:3},{value:"SS3: Git parameters may include special characters",id:"ss3-git-parameters-may-include-special-characters",level:3},{value:"Security assumptions",id:"security-assumptions",level:2},{value:"Assumption: Shell environment variables are trusted",id:"assumption-shell-environment-variables-are-trusted",level:2},{value:"Assumption: Command line is generally trusted",id:"assumption-command-line-is-generally-trusted",level:2},{value:"Assumption: Commands may consume excessive resources",id:"assumption-commands-may-consume-excessive-resources",level:2},{value:"Assumption: STDOUT and STDERR may contain arbitrary characters",id:"assumption-stdout-and-stderr-may-contain-arbitrary-characters",level:2}];function d(e){const s={a:"a",blockquote:"blockquote",code:"code",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(s.p,{children:["Because the Sparo tool acts as a wrapper for Git, our goal is to provide comparable security expectations as the ",(0,r.jsx)(s.code,{children:"git"})," command."]}),"\n",(0,r.jsxs)(s.blockquote,{children:["\n",(0,r.jsxs)(s.p,{children:["\u26a0\ufe0f ",(0,r.jsx)(s.strong,{children:"This is a goal not a guarantee."})," \u26a0\ufe0f"]}),"\n",(0,r.jsx)(s.p,{children:"The software is still in its early stages of development, and not all security\nrequirements have been identified or implemented yet. Efforts to improve Sparo\nsecurity should not be interpreted to contradict the terms of the MIT license:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{children:'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n'})}),"\n"]}),"\n",(0,r.jsx)(s.h2,{id:"security-scenarios",children:"Security scenarios"}),"\n",(0,r.jsxs)(s.p,{children:["Git doesn't provide a formal security specification, so to facilitate analysis of Sparo contributions, we've identified usage scenarios that imply security requirements. We welcome your feedback -- please ",(0,r.jsx)(s.a,{href:"/sparo/pages/support/contributing",children:"let us know"})," if we've overlooked an important use case or if Git does not behave as described."]}),"\n",(0,r.jsx)(s.h3,{id:"ss1-safely-clone-an-untrusted-repo",children:"SS1: Safely clone an untrusted repo"}),"\n",(0,r.jsxs)(s.p,{children:["Suppose that an unfamiliar remote Git repository contains malicious files, which includes malicious config files such as ",(0,r.jsx)(s.code,{children:".gitattributes"}),", ",(0,r.jsx)(s.code,{children:".gitignore"}),", and Git hook scripts. The following operations are expected to be safe:"]}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["Using ",(0,r.jsx)(s.code,{children:"git clone"})," to clone the remote repo."]}),"\n",(0,r.jsxs)(s.li,{children:["Using ",(0,r.jsx)(s.code,{children:"git checkout"})," to checkout files."]}),"\n",(0,r.jsxs)(s.li,{children:["Using ",(0,r.jsx)(s.code,{children:"git commit"})," to commit modifications of local files."]}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["Git ensures safety by ignoring Git hooks and ",(0,r.jsx)(s.code,{children:".gitattributes"}),' filters by default. The user must explicitly run a command to "opt-in", signifying their trust that the repository is free from malicious code. For example, invoking ',(0,r.jsx)(s.code,{children:"rush install"})," will register predefined Git hooks, because NPM installation involves executing untrusted scripts and therefore signifies trust in the cloned repository. As another example, if ",(0,r.jsx)(s.code,{children:".gitattributes"})," references the LFS filter, the user must first opt-in by running ",(0,r.jsx)(s.code,{children:"git lfs install"}),", signifying their trust that the filter author has implemented security protections against malicious inputs for that filter."]}),"\n",(0,r.jsxs)(s.p,{children:["Sparo introduces additional config files such as ",(0,r.jsx)(s.a,{href:"/sparo/pages/configs/profile_json",children:"<profile-name>.json"}),". Parsing of these config files must also treat the inputs as potentially malicious, and provide the same guarantees."]}),"\n",(0,r.jsx)(s.h3,{id:"ss2-safely-clone-an-untrusted-repository-parameter",children:"SS2: Safely clone an untrusted repository parameter"}),"\n",(0,r.jsxs)(s.p,{children:["A command such as ",(0,r.jsx)(s.code,{children:"git clone https://github.com/example/project.git"})," will write into a subfolder called ",(0,r.jsx)(s.code,{children:"project"}),". The Git documentation calls this the ",(0,r.jsx)(s.a,{href:"https://git-scm.com/docs/git-clone#Documentation/git-clone.txt-ltdirectorygt",children:'"humanish"'})," portion of the URL."]}),"\n",(0,r.jsxs)(s.p,{children:["Consider a remote service that receives the ",(0,r.jsx)(s.code,{children:"REPOSITORY"})," parameter as a text string and then invokes ",(0,r.jsx)(s.code,{children:"git clone REPOSITORY"})," with correct shell-escaping of the parameter. In calculating the humanish folder name, Git should not incorporate special characters such as ",(0,r.jsx)(s.code,{children:".."})," or ",(0,r.jsx)(s.code,{children:"/"})," that would cause the operation to write cloned files outside of the intended folder."]}),"\n",(0,r.jsxs)(s.p,{children:["And of course, if an explicit target folder is specified using ",(0,r.jsx)(s.code,{children:"git clone https://github.com/example/project.git my-folder"}),", then no files should be cloned outside of the ",(0,r.jsx)(s.code,{children:"my-folder"})," folder."]}),"\n",(0,r.jsx)(s.h3,{id:"ss3-git-parameters-may-include-special-characters",children:"SS3: Git parameters may include special characters"}),"\n",(0,r.jsxs)(s.p,{children:["Shell interpreters commonly transform expressions involving special characters such as ",(0,r.jsx)(s.code,{children:"$"}),", ",(0,r.jsx)(s.code,{children:"%"}),", ",(0,r.jsx)(s.code,{children:"("}),", etc. For example:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-shell",children:'# Problem: Bash would replace "$project" with the value of\n# the environment variable whose name is "project".\ngit clone https://github.com/example/project.git $project\n'})}),"\n",(0,r.jsx)(s.p,{children:"This requires escaping:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-shell",children:"# This backslash escape ensures that a literal dollar sign\n# is included in the created folder name:\ngit clone https://github.com/example/project.git \\$project\n"})}),"\n",(0,r.jsxs)(s.p,{children:["When the ",(0,r.jsx)(s.code,{children:"sparo"})," command-line invokes subprocesses such as ",(0,r.jsx)(s.code,{children:"git"}),", it must carefully ensure that process arguments are correctly escaped to avoid being transformed by the shell. For example, if ",(0,r.jsx)(s.code,{children:"\\$project"})," gets expanded by the shell during subprocess invocation, the escaping will be defeated, which could be exploited to circumvent the other Sparo security guarantees. If certain characters ",(0,r.jsx)(s.a,{href:"https://github.com/microsoft/rushstack/blob/e2a17c81731cadc6b39b8e75c08dfccb9bc5ce9c/libraries/node-core-library/src/Executable.ts#L689",children:"cannot be safely escaped"})," by Node.js, they should be rejected with an error message."]}),"\n",(0,r.jsx)(s.h2,{id:"security-assumptions",children:"Security assumptions"}),"\n",(0,r.jsx)(s.p,{children:"It's also useful to point out aspects that are NOT expected to be secure."}),"\n",(0,r.jsx)(s.h2,{id:"assumption-shell-environment-variables-are-trusted",children:"Assumption: Shell environment variables are trusted"}),"\n",(0,r.jsxs)(s.p,{children:["For the most part, the ",(0,r.jsx)(s.code,{children:"git"})," CLI assumes that the shell environment variables are trusted. For example, it relies on the ",(0,r.jsx)(s.code,{children:"PATH"})," variable to discover the location of the ",(0,r.jsx)(s.code,{children:"ssh"})," binary, and most of the parent process's variables are passed through to child processes."]}),"\n",(0,r.jsxs)(s.p,{children:["Because Sparo the tool is invoked by the Node.js runtime, arbitrary code execution is possible via environment variables such as ",(0,r.jsx)(s.a,{href:"https://nodejs.org/api/cli.html#node_optionsoptions",children:"NODE_OPTIONS"}),"."]}),"\n",(0,r.jsx)(s.h2,{id:"assumption-command-line-is-generally-trusted",children:"Assumption: Command line is generally trusted"}),"\n",(0,r.jsxs)(s.p,{children:["The ",(0,r.jsx)(s.code,{children:"git"})," command-line accepts parameters such as ",(0,r.jsx)(s.a,{href:"https://git-scm.com/docs/git#Documentation/git.txt--cltnamegtltvaluegt",children:"-c"})," which can trigger execution of arbitrary code. Therefore in general, we assume that the command-line parameters are trusted. However, certain parameters can provide stricter guarantees, for example the ",(0,r.jsx)(s.code,{children:"<repository>"})," argument for ",(0,r.jsx)(s.code,{children:"git clone"})," mentioned in ",(0,r.jsx)(s.strong,{children:"SS3"}),"."]}),"\n",(0,r.jsx)(s.h2,{id:"assumption-commands-may-consume-excessive-resources",children:"Assumption: Commands may consume excessive resources"}),"\n",(0,r.jsxs)(s.p,{children:["Commands such as ",(0,r.jsx)(s.code,{children:"git clone"})," may consume an arbitrary amount of disk space or take arbitrarily long to complete. In general, denial-of-service attacks are not considered an important risk for this type of development tool."]}),"\n",(0,r.jsx)(s.h2,{id:"assumption-stdout-and-stderr-may-contain-arbitrary-characters",children:"Assumption: STDOUT and STDERR may contain arbitrary characters"}),"\n",(0,r.jsxs)(s.p,{children:["When invoking the ",(0,r.jsx)(s.code,{children:"git"})," CLI, the console output may include strings printed by hook scripts or other shell commands. These strings may contain special characters that are unsafe to embed in other contexts such as an HTML document or SQL string literal. It is the responsibility of the calling processes to correctly escape any STDOUT or STDERR output produced by the ",(0,r.jsx)(s.code,{children:"git"})," or ",(0,r.jsx)(s.code,{children:"sparo"})," process."]})]})}function h(e={}){const{wrapper:s}={...(0,i.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},4738:(e,s,t)=>{t.d(s,{R:()=>o,x:()=>a});var r=t(6166);const i={},n=r.createContext(i);function o(e){const s=r.useContext(n);return r.useMemo((function(){return"function"==typeof e?e(s):{...s,...e}}),[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),r.createElement(n.Provider,{value:s},e.children)}}}]); |