Skip to content

--exclude is not honored for paths reached via a symlink target #5232

Description

@spiffcs

What happened:
A directory excluded with --exclude is still indexed and cataloged when a symlink elsewhere points into it.

Exclusions are matched per-path against the paths being indexed and do not consider symlink resolution.

Ex: Matching /boot hits only the /boot directory entry itself.

Indexing can travel to /boot/grub2/grub.cfg via symlink on an un excluded path without ever visiting /boot.

Steps to reproduce the issue:

mkdir -p /tmp/repro/boot/grub2 /tmp/repro/etc
echo menuentry > /tmp/repro/boot/grub2/grub.cfg
ln -s ../boot/grub2/grub.cfg /tmp/repro/etc/grub2.cfg

syft scan dir:/tmp/repro --exclude ./boot -o json | grep grub

Anything else we need to know?:

Quick test in syft/source/directorysource/ can show the failure:

func Test_excludeVsSymlinkTarget(t *testing.T) {
      root, err := filepath.EvalSymlinks(t.TempDir())
      require.NoError(t, err)

      require.NoError(t, os.MkdirAll(filepath.Join(root, "boot", "grub2"), 0o755))
      require.NoError(t, os.WriteFile(filepath.Join(root, "boot", "grub2", "grub.cfg"), []byte("menuentry"), 0o644))
      require.NoError(t, os.MkdirAll(filepath.Join(root, "etc"), 0o755))
      require.NoError(t, os.Symlink(filepath.Join("..", "boot", "grub2", "grub.cfg"), filepath.Join(root, "etc", "grub2.cfg")))

      visitors, err := GetDirectoryExclusionFunctions(root, []string{"./boot"})
      require.NoError(t, err)

      res, err := fileresolver.NewFromDirectory(root, root, visitors...)
      require.NoError(t, err)

      var indexed []string
      for l := range res.AllLocations(context.Background()) {
              if strings.Contains(l.RealPath, "/boot/") {
                      indexed = append(indexed, l.RealPath)
              }
      }
      require.Empty(t, indexed, "excluded paths were indexed anyway")
}

This was split out of #3258, which reported this alongside a fatal crash on the same setup.

The crash was a separate defect and was fixed by #5170. This half was not.

I think this is a design question rather than a one-line fix and should be discussed by the team.

This behavior can be seen in RHEL-family hosts, where /etc/grub2.cfg and /etc/grub2-efi.cfg are symlinks into /boot/grub2.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions