Skip to content

Commit 4820548

Browse files
waleedlatif1claude
andcommitted
fix(supply-chain): attest every pullable tag, and correct docs from review
Review found that the attestation covered only the multi-arch index while create-ghcr-manifests also publishes single-arch tags (:version-amd64, :latest-arm64, and so on) that resolve to different digests. Those were pullable but unsigned, so verification failed for anyone who pinned one. The matrix now attests all three subjects per image — the index and both per-arch manifests. That also makes the SBOMs truthful: the amd64 and arm64 images contain different packages, and one SBOM on the index cannot describe both. create-release now requires attest-images, so a release can no longer advertise images whose signing failed. The helm workflow's path filter now includes the inventory generator, which could previously change without the check that validates it ever running. The inventory header named global.imageRegistry alone as the redirect. That only rewrites the simstudioai/* images; third-party ones also need global.useRegistryForAllImages, and the NVIDIA device plugin is hardcoded in a template and cannot be redirected by any values key at all. Docs corrections from the same round: - The copilot workloads cannot be covered by networkPolicy.egress — it is only rendered into policies the chart creates, and it creates none for them. - Data drains and retention cleanup need Trigger.dev. Without it the work is claimed and enqueued to the database, but nothing drains that job type. - MIGRATION_DATABASE_URL has no chart values key: the migrations init container builds DATABASE_URL itself and reads only the database Secret. - Finished CronJob Jobs are deleted after ten minutes by ttlSecondsAfterFinished, so the history limits do not mean what the page implied. - lock_timeout is 5s for ordinary DDL, but a migration can set it to 0 for statements like CREATE INDEX CONCURRENTLY. - Losing Redis interrupts in-flight execution streams and cancellation, which "drops live updates and nothing else" understated. - NEXT_PUBLIC_BRAND_BACKGROUND_COLOR sets no background; it only tells Sim whether the brand ground is dark. - Pi uses its own image, not the Function base. - doctor skips the Postgres, migration, and Redis probes on the production Compose layout, and does not shape-check optional secrets. - The Daytona snapshot build needs the E2B parity manifest to exist first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt
1 parent 010f3e1 commit 4820548

16 files changed

Lines changed: 78 additions & 28 deletions

File tree

.github/workflows/ci.yml

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -620,10 +620,17 @@ jobs:
620620
# Attaching attestations here instead leaves the index itself untouched — they
621621
# are stored as separate referrer manifests that point at it.
622622
#
623-
# The subject is the sha index digest. `imagetools create` builds the version
624-
# and latest indexes from the same two per-arch manifests in the same order,
625-
# so all three tags resolve to one digest and a single attestation covers
626-
# every tag a customer can pull.
623+
# Every pullable tag is covered. `create-ghcr-manifests` publishes both
624+
# multi-arch tags (`:sha`, `:version`, `:latest`) and single-arch ones
625+
# (`:version-amd64`, `:latest-arm64`, …), and those resolve to different
626+
# digests — an index digest and the two per-arch manifest digests. The matrix
627+
# therefore attests all three subjects per image. `imagetools create` is
628+
# deterministic, so the version and latest indexes share the sha index's
629+
# digest and need no separate attestation.
630+
#
631+
# Per-arch subjects also give a truthful SBOM: the amd64 and arm64 images
632+
# contain different packages, and a single SBOM attached to the index cannot
633+
# describe both.
627634
attest-images:
628635
name: Attest Images
629636
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
@@ -648,6 +655,9 @@ jobs:
648655
- ghcr.io/simstudioai/realtime
649656
- ghcr.io/simstudioai/pii
650657
- ghcr.io/simstudioai/cron
658+
# The published tag suffixes, and so the distinct digests a customer can
659+
# pull. Empty is the multi-arch index.
660+
suffix: ['', '-amd64', '-arm64']
651661

652662
steps:
653663
- name: Login to GHCR
@@ -661,17 +671,18 @@ jobs:
661671
# whatever that tag points at when the step runs, which is not necessarily
662672
# what this run published.
663673
#
664-
# `{{json .Manifest}}` piped through jq, not `{{.Manifest.Digest}}` — the
665-
# latter resolves the index to the runner's own platform and prints that
666-
# manifest's digest instead, which would attest one architecture and leave
667-
# the index a customer actually pulls unsigned.
668-
- name: Resolve index digest
674+
# `{{json .Manifest}}` piped through jq, not a bare `{{.Manifest.Digest}}`:
675+
# buildx renders a format string consisting only of a `.Manifest` field as
676+
# the human-readable inspect block rather than the field value, so the bare
677+
# form does not produce a parseable digest.
678+
- name: Resolve digest
669679
id: digest
670680
run: |
671-
DIGEST="$(docker buildx imagetools inspect "${{ matrix.image }}:${{ github.sha }}" \
681+
REF="${{ matrix.image }}:${{ github.sha }}${{ matrix.suffix }}"
682+
DIGEST="$(docker buildx imagetools inspect "$REF" \
672683
--format '{{json .Manifest}}' | jq -r '.digest')"
673684
if [ -z "$DIGEST" ] || [ "$DIGEST" = "null" ]; then
674-
echo "::error::Could not resolve a digest for ${{ matrix.image }}:${{ github.sha }}"
685+
echo "::error::Could not resolve a digest for ${REF}"
675686
exit 1
676687
fi
677688
echo "value=${DIGEST}" >> "$GITHUB_OUTPUT"
@@ -754,11 +765,14 @@ jobs:
754765
name: Create GitHub Release
755766
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
756767
timeout-minutes: 10
757-
needs: [create-ghcr-manifests, detect-version]
758-
# Explicit results: see migrate's comment.
768+
needs: [create-ghcr-manifests, attest-images, detect-version]
769+
# Explicit results: see migrate's comment. attest-images is a gate, not just
770+
# an ordering edge — a release must not advertise images whose signature or
771+
# attestation failed to publish.
759772
if: >-
760773
!cancelled() &&
761774
needs.create-ghcr-manifests.result == 'success' &&
775+
needs.attest-images.result == 'success' &&
762776
needs.detect-version.result == 'success' &&
763777
needs.detect-version.outputs.is_release == 'true'
764778
permissions:

