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
52 changes: 52 additions & 0 deletions .github/workflows/managed-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ on:
workflow_call:
pull_request:
paths:
- ".github/actions/ci-reviewed-npm-audit/**"
- ".github/workflows/managed-images.yaml"
- ".dockerignore"
- "Dockerfile"
- "agents/**"
- "ci/npm-audit-exceptions.json"
- "ci/reviewed-npm-audit.json"
- "nemoclaw/**"
- "nemoclaw-blueprint/**"
- "scripts/**"
Expand Down Expand Up @@ -49,6 +51,55 @@ env:
REGISTRY: ghcr.io

jobs:
pr-reviewed-npm-audit:
name: PR reviewed npm audit
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- name: Checkout commit under review
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha }}
path: candidate
persist-credentials: false

- name: Checkout trusted reviewed npm audit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.base.sha }}
path: .trusted-reviewed-npm-audit
persist-credentials: false
sparse-checkout: |
.github/actions/ci-reviewed-npm-audit
ci/npm-audit-exceptions.json
ci/reviewed-npm-audit.json
scripts/audit-reviewed-npm-graph.mts
scripts/lib/openclaw-npm-remediation.mts
scripts/lib/reviewed-npm-archive.mts
scripts/lib/reviewed-npm-audit.mts
sparse-checkout-cone-mode: false

- name: Verify exact audit source and target
shell: bash
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
CANDIDATE_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
[[ "$BASE_SHA" =~ ^[a-f0-9]{40}$ ]]
[[ "$CANDIDATE_SHA" =~ ^[a-f0-9]{40}$ ]]
test "$(git -C .trusted-reviewed-npm-audit rev-parse --verify HEAD)" = "$BASE_SHA"
test "$(git -C candidate rev-parse --verify HEAD)" = "$CANDIDATE_SHA"

- name: Audit exact PR production npm graphs
uses: ./.trusted-reviewed-npm-audit/.github/actions/ci-reviewed-npm-audit
with:
target-root: ${{ github.workspace }}/candidate
report-dir: artifacts/reviewed-npm-audit

pr-staging-qa-deep-code:
name: Staging QA base permission regression (Deep Agents Code)
if: github.event_name == 'pull_request'
Expand Down Expand Up @@ -269,6 +320,7 @@ jobs:
# External contributor PRs retain the local build/direct-startup lane after
# GitHub's maintainer workflow approval. Exact digest publication is limited
# to branches in NVIDIA/NemoClaw; fork jobs never log in or push.
needs: pr-reviewed-npm-audit
if: github.event_name == 'pull_request'
runs-on: ubuntu-24.04
timeout-minutes: 90
Expand Down
33 changes: 18 additions & 15 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -677,9 +677,8 @@
fi

# Anchor the corporate CA for build-time TLS too, not just runtime. The
# OpenClaw/mcporter reinstall path runs npm audit signatures, which fetches the
# sigstore TUF root over TLS; behind a TLS-intercepting corporate proxy that
# fetch needs the operator CA or it fails with SELF_SIGNED_CERT_IN_CHAIN. Node
# OpenClaw/mcporter reinstall path makes registry-backed npm requests; behind a
# TLS-intercepting corporate proxy those requests need the operator CA. Node
# ignores a missing file, so this is a no-op when no CA was baked; at runtime
# nemoclaw-start overrides it with the merged OpenShell + corporate bundle.
ENV NODE_EXTRA_CA_CERTS=/usr/local/share/nemoclaw/corporate-ca.pem
Expand Down Expand Up @@ -805,15 +804,20 @@
RUN command -v codex-acp >/dev/null

