Correctly honor the build context. That is, don't allow import/export of files higher in the directory tree than the convey.yaml
--- a/.hgignore Wed Nov 22 11:48:51 2017 -0600
+++ b/.hgignore Wed Dec 27 01:06:03 2017 -0600
@@ -1,6 +1,7 @@
^convey(-.+-amd64(\.exe)?)?$
+\/(foo|bar|baz|qux|quux)\/ --- a/ChangeLog Wed Nov 22 11:48:51 2017 -0600
+++ b/ChangeLog Wed Dec 27 01:06:03 2017 -0600
@@ -3,6 +3,7 @@
* Added --list-environment to show the config and builtin environment variables. Fixed #132
* Added a user option to the Docker run task. PR #38 (Eric Fritz)
* Added a target option to the Docker build task. To specify which stage to use in a multi stage Dockerfile.
+ * Correctly honor the build context. * Fix a regression in the environment mapper that could mess up script attributes. PR #37 (Eric Fritz)
--- a/docker/export.go Wed Nov 22 11:48:51 2017 -0600
+++ b/docker/export.go Wed Dec 27 01:06:03 2017 -0600
@@ -26,6 +26,7 @@
"github.com/aphistic/gomol"
"bitbucket.org/rw_grim/convey/environment"
+ "bitbucket.org/rw_grim/convey/path" "bitbucket.org/rw_grim/convey/state"
"bitbucket.org/rw_grim/convey/tasks"
"bitbucket.org/rw_grim/convey/yaml"
@@ -110,7 +111,13 @@
src, dest := tasks.ParseFilePath("", file)
- if err := exportFile(name, st.Workspace.Name(), src, dest, st); err != nil {
+ // make sure the destination is in our build context + realDest, err := path.TraversesNonExistent(st.CfgPath, dest) + if err := exportFile(name, st.Workspace.Name(), src, realDest, st); err != nil { --- a/docker/import.go Wed Nov 22 11:48:51 2017 -0600
+++ b/docker/import.go Wed Dec 27 01:06:03 2017 -0600
@@ -21,6 +21,7 @@
"github.com/aphistic/gomol"
"bitbucket.org/rw_grim/convey/environment"
+ "bitbucket.org/rw_grim/convey/path" "bitbucket.org/rw_grim/convey/state"
"bitbucket.org/rw_grim/convey/tasks"
"bitbucket.org/rw_grim/convey/yaml"
@@ -48,8 +49,14 @@
src, dest := tasks.ParseFilePath("", file)
+ // make sure the file we're import is in our build context + realSrc, err := path.Traverses(st.CfgPath, src) params := map[string]interface{}{
"workspaceID": st.Workspace.Name(),
--- a/main.go Wed Nov 22 11:48:51 2017 -0600
+++ b/main.go Wed Dec 27 01:06:03 2017 -0600
@@ -20,6 +20,7 @@
"github.com/alecthomas/kingpin"
@@ -98,19 +99,30 @@
// now make sure we found a config file
fmt.Printf("config file not found, looking for %s\n", strings.Join(loader.Filenames(), ","))
+ // figure out the path to the config file + cfgPath, err := filepath.Abs(filepath.Dir(*configFile)) + fmt.Printf("%#v\n", err) cfg, err := config.LoadFile(*configFile, loader)
if err := logging.Setup(*color, *verbose); err != nil {
fmt.Printf("failed to setup logging: %s\n", err)
defer gomol.ShutdownLoggers()
@@ -136,6 +148,7 @@
cleanupList := cleanup.NewList()
st.CleanupList = cleanupList
st.ForceSequential = *forceSequential
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/path/path.go Wed Dec 27 01:06:03 2017 -0600
@@ -0,0 +1,97 @@
+ * Copyright 2016-2017 Gary Kramlich <grim@reaperworld.com> + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. +// Traverses check whether path traverses outside of root. Returns error if +func Traverses(root, path string) (string, error) { + if !filepath.IsAbs(path) { + path = filepath.Join(root, path) + realPath, err := filepath.EvalSymlinks(path) + if !strings.HasPrefix(realPath, root) { + return "", fmt.Errorf("'%s' is outside of the build context '%s'", path, root) +func TraversesNonExistent(root, path string) (string, error) { + // attempt to do a normal traversal + realPath, err := Traverses(root, path) + // if it failed with a os.PathError that means the path doesn't exist. + // so we will strip off the filename and walk up to see if we can still + if _, ok := err.(*os.PathError); ok { + // reset realPath as the original call to Traverses will return an + // empty string for it on error + // create a variable to hold the path that we've removed + // split the directory and file name, we only hit this on error, + // so we can't actually eval symlinks completely, so it's safe-ish + dir, file := filepath.Split(realPath) + dir = filepath.Dir(dir) + file = filepath.Base(dir) + // join the filename to the beginning for stripped as we're walking + // up the directory tree. + stripped = filepath.Join(file, stripped) + // call Traverses again + realPath, err = Traverses(root, dir) + // if there was no error, we're done + return filepath.Join(realPath, stripped), nil --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/path/path_test.go Wed Dec 27 01:06:03 2017 -0600
@@ -0,0 +1,64 @@
+ * Copyright 2016-2017 Gary Kramlich <grim@reaperworld.com> + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + "github.com/aphistic/sweet" + junit "github.com/aphistic/sweet-junit" + . "github.com/onsi/gomega" +func TestMain(m *testing.M) { + RegisterFailHandler(sweet.GomegaFail) + sweet.Run(m, func(s *sweet.S) { + s.RegisterPlugin(junit.NewPlugin()) + s.AddSuite(&pathSuite{}) +func (s *pathSuite) TestTraversed(t sweet.T) { + tests := map[string]bool{ + Expect(err).To(BeNil()) + wd = filepath.Join(wd, "foo") + for path, res := range tests { + _, err := Traverses(wd, path) + Expect(err).ToNot(BeNil()) + Expect(err).To(BeNil()) --- a/state/state.go Wed Nov 22 11:48:51 2017 -0600
+++ b/state/state.go Wed Dec 27 01:06:03 2017 -0600
@@ -33,6 +33,7 @@
const ExpansionLimit = 100
CleanupList *cleanup.List
Workspace workspace.Workspace
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/breakout.yml Wed Dec 27 01:06:03 2017 -0600
@@ -0,0 +1,28 @@
+ files: breakout.yml:qux/quux/free.yml --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/free.yml Wed Dec 27 01:06:03 2017 -0600
@@ -0,0 +1,20 @@
+ files: breakout.yml:free.yml