.github/workflows/helm.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@ on:
66
paths:
77
- 'helm/sim/**'
88
- '.github/workflows/helm.yml'
9+
# The image inventory is generated from the chart and checked here, so a
10+
# change to its generator has to run this workflow too.
11+
- 'scripts/generate-image-manifest.ts'
12+
- 'package.json'
913
pull_request:
1014
branches: [main, staging, dev]
1115
paths:
1216
- 'helm/sim/**'
1317
- '.github/workflows/helm.yml'
18+
# The image inventory is generated from the chart and checked here, so a
19+
# change to its generator has to run this workflow too.
20+
- 'scripts/generate-image-manifest.ts'
21+
- 'package.json'
1422

1523
concurrency:
1624
group: helm-${{ github.ref }}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,4 @@ curl -H "Authorization: Bearer $CRON_SECRET" \
260260
https://sim.example.com/api/cron/run-data-drains
261261
```
262262

263-
Each due drain is then fanned out as a `run-data-drain` background job, so the deployment also needs `TRIGGER_DEV_ENABLED` with a configured Trigger.dev project, or the default database-backed job queue, which both deployments use out of the box. See [background jobs](/platform/self-hosting/background-jobs).
263+
Each due drain is then fanned out as a `run-data-drain` background job, so the deployment also needs `TRIGGER_DEV_ENABLED` with a configured Trigger.dev project, and a configured Trigger.dev project. Without it the dispatcher still claims the work and enqueues it to the database, but nothing drains that queue for this job type, so the runs stay pending and never execute. See [background jobs](/platform/self-hosting/background-jobs).

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,14 +205,16 @@ curl -H "Authorization: Bearer $CRON_SECRET" \
205205
https://sim.example.com/api/logs/cleanup
206206
```
207207

208-
Each call fans the work out as background jobs, so the deployment also needs `TRIGGER_DEV_ENABLED` with a configured Trigger.dev project, or the default database-backed job queue, which both deployments use out of the box.
208+
Each call fans the work out as background jobs, so the deployment also needs `TRIGGER_DEV_ENABLED` with a configured Trigger.dev project, and a configured Trigger.dev project. Without it the dispatcher still claims the work and enqueues it to the database, but nothing drains that queue for this job type, so the runs stay pending and never execute.
209209

210210
### PII redaction
211211

