CEP XXXX: Upload timestamp in package record metadata#154
CEP XXXX: Upload timestamp in package record metadata#154jezdez wants to merge 1 commit intoconda:mainfrom
Conversation
Propose adding an optional `upload_timestamp` field to per-record metadata in `repodata.json`. Unlike the existing `timestamp` field (build time, builder-controlled), `upload_timestamp` is set by the channel server when an artifact is indexed, providing a trustworthy publication time for supply chain security features (dependency cooldowns) and reproducible environment resolution. Requires: CEP 36
jaimergp
left a comment
There was a problem hiding this comment.
Thank you for bringing this up, I don't think we've discussed the exploitability of timestamp before, and this CEP brings up a few good points. Let's use this opportunity to also secure the acceptable values for timestamp.
| <tr><td> Author(s) </td><td> Jannis Leidel </td></tr> | ||
| <tr><td> Created </td><td> Mar 4, 2026 </td></tr> | ||
| <tr><td> Updated </td><td> Mar 4, 2026 </td></tr> | ||
| <tr><td> Discussion </td><td> NA </td></tr> |
There was a problem hiding this comment.
| <tr><td> Discussion </td><td> NA </td></tr> | |
| <tr><td> Discussion </td><td> https://github.com/conda/ceps/pull/154 </td></tr> |
| <tr><td> Updated </td><td> Mar 4, 2026 </td></tr> | ||
| <tr><td> Discussion </td><td> NA </td></tr> | ||
| <tr><td> Implementation </td><td> NA </td></tr> | ||
| <tr><td> Requires </td><td> CEP 36 </td></tr> |
There was a problem hiding this comment.
I need to think what to do about this field. I think it should only be for other PRs that are open at the same time, not necessarily the ones that have been merged already. I know I replaced these URLs for 29-38 but maybe it was not a good idea.
| [CEP 36](./cep-0036.md) defines the schema for entries in `packages` and `packages.conda` within `repodata.json`. This CEP adds one optional field to the package record metadata: | ||
|
|
||
| - `upload_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 to `timestamp` for time-based operations. | ||
|
|
There was a problem hiding this comment.
Seeing timestamp with this angle of security, I think we could add more restrictions in this CEP:
- Limit
timestampto dates in the past, never in the future. - Rule:
timestamp <= upload_timestamp - Force indexing tools to reject packages where
timestampdoes not obey these rules.
There was a problem hiding this comment.
In conda-index we don't know when a package was uploaded. We could keep track of when it was first seen or first indexed. But there are cases where we might lose that information, like if we decide to re-index a channel. A common workflow is to index a package, and then upload everything to the CDN.
What about the last-modified date from HTTP or the filesystem timestamp?
|
|
||
| ### Channel server requirements | ||
|
|
||
| Channel servers and indexing tools (such as `conda-index`, `quetz`, or Anaconda's infrastructure) SHOULD set `upload_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. |
There was a problem hiding this comment.
Given that the timestamp is going to be the time of indexation, maybe we should call it indexed_timestamp or indexed_at.
Alternatively, we could ask the indexers to peek at the file creation time on disk, but that's flaky and filesystem dependent so let's focus on "first time the package appeared on repodata".
|
|
||
| Channel servers and indexing tools (such as `conda-index`, `quetz`, or Anaconda's infrastructure) SHOULD set `upload_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. | ||
|
|
||
| If a channel re-indexes existing artifacts without a prior `upload_timestamp`, it MAY use the file modification time or another server-side signal as a best-effort approximation. It MUST NOT copy the value from the artifact's `timestamp` field. |
There was a problem hiding this comment.
This is going to be tricky to implement robustly, I fear. Some channels may not have the upload timestamp in their database, and their filesystem metadata may not be reliable (files were copied around, recovered from a backup, etc).
We could be a bit more permissive with using timestamp as long as the value is prior to the approval of this CEP, so we know they are not exploiting that weakness.
There was a problem hiding this comment.
Im also not sure about this. I think it makes more sense to initially set the field to the same value as timestamp. This would allow switching existing exclude-newer implementation to the new upload_timestamp without any interruption.
I was also considering leaving it empty for those records but that still poses a security risk when using exclude-newer.
|
|
||
| Conda clients that implement time-based filtering (e.g., dependency cooldowns, exclude-newer) SHOULD prefer `upload_timestamp` when present. If `upload_timestamp` is absent, clients MAY fall back to `timestamp` with appropriate documentation that the value is builder-controlled. | ||
|
|
||
| ### Schema |
There was a problem hiding this comment.
We haven't done this in other CEPs, so I have some reservations about introducing this precedent. I think we can just update the relevant schemas and their versions as required by CEP specifications, as we would do with the aggregated docs in conda.org. So in a way, schemas are "downstream" from the CEP.
| } | ||
| ``` | ||
|
|
||
| Note that `patchable` is `false`: unlike `timestamp`, this field SHOULD NOT be modified by repodata patching, since it represents a server-side fact. |
There was a problem hiding this comment.
Prior CEPs didn't discuss whether a repodata field is patchable or not. I think it can be inferred from whether they are server-side or package-side, but maybe that's a matter for a CEP. I did leave "the door open" saying that modifications are allowed, but without scoping patches in.
| - `uv --exclude-newer` and `pip --exclude-newer` for reproducible resolution | ||
| - Dependency cooldown features that delay installation of recently published packages as a supply chain security measure | ||
|
|
||
| The conda ecosystem would benefit from an equivalent signal. |
There was a problem hiding this comment.
pixi also has this feature: https://pixi.prefix.dev/latest/reference/pixi_manifest/#exclude-newer-optional
i expect this is currently using the timestamp
There was a problem hiding this comment.
It currently does, and it's a security risk. If this CEP were implemented, we would immediately change that.
|
|
||
| ### Use cases | ||
|
|
||
| 1. **Dependency cooldowns**: A configurable time window that 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](https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns) of supply chain attack windows. |
There was a problem hiding this comment.
| 1. **Dependency cooldowns**: A configurable time window that 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](https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns) of supply chain attack windows. | |
| 1. **Dependency cooldowns**: A configurable time window that excludes packages published too recently from being installed, giving security vendors time to flag malicious packages. See [conda/conda#15759](https://github.com/conda/conda/issues/15759) and [William Woodruff's analysis](https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns) of supply chain attack windows. |
|
|
||
| ### Why milliseconds | ||
|
|
||
| For consistency with the existing `timestamp` field (CEP 34) and `channeldata.json` timestamp (CEP 38), both of which use Unix time in milliseconds. |
There was a problem hiding this comment.
Good to be all milliseconds all the time now, instead of maybe-seconds and maybe-milliseconds.
|
@jezdez How can we move this forward? Anything I can do to help? |
|
@jezdez Gentle ping! I'd like to put this to a vote ASAP but there are still a few open questions. |
|
@baszalmstra Apologies, this dropped off my todo list in the past weeks, I'll take a look! |
Checklist for submitter
cep-0000.mdnamedcep-XXXX.mdin the root level.Checklist for CEP approvals
${greatest-number-in-main} + 1.cep-XXXX.mdfile has been renamed accordingly.# CEP XXXX -header has been edited accordingly.pre-commitchecks are passing.