Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
releaseTag:
description: Exact reviewed tag from sources.json (for example v2026.07.30.1)
description: Exact reviewed tag from sources.json (for example v2026.08.21.1)
required: true
createDraft:
description: Create the protected GitHub Draft Release after all audits pass
Expand Down Expand Up @@ -72,20 +72,20 @@ jobs:
fail-fast: false
matrix:
include:
- { target: darwin-arm64, component: python, runner: '["self-hosted","opensquilla-runtime","darwin-arm64"]' }
- { target: darwin-arm64, component: node, runner: '["self-hosted","opensquilla-runtime","darwin-arm64"]' }
- { target: darwin-x64, component: python, runner: '["self-hosted","opensquilla-runtime","darwin-x64"]' }
- { target: darwin-x64, component: node, runner: '["self-hosted","opensquilla-runtime","darwin-x64"]' }
- { target: linux-arm64, component: python, runner: '["self-hosted","opensquilla-runtime","linux-arm64","glibc"]' }
- { target: linux-arm64, component: node, runner: '["self-hosted","opensquilla-runtime","linux-arm64","glibc"]' }
- { target: linux-x64, component: python, runner: '["self-hosted","opensquilla-runtime","linux-x64","glibc"]' }
- { target: linux-x64, component: node, runner: '["self-hosted","opensquilla-runtime","linux-x64","glibc"]' }
- { target: windows-arm64, component: python, runner: '["self-hosted","opensquilla-runtime","windows-arm64"]' }
- { target: windows-arm64, component: node, runner: '["self-hosted","opensquilla-runtime","windows-arm64"]' }
- { target: windows-arm64, component: gitBash, runner: '["self-hosted","opensquilla-runtime","windows-arm64"]' }
- { target: windows-x64, component: python, runner: '["self-hosted","opensquilla-runtime","windows-x64"]' }
- { target: windows-x64, component: node, runner: '["self-hosted","opensquilla-runtime","windows-x64"]' }
- { target: windows-x64, component: gitBash, runner: '["self-hosted","opensquilla-runtime","windows-x64"]' }
- { target: darwin-arm64, component: python, runner: '["macos-15"]' }
- { target: darwin-arm64, component: node, runner: '["macos-15"]' }
- { target: darwin-x64, component: python, runner: '["macos-15-intel"]' }
- { target: darwin-x64, component: node, runner: '["macos-15-intel"]' }
- { target: linux-arm64, component: python, runner: '["ubuntu-24.04-arm"]' }
- { target: linux-arm64, component: node, runner: '["ubuntu-24.04-arm"]' }
- { target: linux-x64, component: python, runner: '["ubuntu-24.04"]' }
- { target: linux-x64, component: node, runner: '["ubuntu-24.04"]' }
- { target: windows-arm64, component: python, runner: '["windows-11-arm"]' }
- { target: windows-arm64, component: node, runner: '["windows-11-arm"]' }
- { target: windows-arm64, component: gitBash, runner: '["windows-11-arm"]' }
- { target: windows-x64, component: python, runner: '["windows-2025"]' }
- { target: windows-x64, component: node, runner: '["windows-2025"]' }
- { target: windows-x64, component: gitBash, runner: '["windows-2025"]' }
runs-on: ${{ fromJSON(matrix.runner) }}
timeout-minutes: 90
steps:
Expand Down
120 changes: 104 additions & 16 deletions .github/workflows/mirror-oss.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ jobs:
timeout-minutes: 180
environment: runtime-pack-oss
env:
OSS_BUCKET: ${{ vars.RUNTIME_PACK_OSS_BUCKET }}
OSS_ACCESS_KEY_ID: ${{ secrets.RUNTIME_PACK_OSS_ACCESS_KEY_ID }}
OSS_ACCESS_KEY_SECRET: ${{ secrets.RUNTIME_PACK_OSS_ACCESS_KEY_SECRET }}
OSS_BUCKET: opensquilla-releases
OSS_ACCESS_KEY_ID: ${{ secrets.ALIYUN_OSS_ACCESS_KEY_ID }}
OSS_ACCESS_KEY_SECRET: ${{ secrets.ALIYUN_OSS_ACCESS_KEY_SECRET }}
OSS_REGION: cn-beijing
OSS_ENDPOINT: ${{ vars.RUNTIME_PACK_OSS_ENDPOINT }}
OSS_ENDPOINT: https://oss-cn-beijing.aliyuncs.com
steps:
- name: Resolve and validate published tag
id: release
Expand Down Expand Up @@ -88,9 +88,8 @@ jobs:
shell: bash
run: |
set -euo pipefail
[[ -n "${OSS_BUCKET}" ]] || { echo 'Missing RUNTIME_PACK_OSS_BUCKET' >&2; exit 1; }
[[ -n "${OSS_ACCESS_KEY_ID}" ]] || { echo 'Missing RUNTIME_PACK_OSS_ACCESS_KEY_ID' >&2; exit 1; }
[[ -n "${OSS_ACCESS_KEY_SECRET}" ]] || { echo 'Missing RUNTIME_PACK_OSS_ACCESS_KEY_SECRET' >&2; exit 1; }
[[ -n "${OSS_ACCESS_KEY_ID}" ]] || { echo 'Missing ALIYUN_OSS_ACCESS_KEY_ID' >&2; exit 1; }
[[ -n "${OSS_ACCESS_KEY_SECRET}" ]] || { echo 'Missing ALIYUN_OSS_ACCESS_KEY_SECRET' >&2; exit 1; }
[[ "${OSS_BUCKET}" == "opensquilla-releases" ]] || {
echo 'Runtime Packs must mirror to the reviewed opensquilla-releases bucket.' >&2
exit 1
Expand Down Expand Up @@ -120,7 +119,7 @@ jobs:
echo "${OSSUTIL_SHA256} ${OSSUTIL_ZIP}" | sha256sum -c -
unzip -q "${OSSUTIL_ZIP}"
sudo install -m 0755 "ossutil-${OSSUTIL_VERSION}-linux-amd64/ossutil" /usr/local/bin/ossutil
- name: Require overwrite-safe bucket versioning state
- name: Require recovery-safe bucket versioning state
shell: bash
run: |
set -euo pipefail
Expand All @@ -144,11 +143,11 @@ jobs:
for child in node:
yield from statuses(child)

active = list(statuses(value))
if active:
active = {status.casefold() for status in statuses(value)}
if active != {"enabled"}:
raise SystemExit(
"OSS bucket versioning must be unconfigured because PutObject "
f"forbid-overwrite is ignored when versioning is active or suspended: {active}"
"The shared OSS bucket must have versioning enabled so every Runtime Pack "
f"write receives a recoverable version ID; observed states: {sorted(active)}"
)
PY
- name: Mirror only immutable version-scoped objects and read them back
Expand All @@ -157,7 +156,8 @@ jobs:
shell: bash
run: |
set -euo pipefail
mkdir -p readback
mkdir -p readback oss-head
: > oss-version-ids.tsv
object_exists() {
local object="$1"
local listing
Expand All @@ -177,12 +177,50 @@ jobs:
exit 1
}
}
record_version_id() {
local name="$1"
local key="runtime-packs/${RELEASE_TAG}/${name}"
local metadata="oss-head/${name}.json"
ossutil api head-object \
--bucket "${OSS_BUCKET}" \
--key "${key}" \
--endpoint "${OSS_ENDPOINT}" \
--output-format json > "${metadata}"
METADATA_PATH="${metadata}" python - <<'PY'
import json
import os
import re
from pathlib import Path

value = json.loads(Path(os.environ["METADATA_PATH"]).read_text(encoding="utf-8"))

def version_ids(node):
if isinstance(node, dict):
for key, child in node.items():
normalized = re.sub(r"[^a-z0-9]", "", key.casefold())
if normalized in {"versionid", "xossversionid"} and isinstance(child, str):
if child and child.casefold() != "null":
yield child
yield from version_ids(child)
elif isinstance(node, list):
for child in node:
yield from version_ids(child)

found = list(dict.fromkeys(version_ids(value)))
if len(found) != 1:
raise SystemExit(f"expected one non-null OSS Version ID, got {found}")
print(found[0])
PY
}
for source in release-dist/*; do
name="$(basename "${source}")"
object="oss://${OSS_BUCKET}/runtime-packs/${RELEASE_TAG}/${name}"
if object_exists "${object}"; then
verify_object "${source}" "${name}"
echo "Existing immutable object matches: ${object}"
version_id="$(record_version_id "${name}")"
printf '%s\t%s\t%s\n' "${name}" "runtime-packs/${RELEASE_TAG}/${name}" "${version_id}" \
>> oss-version-ids.tsv
echo "Existing version-scoped object matches: ${object}"
continue
else
status="$?"
Expand All @@ -198,11 +236,54 @@ jobs:
--endpoint "${OSS_ENDPOINT}"; then
if object_exists "${object}"; then
verify_object "${source}" "${name}"
continue
else
exit 1
fi
exit 1
fi
verify_object "${source}" "${name}"
version_id="$(record_version_id "${name}")"
printf '%s\t%s\t%s\n' "${name}" "runtime-packs/${RELEASE_TAG}/${name}" "${version_id}" \
>> oss-version-ids.tsv
done
python - <<'PY'
import json
from pathlib import Path

objects = {}
for line in Path("oss-version-ids.tsv").read_text(encoding="utf-8").splitlines():
name, key, version_id = line.split("\t")
if name in objects:
raise SystemExit(f"duplicate OSS Version ID entry: {name}")
objects[name] = {"key": key, "versionId": version_id}
expected = {path.name for path in Path("release-dist").iterdir() if path.is_file()}
if objects.keys() != expected:
raise SystemExit(
f"OSS Version ID set mismatch; missing={sorted(expected - objects.keys())}, "
f"extra={sorted(objects.keys() - expected)}"
)
Path("oss-version-ids.json").write_text(
json.dumps({"objects": objects}, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
PY
- name: Require anonymous client downloads to match the release bytes
env:
RELEASE_TAG: ${{ steps.release.outputs.tag }}
shell: bash
run: |
set -euo pipefail
mkdir public-readback
for source in release-dist/*; do
name="$(basename "${source}")"
url="https://${OSS_BUCKET}.oss-cn-beijing.aliyuncs.com/runtime-packs/${RELEASE_TAG}/${name}"
curl --fail --silent --show-error --location --retry 3 \
--output "public-readback/${name}" "${url}"
local_sha="$(sha256sum "${source}" | awk '{print $1}')"
public_sha="$(sha256sum "public-readback/${name}" | awk '{print $1}')"
[[ "${local_sha}" == "${public_sha}" ]] || {
echo "Anonymous OSS readback mismatch for ${name}" >&2
exit 1
}
done
- name: Verify GitHub and OSS inventories byte for byte
shell: bash
Expand Down Expand Up @@ -256,3 +337,10 @@ jobs:
diff -u \
<(cd release-dist && sha256sum -- * | sort) \
<(cd readback && sha256sum -- * | sort)
- name: Preserve exact OSS version IDs for audit
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: runtime-pack-oss-version-ids-${{ steps.release.outputs.tag }}
path: oss-version-ids.json
if-no-files-found: error
retention-days: 90
47 changes: 33 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ system and CPU architecture. Source archives, versions, and digests are pinned i
- Historical Runtime Pack digests must be unique, lowercase SHA-256 values and must
never repeat the current pack digest. The first release starts with empty histories.
- Git for Windows self-extracting archives are unpacked only inside trusted CI; the
OpenSquilla client never downloads or executes the SFX.
OpenSquilla client never downloads or executes the SFX. The native Windows build
also requires valid Authenticode signatures on the SFX and the probed Git/Bash
executables before any of them can enter a Runtime Pack.
- Every discovered upstream license/notice file is preserved. Explicit per-file,
count, and total-size bounds fail the build instead of silently truncating notices.
- Pack extraction rejects traversal, link escapes, special files, duplicate paths,
and expansion beyond declared limits. Safe internal upstream links are resolved inside
the reviewed archive namespace and materialized as regular files; links never ship
Expand All @@ -59,19 +63,34 @@ controls:
OSS.
- Protect the `runtime-pack-release` and `runtime-pack-oss` environments with required
reviewers. Only the former may create a Draft Release; it never publishes one.
- Register isolated organization runners with the exact labels declared in
`sources.json`. Every runner needs Python 3.12; Windows runners also need a trusted
7-Zip CLI. Do not attach these labels to general-purpose or fork-controlled runners.
- Create the `opensquilla-releases` bucket in `cn-beijing` with bucket versioning
unconfigured. OSS ignores `forbid-overwrite` when versioning is enabled or
suspended, so the workflow refuses either state.
- Give the mirror RAM identity only list/read/create access under
`runtime-packs/*`. It must not have `DeleteObject`, unrestricted overwrite, bucket
administration, or access to desktop update-channel paths. Keep the access key only
in the protected `runtime-pack-oss` environment.
- Set `RUNTIME_PACK_OSS_BUCKET=opensquilla-releases` and
`RUNTIME_PACK_OSS_ENDPOINT=https://oss-cn-beijing.aliyuncs.com`. The workflow rejects
other destinations and never writes a moving `latest` or `stable` alias.
- The reviewed matrix uses standard GitHub-hosted native runners declared in
`sources.json`: macOS arm64/x64, Linux arm64/x64, and Windows arm64/x64. This avoids
persistent organization runners and keeps every build on the target architecture.
Windows images must continue to provide the trusted 7-Zip and PowerShell tools used
by the Git Bash extraction and Authenticode gates.
- Reuse the reviewed `opensquilla-releases` bucket in `cn-beijing`, but isolate every
object under `runtime-packs/<release-tag>/`. Bucket versioning must remain enabled so
every write has a recoverable Version ID. OSS ignores `forbid-overwrite` on a
versioned bucket, so the workflow never relies on that header: an existing object is
accepted only when its downloaded SHA-256 already matches the GitHub Release byte.
- The workflow itself only lists, reads, and creates objects under `runtime-packs/*`;
it has no delete or desktop update-channel operation. Keep the reused access key
behind required reviewers in the protected `runtime-pack-oss` environment. When RAM
policy changes become available, replace it with a prefix-scoped identity rather
than expanding the existing credential further.
- Allow anonymous `GetObject` only for `runtime-packs/*`; do not grant anonymous
`ListObjects`, write, delete, or bucket-administration permissions. The mirror job
downloads every object again over the exact unsigned public client URL and compares
its SHA-256 before succeeding.
- Expose the existing encrypted `ALIYUN_OSS_ACCESS_KEY_ID` and
`ALIYUN_OSS_ACCESS_KEY_SECRET` to the protected `runtime-pack-oss` environment.
GitHub secrets are repository-scoped, so using the same values in this repository
still requires an environment or organization secret grant; the values are never
copied by a workflow. The bucket and Beijing endpoint are fixed in the workflow,
which never writes a moving `latest` or `stable` alias.
- Preserve the generated `oss-version-ids.json` workflow artifact for each mirror run.
It records the exact current Version ID of every verified object without changing
the immutable GitHub Release.

Before approving a Draft Release, reviewers must independently confirm every upstream
URL and SHA-256 pin, all fourteen native probe jobs, the exact asset inventory,
Expand Down
27 changes: 20 additions & 7 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,25 @@ pin, generated archive, catalog digest, or SBOM is wrong, maintainers must revok
draft or affected catalog and publish a new catalog version. Existing bytes must not
be replaced under the same tag.

GitHub immutable releases must be enabled before the first publication. The OSS
bucket must remain unversioned so `PutObject` with `forbid-overwrite=true` cannot be
silently converted into a new object version. The mirror identity must not have
delete, bucket-administration, or desktop update-channel permissions.
GitHub immutable releases must be enabled before the first publication. Runtime Packs
reuse the versioned `opensquilla-releases` bucket only under the isolated
`runtime-packs/<release-tag>/` prefix. Because OSS ignores `forbid-overwrite` when
versioning is enabled, the mirror treats that header as defense in depth only: it
refuses existing different bytes, verifies authenticated and anonymous downloads, and
records each non-null Version ID. The mirror workflow never writes a desktop release
or update-channel path. If the reused credential currently has broader rights, the
protected environment and required reviewers are compensating controls until a
prefix-scoped RAM identity can replace it; do not grant new delete or bucket-policy
permissions for Runtime Packs.

The client path requires anonymous `GetObject` only under `runtime-packs/*`. Anonymous
listing and every mutation remain forbidden. Mirroring is successful only after an
unsigned HTTPS download of every object matches the corresponding GitHub Release byte
for byte.

The GitHub Draft Release and the protected `runtime-pack-release` and
`runtime-pack-oss` environments require maintainer approval. OSS credentials must be
scoped to `runtime-packs/*` and must not have permission to modify desktop update
channels.
`runtime-pack-oss` environments require maintainer approval. Reusing an existing
credential is an operational compromise, not an integrity dependency: where IAM
changes are available, scope a separate RAM identity to `runtime-packs/*`. Regardless
of credential scope, clients reject any byte sequence whose catalog size or SHA-256
does not match and try the alternate GitHub/OSS source.
Loading
Loading