Skip to content

fix(EC-1993): reject past --effective-time values by default#3424

Open
cuipinghuo wants to merge 1 commit into
conforma:mainfrom
cuipinghuo:forbid-setting-effective-time
Open

fix(EC-1993): reject past --effective-time values by default#3424
cuipinghuo wants to merge 1 commit into
conforma:mainfrom
cuipinghuo:forbid-setting-effective-time

Conversation

@cuipinghuo

@cuipinghuo cuipinghuo commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Backdating --effective-time suppresses time-gated security rules at both
the CLI level (severity demotion) and the Rego level (when_ns
propagation). Reject effective-time values more than 5 minutes in the
past unless --allow-past-effective-time is explicitly set.

Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com

What:

  • Add validation in parseEffectiveTime() to reject times >5min in the past
  • Add --allow-past-effective-time flag to validate image, validate input, and compare commands
  • 5-minute grace period to accommodate clock skew

Why:

  • Setting --effective-time to a past date demotes time-gated rule failures to warnings (CLI level)
    and shifts the time reference for all Rego rules via when_ns (Rego level), effectively bypassing
    security checks at the integration test gate where users control IntegrationTestScenario params

Tickets:

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 20, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:26 PM UTC · Completed 6:31 PM UTC
Commit: 87c4a29 · View workflow run →

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 705a50d8-cb18-4925-b043-ae0a1952749f

📥 Commits

Reviewing files that changed from the base of the PR and between b09a732 and 68ae46f.

📒 Files selected for processing (17)
  • cmd/compare/compare.go
  • cmd/validate/image.go
  • cmd/validate/image_test.go
  • cmd/validate/input.go
  • docs/modules/ROOT/pages/ec_compare.adoc
  • docs/modules/ROOT/pages/ec_validate_image.adoc
  • docs/modules/ROOT/pages/ec_validate_input.adoc
  • docs/modules/ROOT/pages/verify-conforma-konflux-ta.adoc
  • docs/modules/ROOT/pages/verify-enterprise-contract.adoc
  • features/task_validate_image.feature
  • features/validate_image.feature
  • internal/input/report_test.go
  • internal/input/validate_test.go
  • internal/policy/policy.go
  • internal/policy/policy_test.go
  • tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml
  • tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml
🚧 Files skipped from review as they are similar to previous changes (10)
  • tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml
  • cmd/validate/input.go
  • tasks/verify-conforma-konflux-ta/0.1/verify-conforma-konflux-ta.yaml
  • cmd/validate/image.go
  • internal/input/validate_test.go
  • internal/input/report_test.go
  • features/task_validate_image.feature
  • cmd/validate/image_test.go
  • internal/policy/policy_test.go
  • internal/policy/policy.go

📝 Walkthrough

Walkthrough

The change adds --allow-past-effective-time to comparison and validation commands, propagates the option through policy and Tekton task configuration, and rejects timestamps older than five minutes unless explicitly allowed. Tests and documentation cover parsing, constructors, command execution, and task scenarios.

Changes

Past effective-time allowance

