diff --git a/act/container/docker_run.go b/act/container/docker_run.go index 5b3f7814..04ea67dd 100644 --- a/act/container/docker_run.go +++ b/act/container/docker_run.go @@ -1158,9 +1158,9 @@ func parseOptionsHostConfig(options string) (*container.HostConfig, error) { // sanitizeConfig remove the invalid configurations from `config` and `hostConfig` func (cr *containerReference) sanitizeConfig(ctx context.Context, config *container.Config, hostConfig *container.HostConfig) (*container.Config, *container.HostConfig) { logger := common.Logger(ctx) + matcher := newValidVolumeMatcher(ctx, cr.input.ValidVolumes) if len(cr.input.ValidVolumes) > 0 { - matcher := newValidVolumeMatcher(ctx, cr.input.ValidVolumes) // sanitize binds sanitizedBinds := make([]string, 0, len(hostConfig.Binds)) for _, bind := range hostConfig.Binds { @@ -1181,26 +1181,22 @@ func (cr *containerReference) sanitizeConfig(ctx context.Context, config *contai } } hostConfig.Binds = sanitizedBinds - // sanitize mounts - sanitizedMounts := make([]mount.Mount, 0, len(hostConfig.Mounts)) - for _, mt := range hostConfig.Mounts { - if matcher.isValid(mt.Source, mt.Type) { - sanitizedMounts = append(sanitizedMounts, mt) - } else { - logger.Warnf("[%s] is not a valid volume, will be ignored", mt.Source) - } - } - hostConfig.Mounts = sanitizedMounts } else { for _, bind := range hostConfig.Binds { logger.Warnf("[%s] is not a valid volume, will be ignored", bind) } - for _, mt := range hostConfig.Mounts { + hostConfig.Binds = []string{} + } + // sanitize mounts + sanitizedMounts := make([]mount.Mount, 0, len(hostConfig.Mounts)) + for _, mt := range hostConfig.Mounts { + if matcher.isValid(mt.Source, mt.Type) { + sanitizedMounts = append(sanitizedMounts, mt) + } else { logger.Warnf("[%s] is not a valid volume, will be ignored", mt.Source) } - hostConfig.Binds = []string{} - hostConfig.Mounts = []mount.Mount{} } + hostConfig.Mounts = sanitizedMounts return config, hostConfig } @@ -1284,6 +1280,9 @@ func (m validVolumeMatcher) isValid(source string, sourceType mount.Type) bool { if m.allowAll { return true } + if sourceType == mount.TypeTmpfs && source == "" { // backed by memory, exposes nothing from the host, same as --tmpfs + return true + } if isHostVolumeSource(source, sourceType) { normalized, err := normalizeHostVolumePath(source) if err != nil { diff --git a/act/container/docker_run_test.go b/act/container/docker_run_test.go index c7aeb751..70c40ed0 100644 --- a/act/container/docker_run_test.go +++ b/act/container/docker_run_test.go @@ -762,6 +762,22 @@ func TestCheckVolumesRejectsEscapingHostPaths(t *testing.T) { assert.Empty(t, hostConf.Binds) } +func TestSanitizeConfigKeepsTmpfsMounts(t *testing.T) { + hostConfig, _ := mergeOptions(t, "", "--mount type=tmpfs,dst=/var/lib/mysql", false) + require.Len(t, hostConfig.Mounts, 1) + + for _, validVolumes := range [][]string{nil, {"data"}} { + logger, hook := test.NewNullLogger() + _, sanitized := (&containerReference{input: &NewContainerInput{ValidVolumes: validVolumes}}).sanitizeConfig(common.WithLogger(context.Background(), logger), &container.Config{}, &container.HostConfig{Mounts: []mount.Mount{ + hostConfig.Mounts[0], + {Type: mount.TypeVolume, Target: "/anonymous"}, + {Type: mount.TypeTmpfs, Source: "/etc", Target: "/etc-copy"}, + }}) + assert.Equal(t, hostConfig.Mounts, sanitized.Mounts) + assert.Len(t, hook.AllEntries(), 2) + } +} + func TestContainerInfoFromInspect(t *testing.T) { t.Run("reports no healthcheck when the image declares none", func(t *testing.T) { info := containerInfoFromInspect(container.InspectResponse{ diff --git a/internal/pkg/config/config.example.yaml b/internal/pkg/config/config.example.yaml index 89f8b78a..fb33dc2c 100644 --- a/internal/pkg/config/config.example.yaml +++ b/internal/pkg/config/config.example.yaml @@ -237,14 +237,13 @@ container: # If it's empty, /workspace will be used. # Purely numeric subdirectories under this path are reserved for task workspaces and may be removed by idle cleanup. #workdir_parent: - # Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/bmatcuk/doublestar#patterns - # `*` matches within a single path segment, `**` also matches across path separators. - # You can specify multiple volumes. If the sequence is empty, no volumes can be mounted. - # For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to: + # Volumes and bind mounts containers may mount, as globs (https://github.com/bmatcuk/doublestar#patterns). + # `*` matches within a path segment, `**` across segments. Empty allows none. tmpfs mounts are always allowed. + # For example, to allow the `data` volume and all json files in `/src`: # valid_volumes: # - data # - /src/*.json - # If you want to allow any volume, please use the following configuration: + # To allow any volume: # valid_volumes: # - '**' #valid_volumes: []