CEP 47 - Index timestamp in package record metadata
| Title | Index timestamp in package record metadata |
| Status | Accepted |
| Author(s) | Jannis Leidel |
| Created | Mar 4, 2026 |
| Updated | Jun 30, 2026 |
| Discussion | https://github.com/conda/ceps/pull/154 |
| Implementation | conda/conda#15759 |
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC2119 when, and only when, they appear in all capitals, as shown here.
Abstract
This CEP proposes adding an optional indexed_timestamp field to per-record metadata in repodata.json, as specified by CEP 36. Unlike the existing timestamp field (which records build time and is set by the package builder), indexed_timestamp is set by the channel server when an artifact first appears in the channel index, providing a trustworthy, server-controlled record of when the package became available to users.
Motivation
The conda ecosystem currently lacks a reliable, per-artifact indicator of when a package was made available on a channel. This gap has practical consequences for two emerging use cases: supply chain security and environment reproducibility.
The timestamp field is builder-controlled
CEP 34 defines timestamp in info/index.json as "Starting time of the package build", expressed as Unix time in milliseconds. This value propagates into repodata.json as specified by CEP 36. Because it is set by the build tool (conda-build, rattler-build), a package author or malicious actor can set it to any value.
channeldata.json is too coarse
CEP 38 specifies a timestamp field in channeldata.json, defined as the most recent timestamp value of all records for a package name, expressed as POSIX time in seconds. This is per-package-name, not per-artifact, and it is derived from builder-controlled package record timestamps rather than a server-controlled publication time. It cannot be used to determine when a specific version or build was published.
Ecosystem precedent
PyPI provides server-controlled upload_time and upload_time_iso_8601 fields per release file in its JSON API. These are set by the PyPI server at upload time and cannot be modified by package authors. This is the foundation for reliable tools like:
uv --exclude-newerandpip --exclude-newerfor reproducible resolution- Dependency cooldown features that delay installation of recently published packages as a supply chain security measure
Within the conda ecosystem, pixi already provides an exclude-newer option, but it currently relies on the builder-controlled timestamp field. The conda ecosystem would benefit from a server-controlled equivalent that cannot be set by the package author.
Channel models have different risk profiles
The value of a server-controlled index timestamp varies by channel model. Curated channels like Anaconda's defaults already have human review gates before packages reach users, similar to the Debian model. Community forges like conda-forge release at the speed of volunteer CI, where packages can go from merged PR to indexed artifact in minutes. Cooldown support is most critical for these faster-moving channels.
Use cases
-
Dependency cooldowns, where a configurable time window excludes packages published too recently from being installed, giving security vendors time to flag malicious packages. See conda/conda#15759 and William Woodruff's analysis of supply chain attack windows.
-
Reproducible environments, where resolving an environment as it would have looked at a specific point in time (similar to
uv --exclude-newer) uses the publication date rather than the build date. -
Auditing and forensics, where it is useful to know exactly when a given artifact appeared on a channel, independent of when it was built.
Specification
Package record metadata
CEP 36 defines the schema for entries in packages and packages.conda within repodata.json. This CEP adds one optional field to the package record metadata:
indexed_timestamp: int. Optional. Unix time in milliseconds when the artifact was added to the channel index. It MUST be set by the channel server or indexing tool, not by the build tool. If absent, clients MAY fall back totimestampfor time-based operations.
Channel server requirements
Channel servers and indexing tools (such as conda-index, quetz, or Anaconda's infrastructure) SHOULD set indexed_timestamp on each package record when the artifact is first indexed into the channel. The value MUST reflect the actual time the artifact became available in the channel, not the build time or any value from the artifact itself.
For artifacts that predate this CEP and lack an indexed_timestamp, channel servers MAY seed the value from the artifact's timestamp field or from another server-side signal such as file modification time. This is acceptable because the security benefits of indexed_timestamp (dependency cooldowns, exclude-newer) protect against the initial publication window, which has long closed for existing artifacts.
Once indexed_timestamp has been assigned for a package record, channel servers and indexing tools MUST preserve that value across subsequent indexing runs. They MUST NOT recompute it from file modification time, the artifact's timestamp, or any other fallback signal while an existing indexed_timestamp is present. Channel operators MAY correct demonstrably erroneous values as an intentional metadata correction.
For artifacts first indexed after this CEP is approved, the value MUST be set by the server at indexing time.
Timestamp validation
When indexing an artifact, channel servers SHOULD validate that the artifact's timestamp value is not in the future and that timestamp <= indexed_timestamp. Artifacts that violate these constraints SHOULD be rejected or flagged, as a future timestamp may indicate a misconfigured build or an attempt to manipulate time-based resolution.
Client behavior
Conda clients that implement time-based filtering (e.g., dependency cooldowns, exclude-newer) SHOULD prefer indexed_timestamp when present. If indexed_timestamp is absent, clients MAY fall back to timestamp with appropriate documentation that the value is builder-controlled. Clients MAY support global, channel-scoped, package-scoped, or other policy scopes, but each policy should apply the same per-record effective timestamp.
Rationale
Why per-record, not per-package
channeldata.json (CEP 38) already includes a timestamp, but at the package-name level and derived from package record timestamps. Different versions and builds of the same package are published at different times, so a per-record field is necessary for meaningful time-based filtering.
Why server-controlled
The timestamp field is builder-controlled and has known issues: it can be set to any value by the packager, and it reflects when the build started rather than when the package became available to users. A server-controlled field closes this gap.
Why indexed_timestamp, not upload_timestamp
The field measures when the artifact appeared in the channel index, not when it was uploaded. In many conda workflows, these are distinct events. For example, conda-forge uploads built artifacts to a staging channel (cf-staging), validates them, and only then copies them to the production conda-forge channel where they are indexed into repodata.json. The conda-index maintainer also describes this as tracking when a package was "first indexed" rather than when it was uploaded.
The name indexed_timestamp accurately reflects this, and fits naturally alongside the existing timestamp field in the package record schema.
Not subject to repodata patching
Because indexed_timestamp is a server-controlled fact about when an artifact entered the index, it SHOULD NOT be modified by repodata patching. Repodata patches are intended for correcting package-side metadata (such as dependency constraints), not for altering server-side provenance.
Why optional
Not all channel servers may be able to provide this field immediately. Making it optional allows gradual adoption while clients can fall back to timestamp.
Backwards compatibility
Adding an optional field to existing package records in packages and packages.conda is a purely additive change. CEP 36 states that additional keys "SHOULD NOT be present and SHOULD be ignored", meaning older clients will silently ignore indexed_timestamp. This does not require the repodata revision mechanism proposed in conda/ceps#146, though it is consistent with that proposal's philosophy of backwards-compatible evolution.
This follows the same pattern as the url field proposed in conda/ceps#151, which also adds an optional per-record field to the existing schema.
Why milliseconds
For consistency with the existing package record timestamp field defined by CEP 34, which uses Unix time in milliseconds. The channeldata.json timestamp defined by CEP 38 uses seconds, but indexed_timestamp belongs to package records in repodata.json and should follow the package record convention.
Future work
indexed_timestamp as specified is a server-controlled integer, which is a meaningful improvement over the builder-controlled timestamp field for the use cases this CEP targets, but it is still a single party's claim. A compromised channel server, a backup restore, or a re-indexing operation could silently change the value, and clients have no way to independently verify that the timestamp is authentic. Closing this gap is out of scope here, but two directions are worth noting for follow-up work:
-
A channel server could request a signed Timestamp Response (TSR) from a Trusted Third Party Timestamp Authority (TSA) at indexing time, as defined in RFC 3161, and store the TSR alongside
indexed_timestampin repodata. Public TSAs include Sigstore'stimestamp.sigstore.dev, FreeTSA, and DigiCert.The Sigstore bundle format already carries RFC 3161 timestamps in its
TimestampVerificationDatafield, so an attestation-aware indexer (see CEP 27) could combine the two signals without inventing a new envelope. -
Build tools such as
rattler-buildandconda-buildcould embed a TSA-signed timestamp over the package contents inside the artifact itself, providing a verifiable lower bound on when the package was built. This is orthogonal toindexed_timestampand would more naturally amend CEP 34.
Both directions would need their own CEPs to specify field names, encoding, verification semantics, and a migration story.
References
- CEP 27 - Standardizing a publish attestation for the conda ecosystem
- CEP 34 - Contents of conda packages
- CEP 36 - Package metadata files served by conda channels
- CEP 38 - Channel-wide metadata files served by conda channels
- conda/ceps#146 - A backwards-compatible repodata update strategy
- conda/ceps#151 - URL field for package records
- schemas.conda.org - repodata-record-1.schema.json, downstream schema to update with
indexed_timestamp - schemas.conda.org - common-1.schema.json
- PyPI JSON API - upload_time field
- RFC 3161 - Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP)
- Sigstore Bundle Format - TimestampVerificationData
- conda/conda#15759 - Exclude-newer tracking issue
- conda/conda#15761 - Exclude-newer implementation in conda, including global, package, and channel-scoped policy
- conda/conda-libmamba-solver#905 - Exclude-newer policy support for conda-libmamba-solver
- conda/conda-rattler-solver#49 - Exclude-newer policy support for conda-rattler-solver
- mamba-org/mamba#4228 - exclude_newer_timestamp support in libmamba
- William Woodruff - We should all be using dependency cooldowns
- Andrew Nesbitt - Package managers need to cool down
- Andrew Nesbitt - Package security defenses for AI agents
All CEPs are explicitly CC0 1.0 Universal.