# Upgrade OpenClaw if the base image is stale.
# Reuse exact OpenClaw and locked-mcporter base installs only when the protected
# provenance marker matches this build target; otherwise reinstall both.
# Reuse exact OpenClaw and locked-mcporter base installs only from a published
# NemoClaw base whose package provenance marker matches this build target;
# otherwise reinstall both.
#
# The GHCR base image (sandbox-base:latest) may lag behind the version pinned in
# Dockerfile.base, and legacy/custom bases may report the target version without
# proving which archive and lifecycle produced it. Current official/local bases
# emit the marker only after installing and auditing both dependencies. The
# final image consumes it before applying NemoClaw patches so it cannot
# masquerade as a pristine base when reused as a custom BASE_IMAGE.
# proving which archive and lifecycle produced it. The marker records package
# and advisory-audit metadata, not trusted-CI signature attestation. Only a
# digest-pinned base from the official GHCR publication path supplies that
# independent gate. Mutable tags and local bases cannot authorize reuse even
# when their marker matches; the existing version checks reinstall the locked
# runtimes or reject a newer base. The final image consumes the marker before
# applying NemoClaw patches so a custom base cannot masquerade as a pristine
# published base.
#
# OPENCLAW_VERSION is the NemoClaw runtime build target. It must be at least the
# blueprint minimum, which also supports the legacy direct-blueprint image path.
Expand Down Expand Up @@ -875,7 +879,7 @@
OPENCLAW_PROVENANCE_PATH=/usr/local/share/nemoclaw/openclaw-base-provenance-v1; \
OPENCLAW_EXPECTED_PROVENANCE="$(mktemp)"; \
printf '%s\n' \
'schema=3' \
'schema=4' \
"package=openclaw@${OPENCLAW_VERSION}" \
"integrity=${EXPECTED_INTEGRITY}" \
"tarball=${EXPECTED_TARBALL}" \
Expand All @@ -888,14 +892,14 @@
"mcporter-audit-policy-sha256=${MCPORTER_AUDIT_POLICY_SHA256}" \
"mcporter-audit-status=${MCPORTER_EXPECTED_AUDIT_STATUS}" \
"mcporter-audit-exceptions=${MCPORTER_EXPECTED_AUDIT_EXCEPTIONS}" \
'mcporter-recipe=locked-ci+reviewed-audit+signatures-v2' \
'mcporter-recipe=locked-ci+reviewed-audit-v3' \
> "$OPENCLAW_EXPECTED_PROVENANCE"; \
TRUSTED_BASE_IMAGE=0; \
CI_GATED_BASE_IMAGE=0; \
case "$BASE_IMAGE" in \
ghcr.io/nvidia/nemoclaw/sandbox-base:*|ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:*|nemoclaw-sandbox-base-local|nemoclaw-sandbox-base-local:*) TRUSTED_BASE_IMAGE=1 ;; \
ghcr.io/nvidia/nemoclaw/sandbox-base@sha256:*) CI_GATED_BASE_IMAGE=1 ;; \
esac; \
USE_REVIEWED_BASE_RUNTIME=0; \
if [ "$TRUSTED_BASE_IMAGE" = "1" ] \
if [ "$CI_GATED_BASE_IMAGE" = "1" ] \
&& [ -f "$OPENCLAW_PROVENANCE_PATH" ] \
&& [ ! -L "$OPENCLAW_PROVENANCE_PATH" ] \
&& [ "$(stat -c '%u:%g:%a' "$OPENCLAW_PROVENANCE_PATH" 2>/dev/null || true)" = "0:0:444" ] \
Expand Down Expand Up @@ -981,7 +985,6 @@
node --experimental-strip-types /scripts/lib/reviewed-npm-audit.mts \
--directory /usr/local/lib/nemoclaw/mcporter-runtime \
--exceptions /scripts/npm-audit-exceptions.json --graph mcporter-runtime --threshold high; \
npm --prefix /usr/local/lib/nemoclaw/mcporter-runtime audit signatures; \
fi

# Patch OpenClaw media fetch for proxy-only sandbox (NVIDIA/NemoClaw#1755).
Expand Down Expand Up @@ -1643,10 +1646,10 @@
# CHAT_UI_URL is a non-loopback address (Brev Launchable, remote deployments)
# since terminal-based pairing is impossible in those contexts.
# Default: "0" (device auth enabled for local deployments — secure by default).
ARG NEMOCLAW_DISABLE_DEVICE_AUTH=0

Check warning on line 1649 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "NEMOCLAW_DISABLE_DEVICE_AUTH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 1649 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "NEMOCLAW_DISABLE_DEVICE_AUTH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
# Internal audit provenance for the opt-out above. Standard onboarding rewrites
# this to managed-onboard; direct image builders retain operator provenance.
ARG NEMOCLAW_DEVICE_AUTH_OPT_OUT_SOURCE=operator

