Skip to content

reject broken symlinks instead of skipping them as cycles - #681

Merged
mihaimaruseac merged 1 commit into
sigstore:mainfrom
Support-itecz:broken-symlink-reject
Oct 14, 2026
Merged

mihaimaruseac merged 1 commit into
sigstore:mainfrom
Support-itecz:broken-symlink-reject

Conversation

@Support-itecz

Copy link
Copy Markdown
Contributor

Summary

check_symlink() (added in #680) treats every failure of path.resolve(strict=True) as a symlink cycle: it logs "Symlink cycle detected" and both serializers skip the entry. A dangling symlink fails strict resolution too, so with allow_symlinks enabled it is silently left out of the manifest when signing, and one added to a signed model afterwards passes verification unreported, while an added regular file fails with "Extra files found in model". Since #667 the verifier applies the allow_symlinks policy recorded in the signature, so any model signed with symlinks allowed verifies this way regardless of the caller's flag. The link can name a target that only appears after verification (under /tmp, a cache path), at which point a loader walking the verified directory reads content nobody signed. Before #680 a broken symlink failed closed with a ValueError from check_file_or_directory.

Narrow the cycle handling to actual loops (RuntimeError on Python 3.12 and earlier, OSError with ELOOP on 3.13 and later) and raise ValueError for a symlink that cannot be resolved, at both signing and verification. To reproduce: sign a directory with set_allow_symlinks(True), add (model / "x").symlink_to(model / "missing"), verify. Before this change verification succeeds; with it, it fails the same way an added regular file does.

Checklist
  • All commits are signed-off, using DCO
  • All new code has docstrings and type annotations
  • All new code is covered by tests. Aim for at least 90% coverage. CI is configured to highlight lines not covered by tests.
  • Public facing changes are paired with documentation changes
  • Release note has been added to CHANGELOG.md if needed

check_symlink() treats every failure of resolve(strict=True) as a
symlink cycle and skips the entry with a warning. A dangling symlink
also fails strict resolution, so with allow_symlinks enabled it is
silently left out of the manifest when signing, and one added to a
signed model afterwards passes verification unreported, while an added
regular file fails with "Extra files found". The recorded
allow_symlinks policy is applied automatically when verifying, so this
holds for the plain CLI verify flow too.

Narrow the cycle handling to actual loops (RuntimeError on Python 3.12
and earlier, OSError with ELOOP on 3.13 and later) and raise ValueError
for a symlink that cannot be resolved, restoring the fail-closed
behavior that existed before check_symlink was introduced.

Signed-off-by: Itecz Solution <support@itecz.au>
@Support-itecz
Support-itecz requested review from a team as code owners October 14, 2026 07:58
@mihaimaruseac
mihaimaruseac merged commit f3b25be into sigstore:main Oct 14, 2026
51 of 53 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants