Skip to content

Commit e409f57

Browse files
waleedlatif1claude
andcommitted
fix(supply-chain): never attest a moving tag this run did not publish
The resolver added `latest` unconditionally. When create-ghcr-manifests' guard holds those tags back — it does that when the branch has moved on — they still point at an older build, so the job would have put this run's signature and provenance on an image it did not produce. A moving alias is now included only when it resolves to the index this run published; the per-arch aliases ship in the same guarded block, so one comparison gates all three. `digest_of` also aborted the step instead of reporting an absent tag: under `set -e` a failing inspect inside a command substitution takes the step down before the caller can skip it. Confirmed against a real registry, then fixed with an explicit `if !` rather than swallowing the status. Docs, all verified against source: - The General tab does not buffer everything: workspace scope, the default switch, and member changes apply immediately, so Discard does not undo them. - Both data-drain screenshots were declared at the wrong aspect ratio when I converted them to <Image>; they rendered distorted. - LM Studio needs VLLM_API_KEY when its API authentication is on. - The rollback snippet hardcoded a version that does not exist. - Docker Compose schedules the drain endpoint too, so the table implied Compose operators had to add their own. - Generating CRON_SECRET in a shell configures neither the app nor the caller. - SANDBOXES_ENABLED is required beyond the provider and Function base on a billing-free self-host, and Pi runs on its own image and fails closed without E2B_PI_TEMPLATE_ID or DAYTONA_PI_SNAPSHOT_ID. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt
1 parent 9791b80 commit e409f57

7 files changed

Lines changed: 61 additions & 25 deletions

File tree

.github/workflows/ci.yml

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -664,21 +664,51 @@ jobs:
664664
set -euo pipefail
665665
666666
IMAGES="simstudio migrations realtime pii cron"
667-
TAGS="${SHA} ${SHA}-amd64 ${SHA}-arm64 latest latest-amd64 latest-arm64"
668-
if [ "${IS_RELEASE}" = "true" ]; then
669-
TAGS="${TAGS} ${VERSION} ${VERSION}-amd64 ${VERSION}-arm64"
670-
fi
667+
668+
# Prints the digest, or nothing when the tag is not published. The
669+
# explicit `if !` matters: under `set -e` a failing inspect inside a
670+
# command substitution aborts the step before the caller can treat an
671+
# absent tag as "skip".
672+
digest_of() {
673+
local raw
674+
if ! raw="$(docker buildx imagetools inspect "$1" --format '{{json .Manifest}}' 2>/dev/null)"; then
675+
return 0
676+
fi
677+
printf '%s' "$raw" | jq -r '.digest // empty'
678+
}
671679
672680
: > /tmp/subjects.jsonl
673681
for name in $IMAGES; do
674682
image="ghcr.io/simstudioai/${name}"
683+
684+
# The sha tags are this run's own output and always exist.
685+
sha_index="$(digest_of "${image}:${SHA}")"
686+
if [ -z "$sha_index" ]; then
687+
echo "::error::No index published for ${image}:${SHA}"
688+
exit 1
689+
fi
690+
tags="${SHA} ${SHA}-amd64 ${SHA}-arm64"
691+
692+
# A moving alias is only ours if it resolves to the index this run
693+
# published. create-ghcr-manifests holds the latest tags back when
694+
# its monotonic guard sees a newer commit, and they then still point
695+
# at an older build — attesting those would put this run's signature
696+
# and provenance on an image it did not produce. The per-arch
697+
# aliases are published in the same guarded block as `latest`, so
698+
# that one comparison gates all three.
699+
if [ "$(digest_of "${image}:latest")" = "$sha_index" ]; then
700+
tags="${tags} latest latest-amd64 latest-arm64"
701+
else
702+
echo "Skipping latest tags for ${image}: they do not point at this run's index."
703+
fi
704+
705+
if [ "${IS_RELEASE}" = "true" ] && [ "$(digest_of "${image}:${VERSION}")" = "$sha_index" ]; then
706+
tags="${tags} ${VERSION} ${VERSION}-amd64 ${VERSION}-arm64"
707+
fi
708+
675709
seen=""
676-
for tag in $TAGS; do
677-
# A tag that this run did not publish (no release, or the latest
678-
# guard held it back) is expected to be missing — skip it rather
679-
# than fail the run.
680-
digest="$(docker buildx imagetools inspect "${image}:${tag}" \
681-
--format '{{json .Manifest}}' 2>/dev/null | jq -r '.digest // empty')"
710+
for tag in $tags; do
711+
digest="$(digest_of "${image}:${tag}")"
682712
[ -n "$digest" ] || continue
683713
case " $seen " in *" $digest "*) continue ;; esac
684714
seen="$seen $digest"