212212
PII redaction runs against a standalone [Presidio](https://microsoft.github.io/presidio/) service. Deploy it (see `apps/pii`) and point Sim at it:
213213

214214
```bash
215215
# The Presidio service exposing /analyze and /anonymize
216+
# Helm: http://<release>-pii.<namespace>.svc.cluster.local:5001
217+
# Docker Compose: http://<pii service name>:5001
216218
PII_URL=http://sim-pii.simstudio.svc.cluster.local:5001
217219
```
218220

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ curl -H "Authorization: Bearer $CRON_SECRET" \
9292
https://sim.example.com/api/logs/cleanup
9393
```
9494

95-
Both features also fan work out as background jobs, so the deployment needs `TRIGGER_DEV_ENABLED` with a configured Trigger.dev project, or the default database-backed job queue, which both deployments use out of the box. See [background jobs](/platform/self-hosting/background-jobs).
95+
Both features also fan work out as background jobs, so the deployment needs `TRIGGER_DEV_ENABLED` with a configured Trigger.dev project, and a configured Trigger.dev project. Without it the dispatcher still claims the work and enqueues it to the database, but nothing drains that queue for this job type, so the runs stay pending and never execute. See [background jobs](/platform/self-hosting/background-jobs).
9696

9797
## Choose an organization model
9898

apps/docs/content/docs/platform/enterprise/verified-domains.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,4 @@ NEXT_PUBLIC_SSO_ENABLED=true
7373

7474
`ENTERPRISE_ENABLED` turns both on together. See the [self-hosted enterprise guide](/platform/enterprise/self-hosted).
7575

76-
Once enabled, verify domains from **Settings → Organization → Single sign-on**, in the **Verified domains** section above the identity provider configuration. The older `/settings/domains` link still resolves to the same page.
76+
Once enabled, verify domains from **Settings → Organization → Single sign-on**, in the **Verified domains** section above the identity provider configuration. The older `/workspace/<workspaceId>/settings/domains` path still resolves to the same page.

apps/docs/content/docs/platform/enterprise/whitelabeling.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ NEXT_PUBLIC_BRAND_PRIMARY_COLOR=#701ffc
133133
NEXT_PUBLIC_BRAND_PRIMARY_HOVER_COLOR=#6518e6
134134
NEXT_PUBLIC_BRAND_ACCENT_COLOR=#9d54ff
135135
NEXT_PUBLIC_BRAND_ACCENT_HOVER_COLOR=#8c3dff
136+
# Not a background: this only tells Sim whether your brand ground is dark,
137+
# which it uses to pick contrasting text.
136138
NEXT_PUBLIC_BRAND_BACKGROUND_COLOR=#0c0c0c
137139
```
138140

@@ -162,6 +164,8 @@ branding:
162164
files:
163165
custom.css: |
164166
.sidebar { background-color: #0c0c0c; }
167+
wordmark.svg: |
168+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 24"><text y="18">Acme</text></svg>
165169
# Binary files (PNG, JPG, ICO) base64-encoded
166170
# base64 -i logo.png | tr -d '\n'
167171
binaryFiles:

apps/docs/content/docs/platform/self-hosting/background-jobs.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ cronjobs:
7171
enabled: true
7272
```
7373
74-
Each job runs a small `curlimages/curl` pod that calls the app's **in-cluster Service** (not the ingress), with `concurrencyPolicy: Forbid` so a slow run never overlaps itself. The Service load-balances that single call to one replica, so a job never fans out across pods. The chart keeps three completed and one failed Job per CronJob for inspection; it sets no `backoffLimit`, so a failing job retries up to the Kubernetes default of six times before it is marked failed.
74+
Each job runs a small `curlimages/curl` pod that calls the app's **in-cluster Service** (not the ingress), with `concurrencyPolicy: Forbid` so a slow run never overlaps itself. The Service load-balances that single call to one replica, so a job never fans out across pods. The chart keeps three completed and one failed Job per CronJob, but `ttlSecondsAfterFinished` defaults to 600, so a finished Job is deleted ten minutes after it ends — inspect a failure promptly or collect the logs. It sets no `backoffLimit`, so a failing job retries up to the Kubernetes default of six times before it is marked failed.
7575

7676
Disable individual jobs you do not need — billing reconciliation is the obvious one on a self-hosted install:
7777

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,4 @@ Sim is self-contained for the core editor and execution engine. A few features r
113113
| **Agent blocks** | An API key for at least one model provider | Or a self-hosted OpenAI-compatible endpoint: Ollama, vLLM, LM Studio, or LiteLLM. |
114114
| **Chat module** | `COPILOT_API_KEY` from sim.ai | Set `NEXT_PUBLIC_CHAT_DISABLED=true` to hide the module instead. |
115115
| **Integrations** | Your own OAuth app per service | See [Integrations & OAuth](/platform/self-hosting/integrations-oauth). |
116-
| **Remote Function / Pi execution** | Optional E2B or Daytona key, plus a Function base image | Without one, JavaScript Function code that has no `import` or `require` still runs in the in-process isolated VM. Python, Shell, JavaScript with external imports, custom Function Sandboxes, and Pi require a configured remote provider **and** an immutable Function base image — the key alone is not enough. See [Sandboxes](/platform/self-hosting/sandboxes). |
116+
| **Remote Function / Pi execution** | Optional E2B or Daytona key, plus a Function base image | Without one, JavaScript Function code that has no `import` or `require` still runs in the in-process isolated VM. Python, Shell, JavaScript with external imports, and custom Function Sandboxes require a configured remote provider **and** an immutable Function base image — the key alone is not enough. Pi uses a separate image, pinned with `E2B_PI_TEMPLATE_ID` or `DAYTONA_PI_SNAPSHOT_ID`. See [Sandboxes](/platform/self-hosting/sandboxes). |

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Sim uses Redis as a message bus and shared cache. Both deployments ship it by de
2424
With more than one app or realtime replica and no `REDIS_URL`, users on different pods stop seeing each other's edits and live status updates. Beyond one startup log line noting single-pod mode, nothing is logged — the app looks healthy and quietly loses events. Treat Redis as mandatory the moment `replicaCount` exceeds 1.
2525
</Callout>
2626

27-
Everything Sim keeps in Redis is cache, coordination state, or an in-flight event — never committed data, which lives in PostgreSQL and object storage. Losing the instance drops live updates in progress and nothing else, so persistence is not required.
27+
Everything Sim keeps in Redis is cache, coordination state, or an in-flight event — never committed data, which lives in PostgreSQL and object storage. Persistence is therefore not required. Losing or restarting the instance is not free, though: in-flight execution streams and cancellation markers live here, so active runs stop streaming and a cancellation issued across the gap may not land. Completed work is unaffected.
2828

2929
## Configuration
3030

0 commit comments

Comments
 (0)