Check warning on line 1652 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "NEMOCLAW_DEVICE_AUTH_OPT_OUT_SOURCE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 1652 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "NEMOCLAW_DEVICE_AUTH_OPT_OUT_SOURCE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
# Compatibility build arg for older custom Dockerfiles and rebuild tooling.
# NemoClaw-managed images intentionally do not consume it; gateway auth tokens
# are generated at container startup and are never baked into image layers.
Expand Down Expand Up @@ -1680,7 +1683,7 @@
# NEMOCLAW_MESSAGING_PLAN_B64 intentionally remains ARG-only: Docker exposes it
# to build RUN processes without retaining the full plan in the final image env.
# Direct ARG interpolation into inline source is a code injection vector (C-2).
ENV NEMOCLAW_MODEL=${NEMOCLAW_MODEL} \

Check warning on line 1686 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "NEMOCLAW_DEVICE_AUTH_OPT_OUT_SOURCE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 1686 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "NEMOCLAW_DISABLE_DEVICE_AUTH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 1686 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "NEMOCLAW_DEVICE_AUTH_OPT_OUT_SOURCE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 1686 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "NEMOCLAW_DISABLE_DEVICE_AUTH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
NEMOCLAW_INFERENCE_PROVIDER_ID=${NEMOCLAW_INFERENCE_PROVIDER_ID} \
NEMOCLAW_UPSTREAM_PROVIDER=${NEMOCLAW_UPSTREAM_PROVIDER} \
NEMOCLAW_PRIMARY_MODEL_REF=${NEMOCLAW_PRIMARY_MODEL_REF} \
Expand Down
5 changes: 2 additions & 3 deletions Dockerfile.base
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,6 @@ RUN --mount=type=bind,source=nemoclaw-blueprint/blueprint.yaml,target=/tmp/bluep
--directory /usr/local/lib/nemoclaw/mcporter-runtime \
--exceptions /scripts/npm-audit-exceptions.json --graph mcporter-runtime --threshold high \
--report /tmp/mcporter-npm-audit.json --result /tmp/mcporter-npm-audit-policy.json \
&& npm --prefix /usr/local/lib/nemoclaw/mcporter-runtime audit signatures \
&& MCPORTER_AUDIT_STATUS="$(node -p "require('/tmp/mcporter-npm-audit-policy.json').status")" \
&& MCPORTER_AUDIT_EXCEPTIONS="$(node -p "require('/tmp/mcporter-npm-audit-policy.json').acceptedAdvisories.join(',') || 'none'")" \
&& MCPORTER_AUDIT_POLICY_SHA256="$(node -p "require('/tmp/mcporter-npm-audit-policy.json').exceptionPolicySha256")" \
Expand All @@ -568,7 +567,7 @@ RUN --mount=type=bind,source=nemoclaw-blueprint/blueprint.yaml,target=/tmp/bluep
&& mkdir -p "$OPENCLAW_PROVENANCE_DIR" \
&& OPENCLAW_PROVENANCE_TMP="$(mktemp "${OPENCLAW_PROVENANCE_PATH}.tmp.XXXXXX")" \
&& printf '%s\n' \
'schema=3' \
'schema=4' \
"package=openclaw@${OPENCLAW_VERSION}" \
"integrity=${EXPECTED_INTEGRITY}" \
"tarball=${EXPECTED_TARBALL}" \
Expand All @@ -581,7 +580,7 @@ RUN --mount=type=bind,source=nemoclaw-blueprint/blueprint.yaml,target=/tmp/bluep
"mcporter-audit-policy-sha256=${MCPORTER_AUDIT_POLICY_SHA256}" \
"mcporter-audit-status=${MCPORTER_AUDIT_STATUS}" \
"mcporter-audit-exceptions=${MCPORTER_AUDIT_EXCEPTIONS}" \
'mcporter-recipe=locked-ci+reviewed-audit+signatures-v2' \
'mcporter-recipe=locked-ci+reviewed-audit-v3' \
> "$OPENCLAW_PROVENANCE_TMP" \
&& chmod 0444 "$OPENCLAW_PROVENANCE_TMP" \
&& mv -f "$OPENCLAW_PROVENANCE_TMP" "$OPENCLAW_PROVENANCE_PATH" \
Expand Down
19 changes: 17 additions & 2 deletions agents/openclaw/dependency-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,21 @@ The lock records the exact version, registry URL, and integrity for every transi

- `invalidState`: the image installs a package graph, tarball, license, or advisory state that differs from the independently queried npm registry records for `mcporter@0.7.3`, resolves `@hono/node-server` to any version other than exact `2.0.11`, resolves `fast-uri` to any version other than exact `3.1.5`, resolves `hono` to any version other than exact `4.12.34`, or resolves `ip-address` to any version other than exact `10.3.1`.
- `sourceBoundary`: npm owns registry metadata, tarball integrity, provenance signatures, and advisory responses; NemoClaw owns the exact lock, script-disabled install, Docker integrity assertion, empty-by-default audit exception registry, and review record.
- `whyNotSourceFix`: a repository note cannot make external registry state trustworthy, so image builds execute `npm audit` and `npm audit signatures` against the locked production graph and reviewers compare the lock with the registry response.
- `regressionTest`: `test/mcporter-supply-chain.test.ts` keeps the version, integrity, lock metadata, Docker install flags, audit commands, and this review synchronized; `test/reviewed-npm-audit.test.ts` proves exact matching and fail-closed exception validation.
- `whyNotSourceFix`: a repository note cannot make external registry state trustworthy, so the required `reviewed-npm-audit` CI check materializes the exact locked production graph and verifies its registry signatures.
- `imageBuildBoundary`: image builds verify the committed lock, registry origin, tarball integrity, installed graph, lifecycle suppression, and reviewed advisory policy without connecting to Sigstore.
The `schema=4` and `mcporter-recipe=locked-ci+reviewed-audit-v3` provenance values record this boundary.
They do not attest that trusted CI verified registry signatures.
- `enforcementBoundary`: any nonzero `npm audit signatures` status fails the required CI check.
The PR workflow requires this check before merge.
The `pr-reviewed-npm-audit` job loads its audit implementation from the base branch revision and evaluates the dependency files from the commit under review.
The managed-image build job requires that result before local builds and same-repository digest publication.
The base-image workflow requires its audit result before it builds or publishes any base image.
It also requires the result before it invokes managed-image publication.
Final OpenClaw images reuse a matching installed runtime only from a digest-pinned base in the official GHCR namespace.
The publication workflow gates that base on the check.
A matching marker from a local base or mutable tag is package metadata without independent CI attestation.
It cannot authorize reuse; the existing version checks reinstall the locked runtime or reject a newer base.
- `regressionTest`: `test/mcporter-supply-chain.test.ts` keeps the version, integrity, lock metadata, Docker install flags, image-build audit boundary, `reviewed-npm-audit` CI check, and this review synchronized.
`test/managed-image-publication-workflow.test.ts` verifies that the base branch supplies the audit implementation, the commit under review supplies the input, and publication depends on the audit.
`test/reviewed-npm-audit.test.ts` proves exact matching and fail-closed exception validation.
- `removalCondition`: remove this runtime dependency and review when OpenClaw provides the required authenticated Streamable HTTP client lifecycle without mcporter, or repeat the independent review for a newly pinned version.
2 changes: 1 addition & 1 deletion ci/source-shape-test-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
},
{
"file": "test/corporate-ca-build-tls-anchor.test.ts",
"test": "decodes the CA and exports NODE_EXTRA_CA_CERTS before the reinstall audit-signatures step",
"test": "decodes the CA and exports NODE_EXTRA_CA_CERTS before registry-backed dependency requests (#8925)",
"category": "security"
},
{
Expand Down
14 changes: 13 additions & 1 deletion docs/security/configure-corporate-ca-trust.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,19 @@ This lets the in-sandbox OpenShell proxy validate TLS when it opens the upstream

<AgentOnly variant="openclaw">

It sets `NODE_EXTRA_CA_CERTS` before build-time Node.js dependency verification, including `npm audit signatures` requests that cross a TLS-inspecting proxy.
It sets `NODE_EXTRA_CA_CERTS` before registry-backed build-time Node.js dependency requests that cross a TLS-inspecting proxy.
The required `reviewed-npm-audit` CI check materializes the dependency graph from the committed lock and verifies its registry signatures.
Any nonzero `npm audit signatures` status fails the check.
The pull request (PR) check must pass before merge.
The managed-image PR workflow loads its audit implementation from the base branch revision and evaluates the dependency files from the commit under review.
The image-build job requires that result before local builds and same-repository digest publication.
The base-image workflow requires its audit result before it builds or publishes any base image.
It also requires the result before it invokes managed-image publication.
Managed OpenClaw sandbox image builds do not repeat that Sigstore request.
The base provenance marker records package and advisory-audit metadata, not the CI signature result.
The final image reuses those runtimes only from a digest-pinned base in the official GHCR namespace.
A locally built base or mutable tag does not provide independent CI publication evidence.
Its marker cannot authorize reuse; the existing version checks reinstall the locked OpenClaw and mcporter runtimes or reject a newer base.
When NemoClaw selects a corporate CA, it sets `NEMOCLAW_MANAGED_IMAGE_RUNTIME_USER=root` for the image build.
The OpenClaw managed startup runtime creates `/run/nemoclaw/managed-startup-ca-bundle.pem` as `root:root` with mode `0444` before it starts agent commands as the `sandbox` user.
The `sandbox` user can read the merged bundle but cannot modify or replace it.
Expand Down
12 changes: 7 additions & 5 deletions test/corporate-ca-build-tls-anchor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ describe("corporate proxy CA build-time TLS anchor (#6839)", () => {
expect(matches).toHaveLength(1);
});

// source-shape-contract: security -- The build-time TLS trust anchor must precede registry and signature-verifying fetches
it("decodes the CA and exports NODE_EXTRA_CA_CERTS before the reinstall audit-signatures step", () => {
// source-shape-contract: security -- The build-time TLS trust anchor must precede registry-backed dependency requests
it("decodes the CA and exports NODE_EXTRA_CA_CERTS before registry-backed dependency requests (#8925)", () => {
const argIndex = dockerfile.indexOf("ARG NEMOCLAW_CORPORATE_CA_B64=");
const decodeIndex = dockerfile.indexOf('RUN if [ -n "${NEMOCLAW_CORPORATE_CA_B64}" ]; then');
const anchorIndex = dockerfile.indexOf(
Expand All @@ -32,23 +32,25 @@ describe("corporate proxy CA build-time TLS anchor (#6839)", () => {
"node --experimental-strip-types /scripts/lib/patch-bundled-npm-ip-address.mts",
curlAnchorIndex,
);
const auditSignaturesIndex = dockerfile.indexOf("mcporter-runtime audit signatures");
const mcporterInstallIndex = dockerfile.indexOf(
"npm --prefix /usr/local/lib/nemoclaw/mcporter-runtime ci",
);

for (const [name, index] of Object.entries({
argIndex,
decodeIndex,
anchorIndex,
curlAnchorIndex,
ipAddressPatchIndex,
auditSignaturesIndex,
mcporterInstallIndex,
})) {
expect(index, name).toBeGreaterThan(-1);
}
expect(argIndex).toBeLessThan(decodeIndex);
expect(decodeIndex).toBeLessThan(anchorIndex);
expect(anchorIndex).toBeLessThan(curlAnchorIndex);
expect(curlAnchorIndex).toBeLessThan(ipAddressPatchIndex);
expect(anchorIndex).toBeLessThan(auditSignaturesIndex);
expect(anchorIndex).toBeLessThan(mcporterInstallIndex);
});
});

Expand Down
1 change: 1 addition & 0 deletions test/fetch-guard-patch-regression.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ describe("fetch-guard patch regression guard", () => {
expect(invocation.calls).toMatch(
/node --experimental-strip-types \/scripts\/lib\/reviewed-npm-audit\.mts --directory \S+ --exceptions \S+ --graph mcporter-runtime --threshold high/,
);
expect(invocation.calls).not.toContain("audit signatures");
readRequiredMatch(
DOCKERFILE_BASE,
/(npm --prefix \/usr\/local\/lib\/nemoclaw\/mcporter-runtime ci\s*\\\s*--ignore-scripts --omit=dev --no-audit --no-fund --no-progress)/,
Expand Down
Loading
Loading