apps/docs/content/docs/platform/enterprise/access-control.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Click **+ Create** and enter a name (required) and optional description. A group
4545

4646
### 3. Configure permissions
4747

48-
Click **Details** on a group to open its configuration. It has four tabs — **General**, **Model Providers**, **Blocks**, and **Platform**. Changes are buffered until you save.
48+
Click **Details** on a group to open its configuration. It has four tabs — **General**, **Model Providers**, **Blocks**, and **Platform**. Name, description, and the permission settings are buffered until you save; the workspace scope, the default-group switch, and member changes are applied immediately, so **Discard** does not undo them.
4949

5050
Throughout the editor, a **checked** box means allowed. Clearing a checkbox is what applies a restriction.
5151

apps/docs/content/docs/platform/enterprise/data-drains.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ Drains are independent of [Data Retention](/platform/enterprise/data-retention)
1616

1717
Go to **Settings → Organization → Data drains** in your workspace, then click **New drain**.
1818

19-
<Image src="/static/enterprise/data-drains-list.png" alt="Data Drains settings page showing two configured drains — one exporting workflow logs to Amazon S3 daily, another exporting Chat conversations to an HTTPS webhook hourly" width={900} height={477} />
19+
<Image src="/static/enterprise/data-drains-list.png" alt="Data Drains settings page showing two configured drains — one exporting workflow logs to Amazon S3 daily, another exporting Chat conversations to an HTTPS webhook hourly" width={900} height={885} />
2020

21-
<Image src="/static/enterprise/data-drains-new.png" alt="New data drain dialog with fields for name, source, cadence, destination, and S3 credentials" width={900} height={566} />
21+
<Image src="/static/enterprise/data-drains-new.png" alt="New data drain dialog with fields for name, source, cadence, destination, and S3 credentials" width={900} height={491} />
2222

2323
Each drain has four pieces:
2424

apps/docs/content/docs/platform/enterprise/self-hosted.mdx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,17 @@ See [Sandboxes](/platform/self-hosting/sandboxes) for the provider credentials,
7272
Two enterprise features do their work from a cron-driven HTTP endpoint rather than from the app process. All four endpoints authenticate with a bearer token equal to `CRON_SECRET`, and both return `401` when `CRON_SECRET` is unset:
7373

7474
```bash
75-
CRON_SECRET=$(openssl rand -hex 32)
75+
openssl rand -hex 32
7676
```
7777

78-
| Feature | Endpoint | Suggested schedule | Scheduled by the Helm chart |
79-
|---------|----------|--------------------|-----------------------------|
80-
| Data drains | `GET /api/cron/run-data-drains` | Hourly | Yes |
81-
| Retention — logs | `GET /api/logs/cleanup` | Daily | **No** |
82-
| Retention — soft deletes | `GET /api/cron/cleanup-soft-deletes` | Daily | **No** |
83-
| Retention — Chat tasks | `GET /api/cron/cleanup-tasks` | Daily | **No** |
78+
Persist that value as `CRON_SECRET` on the app **and** on whatever calls these endpoints. Generating it in a shell configures neither, and a mismatch returns `401`, so the work silently never runs.
79+
80+
| Feature | Endpoint | Suggested schedule | Scheduled for you |
81+
|---------|----------|--------------------|-------------------|
82+
| Data drains | `GET /api/cron/run-data-drains` | Hourly | Yes — Helm and Docker Compose both call it |
83+
| Retention — logs | `GET /api/logs/cleanup` | Daily | **No** — schedule it yourself |
84+
| Retention — soft deletes | `GET /api/cron/cleanup-soft-deletes` | Daily | **No** — schedule it yourself |
85+
| Retention — Chat tasks | `GET /api/cron/cleanup-tasks` | Daily | **No** — schedule it yourself |
8486

8587
<Callout type="warn">
8688
The Helm chart schedules the data-drain dispatcher but **not** the three retention cleanup endpoints. Setting `DATA_RETENTION_ENABLED=true` on the chart alone deletes nothing — the windows are evaluated only when one of those endpoints is called. Add them to `cronjobs.jobs` yourself, or drive them from an external scheduler.

apps/docs/content/docs/platform/self-hosting/security.mdx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,10 @@ Workflows can execute user-authored JavaScript and Python. Know which sandbox yo
131131
| **Daytona** | `SANDBOX_PROVIDER=daytona`, `DAYTONA_API_KEY`, `DAYTONA_FUNCTION_SNAPSHOT_ID` | Remote sandbox per execution. |
132132

133133
Python, Shell, JavaScript with external imports, and tooling-dependent blocks
134-
require a remote sandbox provider. JavaScript without `import` or `require`
134+
require a remote sandbox provider. On a billing-free self-host the provider and
135+
Function base are not sufficient on their own: `SANDBOXES_ENABLED` grants the
136+
server-side entitlement, and without it Shell and the Sandboxes settings stay
137+
hidden even when the provider is configured correctly. JavaScript without `import` or `require`
135138
continues to run in the in-process isolate when no remote provider is configured.
136139

137140
<Callout type="warn">
@@ -149,7 +152,7 @@ Two browser-side variables project server state into the UI, and neither is deri
149152
| Variable | Effect when unset |
150153
|---|---|
151154
| `NEXT_PUBLIC_SANDBOXES_ENABLED` | The Function block's Shell language, its Sandbox picker, and **Settings → Sandboxes** stay hidden, even with a working provider. Python stays selectable and fails at execution |
152-
| `NEXT_PUBLIC_E2B_ENABLED` | The E2B-backed Pi block modes stay hidden; `sim-setup doctor` reports it as a mismatch against `E2B_ENABLED` |
155+
| `NEXT_PUBLIC_E2B_ENABLED` | The E2B-backed Pi block modes stay hidden; `sim-setup doctor` reports it as a mismatch against `E2B_ENABLED`. Revealing them is not enough to make them run — Pi executes on its own image, pinned with `E2B_PI_TEMPLATE_ID` or `DAYTONA_PI_SNAPSHOT_ID`, and fails closed without it |
153156

154157
Set the public values only **after** the server-side configuration above is complete — they are assertions about readiness, not switches, and the server-side check has its own conditions beyond them. See [Sandboxes](/platform/self-hosting/sandboxes) for the base-image build and promotion procedure. `npx sim-setup doctor` reports a mismatch in either direction.
155158

apps/docs/content/docs/platform/self-hosting/troubleshooting.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Two things this does not cover:
5959

6060
Sim identifies dynamically discovered LM Studio and vLLM models by their `vllm/` prefix. If the endpoint is unavailable and you manually enter the raw LM Studio model identifier, Sim treats that unknown identifier as an Ollama model.
6161

62-
1. Set `VLLM_BASE_URL`. No Compose file sets it for you — `docker-compose.ollama.yml` only sets `OLLAMA_URL` — so add it to the `simstudio` service's `environment:` block (or to `.env` and interpolate it), then recreate the service and confirm it landed:
62+
1. Set `VLLM_BASE_URL`, and `VLLM_API_KEY` too if you enabled LM Studio's API authentication — without the key, discovery requests omit the bearer token and fail. No Compose file sets either for you — `docker-compose.ollama.yml` only sets `OLLAMA_URL` — so add them to the `simstudio` service's `environment:` block (or to `.env` and interpolate them), then recreate the service and confirm it landed:
6363

6464
```bash
6565
docker compose -f docker-compose.ollama.yml up -d --force-recreate simstudio

apps/docs/content/docs/platform/self-hosting/upgrades.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,9 @@ helm rollback sim -n simstudio
250250
```
251251

252252
```bash
253-
# Docker Compose — edit .env so the pin persists, then restart
254-
echo "SIM_VERSION=v1.2.2" >> .env
253+
# Docker Compose — edit .env so the pin persists, then restart.
254+
# Substitute the previous release tag from the releases page.
255+
echo "SIM_VERSION=<previous-release-tag>" >> .env
255256
docker compose -f docker-compose.prod.yml up -d
256257
```
257258

0 commit comments

Comments
 (0)