Layer / File(s) Summary
Policy parsing and validation
internal/policy/policy.go, internal/policy/policy_test.go
Policy options and constructors propagate past-time allowance; parsing applies a five-minute grace period and tests cover accepted and rejected timestamps.
CLI flag and policy wiring
cmd/compare/compare.go, cmd/validate/image.go, cmd/validate/input.go
Commands register --allow-past-effective-time and pass its value into effective-time handling.
Tekton task propagation
tasks/verify-conforma-konflux-ta/..., tasks/verify-enterprise-contract/...
Task parameters and validate-step wiring pass the allowance to the Conforma CLI.
Compatibility coverage and documentation
internal/input/*_test.go, cmd/validate/image_test.go, features/*.feature, docs/modules/ROOT/pages/*
Constructor calls, command scenarios, task scenarios, and option documentation reflect the new behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TektonTask
  participant CLI
  participant Policy
  participant EffectiveTimeParser
  TektonTask->>CLI: pass --allow-past-effective-time
  CLI->>Policy: provide allowance and effective time
  Policy->>EffectiveTimeParser: parse and validate timestamp
  EffectiveTimeParser-->>Policy: accept or reject timestamp
  Policy-->>CLI: return policy or parsing result
Loading

Suggested reviewers: joejstuart

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the primary change to reject past effective-time values by default.
Description check ✅ Passed The description follows the template and covers what, why, and tickets with enough context for the change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [consumer-completeness] cmd/validate/vsa.go:405 — The validate vsa command constructs policy.Options{} with EffectiveTime but does not set AllowPastEffectiveTime (defaults to false). After this PR, any past effective time passed via --effective-time to ec validate vsa will be rejected, and there is no --allow-past-effective-time flag added to the VSA command to override this. This is an unintentional behavior change — the VSA command silently gains the restriction without providing the escape hatch.
    Remediation: Add --allow-past-effective-time flag to the VSA command (in cmd/validate/vsa.go) and wire it into the policy.Options struct, mirroring the pattern used in cmd/validate/image.go and cmd/validate/input.go.

  • [consumer-completeness] internal/validate/vsa/fallback.go:122 — The fallback validation context constructs policy.Options{} with config.EffectiveTime but does not set AllowPastEffectiveTime. The FallbackConfig struct does not include an AllowPastEffectiveTime field, so there is no way to propagate this setting to fallback validation.
    Remediation: Add AllowPastEffectiveTime to the FallbackConfig struct, propagate the value from the VSA command, and include it in the policy.Options at fallback.go:122.

  • [breaking-cli] internal/policy/policy.go:457 — The default behavior of --effective-time is changed in a backward-incompatible way. Past dates are now rejected unless --allow-past-effective-time is also passed. Any downstream CI/CD pipeline or script using ec validate image --effective-time <past-date> will start failing after upgrading the CLI binary. The Tekton tasks in this repo are updated, but external pipelines, automation scripts, or other repositories that shell out to the ec CLI with past effective times will break silently upon CLI upgrade.
    Remediation: Document this as a breaking change in release notes and consider bumping the minor/major version to signal the behavioral change to downstream consumers. At minimum, release notes must prominently call out that existing --effective-time invocations with past dates will now fail.

Medium

  • [security-bypass-tekton] tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml — The ALLOW_PAST_EFFECTIVE_TIME Tekton parameter is typed as string with default "false". Any pipeline operator who can configure pipeline parameters can set it to "true" to bypass the past-time rejection security control. The parameter description should include a security warning noting that enabling this bypasses time-gating protection. See also: [breaking-config] finding for this file.
    Remediation: Add a security note in the parameter description, e.g., "SECURITY: Enabling this allows bypassing time-gated policy rules."

  • [scope-creep] internal/policy/policy.go:437 — The PR introduces a new exported function policy.ParseEffectiveTime() that has the same name as the existing vsa.ParseEffectiveTime() in internal/validate/vsa/validator.go with a different signature and different behavior (the VSA version has no past-time rejection, no date-only format, and no "attestation" sentinel). Having two exported functions with the same name but different behavior creates developer confusion.
    Remediation: Consider documenting the relationship or filing a follow-up to unify the two functions.

  • [breaking-config] tasks/verify-enterprise-contract/0.1/verify-enterprise-contract.yaml:162 — The new ALLOW_PAST_EFFECTIVE_TIME parameter is added to the existing 0.1 version of both Tekton tasks rather than creating a 0.2 version. Combined with the behavioral change to --effective-time, existing pipelines using EFFECTIVE_TIME with past dates will break when they pick up the new task bundle. See also: [security-bypass-tekton] finding for this file.
    Remediation: Either introduce this as a 0.2 version of the task, or set ALLOW_PAST_EFFECTIVE_TIME default to "true" initially for backward compatibility and change the default to "false" in a later version bump.

Low

  • [edge-case] internal/policy/policy.go:183NewOfflinePolicy unconditionally passes allowPast=true to parseEffectiveTime. This is by design (offline mode has no attack surface from parameter injection), but worth documenting the security rationale. Verified: callers are test files and definition.go which always passes "now".

  • [naming-convention] internal/policy/policy.go:435 — The parameter name choosenTime perpetuates the existing misspelling (should be chosenTime) in the new exported ParseEffectiveTime function. While the typo exists in internal code, the new exported function is an opportunity to use the correct spelling.

  • [flag-declaration-style] cmd/compare/compare.go:77 — The --allow-past-effective-time flag description in compare.go omits "By default, dates more than 5 minutes in the past are rejected" which is present in image.go and input.go. Consistent help text across commands would be preferable.

  • [attestation-bypass] internal/policy/policy.go — When effectiveTime is "attestation", parseEffectiveTime returns nil and rejectPastTime is never called. AttestationTime() directly assigns p.effectiveTime without past-time validation. The attestation path relies on signature verification as its security boundary rather than temporal validation.

  • [grace-period] internal/policy/policy.go — The 5-minute grace period is a reasonable UX trade-off for clock skew. Worth documenting in the security model.

  • [struct-field-ordering] cmd/validate/image.go:639 — The new allowPastEffectiveTime field is placed after effectiveTime, breaking the alphabetical ordering convention used by the imageData struct.

  • [api-contract] cmd/compare/compare.go:92 — The "attestation" sentinel is accepted by --effective-time but not documented in the compare command's help text. Pre-existing condition, not introduced by this PR.

  • [scope] cmd/compare/compare.go:89 — The compare command refactoring replaces inline time parsing with ParseEffectiveTime. This is a behavioral change beyond the stated fix scope — previously compare accepted any past time silently.

Previous run

Review

Findings

Medium

  • [duplicate-validation-logic] cmd/compare/compare.go:100 — The compare command implements its own past-time rejection logic inline using time.Now().UTC() and a hardcoded 5-minute grace period, duplicating the centralized rejectPastTime() function in internal/policy/policy.go. The validate image and validate input commands correctly delegate to parseEffectiveTimerejectPastTime, but the compare command bypasses this path. This creates three maintenance risks: (1) the inline check uses the real wall clock instead of the testable now var, making the compare path untestable with mocked clocks; (2) the grace period is hardcoded rather than referencing pastEffectiveTimeGracePeriod, so the two could drift; (3) the compare command only handles RFC3339 format, while parseEffectiveTime also handles date-only format ("2006-01-02").
    Remediation: Refactor runCompare to use parseEffectiveTime (or at minimum rejectPastTime) from internal/policy instead of reimplementing the past-time check inline.

Labels: PR is a security fix (EC-1993, Red Team findings F7-1/F7-2) that modifies CLI command implementations

Previous run (2)

Review — comment

PR: fix(EC-1993): reject past --effective-time values by default

Summary

This PR adds a security mitigation to prevent users from backdating --effective-time to values more than 5 minutes in the past, which could suppress time-gated security rules at both the CLI level (severity demotion) and the Rego level (when_ns propagation). A new --allow-past-effective-time boolean flag provides an explicit override.

The core logic in internal/policy/policy.go is well-structured: a dedicated rejectPastTime function, a pastEffectiveTimeGracePeriod constant, and clean integration into both RFC3339 and date-only parse paths. The flag is consistently wired through all three commands (validate image, validate input, compare). Test coverage for the core behavior in TestParseEffectiveTimePastRejection is thorough and covers boundary conditions.

Findings

1. Duplicate effective-time validation in compare.go — medium / correctness

cmd/compare/compare.go implements its own past-time rejection logic (diff lines 100–105) independently from the shared rejectPastTime function and pastEffectiveTimeGracePeriod constant in internal/policy/policy.go:

// compare.go — inline, untestable
threshold := time.Now().UTC().Add(-5 * time.Minute)

// policy.go — shared constant, mockable clock
threshold := now().UTC().Add(-pastEffectiveTimeGracePeriod)

This creates two maintenance concerns:

  1. Duplicated threshold: The 5-minute window is a constant in policy.go but hardcoded inline in compare.go. Future threshold changes must be updated in two places, with no compiler or test to catch a drift.
  2. Untestable clock: compare.go calls time.Now() directly instead of the package-level now() function from the policy package, making it impossible to mock time in tests. There is no test coverage for this new validation path in compare.go.

Remediation: Consider refactoring compare.go to delegate effective-time parsing to a shared utility (e.g., exporting ParseAndValidateEffectiveTime from the policy package), or at minimum reference the existing constant and add test coverage for the compare command's validation path.

Positive observations

  • The rejectPastTime function in policy.go is clean, well-placed, and called from both the RFC3339 and date-only parse paths.
  • NewOfflinePolicy correctly passes allowPast: true, preserving backward compatibility for offline scenarios that may need arbitrary time evaluation.
  • All NewInputPolicy call sites (including internal/input/report_test.go, internal/input/validate_test.go, and internal/policy/policy_test.go) are updated consistently with the new parameter.
  • The Options struct uses the zero value (false) as the safe default for AllowPastEffectiveTime, so existing callers of NewPolicy get the restrictive behavior automatically.
  • TestParseEffectiveTimePastRejection provides comprehensive coverage: past dates rejected, grace period boundaries, allowPast=true override, future dates accepted, and "now"/"attestation" unaffected.
  • Existing tests that use past dates (e.g., TestNewPolicy, TestPreProcessPolicy) are correctly updated with AllowPastEffectiveTime: true.

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 20, 2026
@cuipinghuo
cuipinghuo force-pushed the forbid-setting-effective-time branch from 3c390e7 to b09a732 Compare July 21, 2026 13:22
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 21, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:22 PM UTC · Completed 1:43 PM UTC
Commit: 87c4a29 · View workflow run →

  Backdating --effective-time suppresses time-gated security rules at both
  the CLI level (severity demotion) and the Rego level (when_ns
  propagation). Reject effective-time values more than 5 minutes in the
  past unless --allow-past-effective-time is explicitly set.

  Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cuipinghuo
cuipinghuo force-pushed the forbid-setting-effective-time branch from b09a732 to 68ae46f Compare July 21, 2026 17:45
@github-actions github-actions Bot added size: XL and removed size: L labels Jul 21, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 21, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 5:46 PM UTC · Ended 6:08 PM UTC
Commit: 87c4a29 · View workflow run →

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.24528% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/policy/policy.go 69.56% 7 Missing ⚠️
cmd/compare/compare.go 20.00% 4 Missing ⚠️
Flag Coverage Δ
acceptance 54.31% <71.69%> (+0.05%) ⬆️
generative 16.77% <0.00%> (-0.03%) ⬇️
integration 27.99% <45.28%> (+0.02%) ⬆️
unit 71.78% <77.35%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
cmd/validate/image.go 91.31% <100.00%> (+0.10%) ⬆️
cmd/validate/input.go 95.45% <100.00%> (+0.07%) ⬆️
cmd/compare/compare.go 35.16% <20.00%> (+2.87%) ⬆️
internal/policy/policy.go 90.14% <69.56%> (-1.60%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the review comment for full details.

Comment thread internal/policy/policy.go
@@ -435,6 +457,9 @@ func parseEffectiveTime(choosenTime string) (*time.Time, error) {
if when, err := time.Parse(time.RFC3339, choosenTime); err == nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[high] breaking-cli

Default behavior of --effective-time changed in backward-incompatible way. Past dates now rejected unless --allow-past-effective-time is passed. Downstream CI/CD pipelines or scripts using --effective-time with past dates will break after CLI upgrade.

Suggested fix: Document as breaking change in release notes and consider bumping the minor/major version. At minimum, release notes must call out the breaking behavior.

Comment thread internal/policy/policy.go
// that case separately.
func ParseEffectiveTime(choosenTime string, allowPast bool) (time.Time, error) {
t, err := parseEffectiveTime(choosenTime, allowPast)
if err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] scope-creep

New exported ParseEffectiveTime() has the same name as existing vsa.ParseEffectiveTime() in internal/validate/vsa/validator.go with different signature and behavior, creating a confusing dual-function situation.

Suggested fix: Document the relationship or file follow-up to unify the two functions.

type: string
description: Run policy checks with the provided time.
default: "now"
- name: ALLOW_PAST_EFFECTIVE_TIME

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[medium] breaking-config

New parameter added to existing 0.1 version rather than creating 0.2. Combined with behavioral change, existing pipelines using EFFECTIVE_TIME with past dates will break when they pick up the new task bundle.

Suggested fix: Either introduce as 0.2 version, or set default to 'true' initially for backward compatibility.

Comment thread internal/policy/policy.go
Comment thread internal/policy/policy.go
// allowPast is false, times more than 5 minutes in the past are rejected.
// The "attestation" sentinel returns a zero time.Time — callers should handle
// that case separately.
func ParseEffectiveTime(choosenTime string, allowPast bool) (time.Time, error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] naming-convention

Parameter name choosenTime perpetuates existing misspelling in new exported ParseEffectiveTime function. Opportunity to use correct spelling chosenTime.

Comment thread cmd/compare/compare.go
@@ -74,6 +76,7 @@ Examples:
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] flag-declaration-style

Flag description for --allow-past-effective-time in compare.go omits 'By default, dates more than 5 minutes in the past are rejected' which is present in image.go and input.go.

Comment thread cmd/validate/image.go
@@ -633,6 +639,7 @@ type imageData struct {
certificateOIDCIssuer string

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] struct-field-ordering

New allowPastEffectiveTime field breaks alphabetical ordering convention in imageData struct.

Comment thread cmd/compare/compare.go
// For now, use current time as default for attestation time
effectiveTimeValue, err := policy.ParseEffectiveTime(effectiveTime, allowPastEffectiveTime)
if err != nil {
return err

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] api-contract

The 'attestation' sentinel is accepted by --effective-time but not documented in the compare command's help text. Pre-existing condition.

Comment thread cmd/compare/compare.go
@@ -84,20 +87,12 @@ Examples:

func runCompare(cmd *cobra.Command, args []string) error {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] scope

Compare command refactoring replaces inline time parsing with ParseEffectiveTime. Behavioral change beyond stated fix scope — previously compare accepted any past time silently.

@fullsend-ai-review fullsend-ai-review Bot removed the requires-manual-review Review requires human judgment label Jul 21, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:46 PM UTC · Completed 6:08 PM UTC
Commit: 87c4a29 · View workflow run →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant