-
Notifications
You must be signed in to change notification settings - Fork 13
Description
We recently received this vulnerability report from François Proulx @fproulx-boostsecurity (VP of Security Research, BoostSecurity.io).
We're making it public here because:
- As far as we know no one is using this tool in production.
- People will be no worse off than they were without this tool (e.g. this doesn't make anyone more vulnerable).
- It will be much easier to discuss and motivate fixes.
What follows if Francois' report...
Vulnerability Summary
The current slsa-source-poc implementation is vulnerable to a Time-of-Check to Time-of-Use (TOCTOU) race condition that allows an attacker to bypass required security controls and obtain a falsified SLSA source attestation.
The vulnerability stems from a flawed assumption about the reliability of the updated_at timestamp in GitHub's Ruleset API. The implementation uses this timestamp to determine if a security control was active when a commit was pushed. However, the parameters within a given ruleset (such as the number of required reviewers) can be tampered with by a repository admin without any change to the ruleset's updated_at timestamp.
This allows a malicious repository administrator (or an attacker in possession of a sufficiently permissioned PAT) to temporarily disable a security control, push a malicious or un-reviewed commit, and then re-enable the control. The sourcetool will later check the updated_at timestamp, in an attempt to detect a policy change, but will incorrectly attest that the commit was part of a push which complied with the security policy.
Attack Scenario
- Initial State: A repository is configured with a repository ruleset that requires at least one approving review for all pull requests (
required_approving_review_count: 1). Theupdated_attimestamp for this ruleset is2025-01-01T12:00:00Z. - Tamper with Required Approvals: An attacker with sufficient permissions modifies the ruleset via the GitHub API (or the Web UI), changing the
required_approving_review_countfrom1to0. Crucially, this modification does not change the ruleset'supdated_attimestamp, which remains2025-01-01T12:00:00Z. You can observe it by comparing the response of https://api.github.com/repos///rulesets/<ruleset_id> when playing with the parameters. - Push Malicious Commit: The attacker now pushes a commit to the protected branch without undergoing any review. The
sourcetool'scommitActivityfunction correctly records the push time as2025-06-27T10:30:00Z. - Restore Control: The attacker immediately changes the ruleset back, restoring
required_approving_review_countto1. Theupdated_attimestamp still remains2025-01-01T12:00:00Z. - Falsified Attestation: The SLSA source provenance workflow executes.
- It fetches the current ruleset, which appears secure (reviews required).
- It reads the
updated_attimestamp as2025-01-01T12:00:00Zand uses this as theSincevalue for theREVIEW_ENFORCEDcontrol. - The implementation compares the commit's push time (
2025-06-27T10:30:00Z) with the control's supposed start time (2025-01-01T12:00:00Z). - Since the push time is after the
updated_attime, the implementation incorrectly concludes that the review control was active when the malicious commit was pushed. - It generates a signed VSA that wrongly attests to
SLSA_SOURCE_LEVEL_3, giving a false sense of security for the compromised commit.
Vulnerable Code
The flaw is systemic across all functions that determine a control's start time by trusting the ruleset.UpdatedAt.Time. The primary example is computeReviewControl in sourcetool/pkg/ghcontrol/checklevel.go.
// File: sourcetool/pkg/ghcontrol/checklevel.go
func (ghc *GitHubConnection) computeReviewControl(ctx context.Context, rules []*github.PullRequestBranchRule) (*slsa.Control, error) {
var oldestActive *github.RepositoryRuleset
for _, rule := range rules {
if ghc.ruleMeetsRequiresReview(rule) {
ruleset, _, err := ghc.Client().Repositories.GetRuleset(ctx, ghc.Owner(), ghc.Repo(), rule.RulesetID, false)
if err != nil {
return nil, err
}
if ruleset.Enforcement == EnforcementActive {
if oldestActive == nil || oldestActive.UpdatedAt.After(ruleset.UpdatedAt.Time) {
oldestActive = ruleset
}
}
}
}
if oldestActive != nil {
// VULNERABILITY: oldestActive.UpdatedAt.Time is NOT a reliable
// indicator of when the rule's *parameters* were last changed.
return &slsa.Control{Name: slsa.ReviewEnforced, Since: oldestActive.UpdatedAt.Time}, nil
}
return nil, nil
}
This same pattern of misplaced trust in UpdatedAt.Time is present in computeContinuityControl, computeTagHygieneControl, and computeRequiredChecks, making the entire verification model unreliable.
Impact
This vulnerability undermines the security guarantees of the SLSA source track proof-of-concept. It allows for the generation of fraudulent provenance.
Recommended Mitigation
Investigate whether the behavior of the updated_at timestamp is an intentional design decision or a bug in the GitHub Ruleset API. If it is a bug, it should be fixed to ensure the timestamp is reliably updated upon any change to a ruleset's parameters.
If the behavior is intentional, an alternative to the time-based policy should be considered. One such alternative is to replace the Since value in the policy with a fingerprint of the expected ruleset configuration. This fingerprint, a cryptographic hash of the canonical JSON representation of the ruleset object, would allow the verifier to confirm that the ruleset active at the time of the push exactly matches the one defined in the policy, preventing any undetected tampering.