Skip to content

CEP XXXX: Upload timestamp in package record metadata#154

Draft
jezdez wants to merge 1 commit intoconda:mainfrom
jezdez:cep-upload-timestamp
Draft

CEP XXXX: Upload timestamp in package record metadata#154
jezdez wants to merge 1 commit intoconda:mainfrom
jezdez:cep-upload-timestamp

Conversation

@jezdez
Copy link
Copy Markdown
Member

@jezdez jezdez commented Mar 5, 2026

Checklist for submitter

  • I am submitting a new CEP: Upload timestamp in package record metadata.
    • I am using the CEP template by creating a copy cep-0000.md named cep-XXXX.md in the root level.
  • I am submitting modifications to CEP XX.
  • Something else: (add your description here).

Checklist for CEP approvals

  • The vote period has ended and the vote has passed the necessary quorum and approval thresholds.
  • A new CEP number has been minted. Usually, this is ${greatest-number-in-main} + 1.
  • The cep-XXXX.md file has been renamed accordingly.
  • The # CEP XXXX - header has been edited accordingly.
  • The CEP status in the table has been changed to approved.
  • The last modification date in the table has been updated accordingly.
  • The table in the README has been updated with the new CEP entry.
  • The pre-commit checks are passing.

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
Copy link
Copy Markdown
Member

@jaimergp jaimergp left a comment

Choose a reason for hiding this comment

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

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>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
<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>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Seeing timestamp with this angle of security, I think we could add more restrictions in this CEP:

  • Limit timestamp to dates in the past, never in the future.
  • Rule: timestamp <= upload_timestamp
  • Force indexing tools to reject packages where timestamp does not obey these rules.

Copy link
Copy Markdown
Contributor

@dholth dholth Mar 11, 2026

Choose a reason for hiding this comment

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

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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

@pavelzw pavelzw left a comment

Choose a reason for hiding this comment

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

cool!

Comment on lines +38 to +41
- `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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

pixi also has this feature: https://pixi.prefix.dev/latest/reference/pixi_manifest/#exclude-newer-optional
i expect this is currently using the timestamp

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good to be all milliseconds all the time now, instead of maybe-seconds and maybe-milliseconds.

@baszalmstra
Copy link
Copy Markdown
Contributor

@jezdez How can we move this forward? Anything I can do to help?

@baszalmstra
Copy link
Copy Markdown
Contributor

@jezdez Gentle ping! I'd like to put this to a vote ASAP but there are still a few open questions.

@jezdez
Copy link
Copy Markdown
Member Author

jezdez commented Apr 9, 2026

@baszalmstra Apologies, this dropped off my todo list in the past weeks, I'll take a look!

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.

5 participants