feat: reuse digest-pinned enclave components from S3 #468
No reviewers
Labels
No labels
Compat/Breaking
Component/Enclave-Builder
Component/Infrastructure
Deploy/BYOC
Deploy/Full-managed
Deploy/Self-hosted
Interface/API
Interface/CLI
Interface/Git
Interface/Web
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority/Critical
Priority/High
Priority/Low
Priority/Medium
Reviewed/Confirmed
Reviewed/Duplicate
Reviewed/Invalid
Reviewed/Won't Fix
Status/Abandoned
Status/Blocked
Status/Need More Info
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
public/platform!468
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/s3-prebuilt-components"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Closes #250. Companion documentation: public/docs#39.
make uppreparation, immutable publication, component-aware cache identity, and paired EIF/host-helper provenance.8a3e193d638c772b9521b3a1ae9589cb0c83f342, retaining main's network-policy enforcement, compressed bootstrap and size guard in both managed and BYOC deployment generators.Current candidate and evidence
Current candidate:
ea4bfd3aad08aee40a2c0cd48ac014be3ab3787d.Fresh exact-candidate local results: regular suite 1,277 Rust passed / 23 ignored plus 54 Python passed (live Paddle excluded); deployment 34/34; network-policy target 11 Rust plus 26 Python passed; publisher 12/12; real TAP 1/1. Frozen integration and final-delta independent reviews found no blockers. Strict Clippy's remaining baseline failures are disclosed below. No new live S3/Nitro or branch deployment has been run; historical live runs do not validate this rebased runtime.
Fresh exact-candidate commands:
The regular suite is explicitly run without live Paddle credentials. Local fixture, real Linux packet/TAP, generated bootstrap and Caddy checks are not deployed Nitro or provider-authorization evidence. Default ignored tests remain excluded unless separately named.
Historical component source/prebuilt EIF/PCR equality and managed/BYOC runs remain attached to their recorded older revisions. The standalone component recipes, TAP implementation, migration, infra-bootstrap and frontend source are unchanged from the preceding PR head. Four PR-local Clippy findings were corrected with equivalent fail-closed predicates and redundant-borrow removal; the shared renderer integration is also new. This supports scoped evidence retention, not a fresh runtime or fresh framework-selection claim.
Strict Clippy (
cargo clippy --locked -p api -p cli -p enclave-builder --all-targets -- -D warnings) exits 101 on both the final candidate and a fresh main checkout. Their remaining 22 diagnostics match exactly in unchanged source/test files; no PR-added diagnostics remain. This is baseline equivalence, not a clean lint pass.Remaining release boundaries
The user retains manual review/merge and production deployment ownership.
Validation closeout for
b140f98ba462b37197e8ebc563b29b56ebeb52db:RoleLastUsedtelemetry was explicitly excluded; raw evidence is retained.404e66a16614eed59fea6b4b1326875cc7a7f62c913613ff73590b0cadf84b31.Evidence remains mirror-assisted compilation with an isolated S3 fixture, not production/customer IAM or live Nitro rollout proof. No production deployment or merge was performed. Remote CI currently reports no statuses; test claims above are the recorded disposable-host runs.
Review of
dd0bb0bd(this PR only; #464 not covered).High
src/enclave-builder/src/manifest.rs:24:component_setsits in the middle of the measured manifest. An already-released CLI parses it into#[serde(flatten)] extraand writes it back at the end, with sorted keys (ComponentSet/ComponentSpecfields and theComponentmap are not in alphabetical order). Themanifest.jsonbytes in the initramfs change, socaution verifyreports a PCR mismatch on every pinned deploy. Fix: make it the last named field and serialize with sorted keys, or keep it out of the measured manifest.src/api/src/main.rs:4271:validate_source_pinsrequires the tap-framersource_committo equalPLATFORM_GIT_SHA, which is baked into the API image at build time (Makefile:72).COMPONENT_SET_SHA256lives in.env, so any new API image fails every deploy with a 500 "internal error". Cache hits fail too, because the check runs before the cache lookup. Fix: validate at API startup and fail fast, and/or pin tap-framer bysource_tree_sha256instead of the whole platform commit.Medium
src/enclave-builder/src/components/store.rs:69:with_max_attempts(1)turns off SDK retries for every component GET and PUT, including theload_setthat runs on every deploy. A single S3 503, SlowDown or connection reset fails the deploy. Fix: keep standard retries; the conditional-PUT loop already handles 412/409.src/api/src/component_artifacts.rs:273withinfra-bootstrap/main.tf:323: env.example documents settingCOMPONENTS_S3_BUCKETdifferent fromEIF_S3_BUCKET. In that case the managed path copies components into the EIF bucket using platform credentials, but IAM only grantsGetObjectoncomponents/v1/*, so every uncached deploy fails. Fix: grantPutObjectoncomponents/v1/*plus read on the source bucket, or require the two buckets to be the same.src/enclave-builder/src/components/store.rs:91: only a 404 NoSuchKey counts as a missing object. Without an unconditionals3:ListBucket(infra-bootstrap's is limited by ans3:prefixcondition), S3 returns 403 for missing keys, soensure_components_fromfails on the first blob instead of copying it. BYOC also needsPutObjectoncomponents/v1/*for the relay credentials andGetObjectthere for the builder role (builder.rs:1670); neither is documented. The actual impact depends on the BYOC IAM, which isn't in this repo. Fix: skip the destination pre-read and rely onput_immutable's If-None-Match + read-back, and document the BYOC IAM.Rebased onto current #464 and tested
Exact candidate:
4fd6292a4a2171dd1c368fdac92b13f4a5e3e442, a single signed feature commit directly on3321efdbd641b46b343cbe641fee7a313dcbdcec.The rebase preserves #464's current Make targets, canonical standalone TAP Containerfile and independently reviewed host-helper pin. Component publication, source reproduction and startup validation now use the selected canonical recipe and correct source paths. The prepared review fixes were carried forward without restoring old README text or removed wrappers.
Actual execution on an owned, role-free disposable EC2 host
make test: 997 Rust tests passed, 0 failed, 6 default-ignored, plus CLI installer shell checks.cargo test --locked -p enclave-builder --features s3-components --lib: 152 passed;cargo test --locked -p enclave-builder --features s3-components --bin prepare-components: 7 passed. These overlap the regular suite rather than adding unique-test totals.cargo test --locked -p api component_cache_query_and_migration_round_trip -- --ignored --nocaptureandcargo test --locked -p api component_rejection_restore_preserves_running_and_attempt_ownership -- --ignored --nocapture: 1 passed each, with only the isolatedCOMPONENTS_TEST_DATABASE_URLfixture.unshare --user --map-root-user --net cargo test --locked --manifest-path src/tap-framer/Cargo.toml -- --ignored: 1 passed (TAP lifetime).unshare --user --map-root-user --net python3 tests/test_tap_framer.py <binary>: 8 passed against the native helper and 8 passed against the actual static helper exported by the EIF build.prepare-componentspublication: all five components built with--no-cacheand published to loopback Moto. A second invocation reused them without any compiler invocation and produced the identical lock.COMPONENT_E2E_ROOT=/home/tester/pr468-eif-4fd6292/fixture make test-prebuilt-components: 1 passed through the production composition entrypoint. Source/prebuilt EIF, PCR file, measured manifest, compressed rootfs and host TAP helper are byte-identical; both Docker EIF builds used--no-cache.host.sha256:e50265d7f57ce2ade393714b2c52f3173d288b348598f5a85aad1a775f49f283. No trust pin changed.The initial focused run caught a moved fixture path; it was corrected in the committed candidate and the complete final validation rerun. Original failure evidence is retained.
Evidence and scope
Fresh EIF/composition evidence archive SHA-256:
fe58c06ab6f72cf552ff90942b0885635511861044638f212f71a0e48fc79a4c. Archive receipt and every member were independently verified after retrieval. Regular-suite, warm-cache, database and actual-static-helper evidence are retained separately.Codeberg returned HTTP 429/IP-block responses on the disposable host. Builds therefore used an object/commit/tree/lock-verified source relay; original and effective recipes are retained. This does not qualify canonical-network access, live managed/BYOC S3/IAM, or Nitro runtime attestation. No real platform API/controller or production deployment was started. Historical timing figures in the PR body were not remeasured in this rebase.
Owned instance, root EBS, ENI and security group were removed and their absence verified. Existing VPC/subnet and protected live-dev resources, services and configuration were left unchanged; raw before/after inventories and any explicitly classified historical/telemetry differences are retained.
BYOC source-archive blocker resolved
Final framework candidate:
2173d036f43e5f81d5d974425faf6388666f5a44. No platform source change, push, merge, registry release, or production rollout was needed for this follow-up.Root cause and correction
The temporary E2E launcher pinned the BYOC provisioner at
3f3256dad657bd9e062bb2ce8ad284366b4b4cbe, one commit behind the existing upstream fix. CloudTrail confirmed the failed attempt's actual policy alloweds3:PutObjectbut omitteds3:PutObjectTagging. The API uploads the source archive with organization/build tags, so plain component uploads succeeding did not qualify this operation.Built canonical provisioner commit
aa6c8edon the existing role-free disposable runner and pinned the verified immutable imagesha256:88b10efa628ebd2c2a2e2498cfafb72a699240da83ba13cd47b0120c1cbf3bf4. This upstream change adds only the missing object-tagging action within the existing customer-bucket scope. No wildcard S3 grant, removed tags, new operator IAM identity, or weakened verifier.AWS IAM simulation reproduced the missing owned-bucket tagging permission, then allowed it with the upstream fix; unrelated-bucket PUT and tagging stayed denied. During the successful retry, independent S3
HeadObject/GetObjectTaggingreadback confirmed the real archive and both expected tags.Executed verification
python3 /home/tester/build-provisioner-aa6c8ed-build2.py: 4 policy regression tests passed, then canonicalmake buildsucceeded. The first build attempt failed because the disposable runner lackedmake; it was installed there only and the successful build used a fresh directory. Original failure retained.python3 /tmp/pr468-byoc-repair-20261008/verify-adapters.py byoc-review-green: 40 tests passed on the owned guest, including stale-image rejection and BYOC-only selection; expected-red logs retained.python3 /tmp/pr468-byoc-repair-20261008/final-acceptance.py byocinvoked the unchanged final-headbash tests/e2e/test_byoc.sh: 9/9 steps passed, exit 0. Real customer-bucket source/component flow, native Nitro deployment,caution verify --attestation-url <live-endpoint> --no-cache, cryptographic attestation/PCR0/1/2 and component provenance all passed. Independent reproduction ran without operator credentials or an instance role.Hello from Caution.co! Deployment successful!.Restoration and cleanup
Normal dev images/configuration and PostgreSQL identity were restored and verified; normal public health and disabled test-login behavior were checked. Both attempts' isolated test DB containers/volumes are absent. BYOC resources and the disposable verifier's EC2/EBS/ENI/security group/key pair were removed and read back.
The full final inventory also caught the earlier failed attempt's empty VPC and leftover unassociated route table. Their exact deployment tags and absence from the protected baseline were verified, all attachment guards passed, and those owned leftovers were removed. All 12 original protected instances are unchanged; zero unexplained configuration differences remain. Raw inventories and failed comparisons are retained. IAM usage telemetry is explicitly qualified, not discarded: six pre-frozen exact-role timestamp changes plus the unchanged Auto Scaling role's region/timestamp transition, corroborated by CloudTrail launching the journaled BYOC instance. No IAM policy/configuration drift was accepted.
Evidence references and limits
Evidence root:
/tmp/pr468-byoc-repair-20261008/; manifest:blocker-resolution.json.cb95a9bfe91da195ca4842a04c918bb6a0eeb06b6e426680d913b05feb473d532268bd2cda5b2d0c48eeb40bd9933c0c3ddf7bdd22684c4db25f13050bb246e121d76ee9dee6f1e701974822a7caad45d8f5c8deb64497aa76b4e32cb95af524The previously listed deployment acceptance blockers are cleared. This is a scoped merge-readiness result, not production rollout approval. Live Keymaker/V1 remains unverified; separate-account BYOC and a fresh full-EIF cache-replay case are not claimed. The earlier 18-suite campaign remains mixed-revision evidence, not a claim that every suite was rerun here. Production must use a provisioner artifact containing the upstream permission fix; this task did not publish a registry release.
Re-checked at
7c66ace5. All five earlier findings are fixed, thanks. Three concerns remain before merge:1. Released CLIs can no longer verify new deploys (blocker)
Containerfile.eif:26-28adds{{INIT_CONTAINERFILE}}and{{BOOTPROOF_CONTAINERFILE}}outside any block. Every CLI released before this PR only fills in{{TAP_FRAMER_CONTAINERFILE}}, sodocker buildfails on the literal placeholders andcaution verifybreaks for every new deploy, with or without egress. "Historical inline layouts" only covers the other direction (new CLI, old deploy). The hint atverify.rs:1240also still says "PR #464". Decision: A) accept a forced CLI upgrade, make the hint version-based, and add a release note; or B) keepContainerfile.eifself-contained and have publication take the recipes from it.2. Split out the billing and auth fixes
mainstill writescredit_ledger.user_id, which migration 050 dropped (metering/src/credits.rs:87), so Paddle top-ups aren't being credited today. That fix and the gateway 401 change are unrelated to components and shouldn't wait on a 7.7k-line feature PR. Please open them as a separate PR.3. Every API release requires a new component set
TapFramer is pinned to the whole
PLATFORM_GIT_SHA. With a set active, any API image, even one without tap-framer changes, refuses to boot until a new set is published and activated. Pin it bysource_tree_sha256instead, or at minimum document the paired release step in the runbook.Minor:
aa6c8ed.host.sha256is up to date.PR #468 review: "feat: reuse digest-pinned enclave components from S3"
Scope and method
e7c9b933d96c56230658fca230c26475b3ac173a(
feat/s3-prebuilt-components), diffed against merge-basefe15ccdb1b6742a422d4353f5725b1ea92b93b89. 39 files, +8665/-214.shared findings ledger for deduplication and a mandatory novelty check against the
merge-base. Rounds stopped after two consecutive rounds produced no new findings.
the coordinator could not execute (systemd, Docker, Nitro) are marked.
Coordinator-verified facts about this head
cargo check -p enclave-builder --features s3-components -p apisucceedsoffline against a warm cache.
enclave-builder --features s3-components --lib: 153 passed, 0 failed.enclave-builder --features s3-components --bin prepare-components: 11 passed.api --bin api: 218 passed, 1 failed, 2 ignored. The one failure is environmental:builder::component_tests::download_script_checks_hash_and_size_before_helper_executionruns the generated userdata fragment in host bash and needs
jqon PATH;jqisabsent here. The production userdata installs jq itself (
builder.rs:1586), so thereis no production impact.
api --test startup_preflight: 10 passed.enclave-builder --test prebuilt_components: 1 ignored (needs a real Docker/EIFfixture), as documented.
tests/test_prepare_components.py: 9 passed.tests/e2e/test_acceptance_contracts.py:4 passed.
(no Docker, PostgreSQL, Docker socket, or running systemd session in this sandbox).
main.git merge-tree main origin/pr-468reports content conflicts inCargo.lock,Makefile,env.example,src/api/src/main.rs,src/enclave-builder/src/lib.rs,src/enclave-builder/templates/Containerfile.eif,systemd/caution-api.service.Findings
High
R3-1. The default managed deployment cannot publish components: no
PutObjectundercomponents/v1/. (confirmed; K4's fix is incomplete)infra-bootstrap/main.tfadds, in this PR,S3ComponentReads(s3:GetObjecton<eif_bucket>/components/v1/*), ans3:prefixcondition forcomponents/v1/*onS3EIFList, andBuilderComponentReads.S3EIFObjectsgrantsPutObjectonly onbuilds/*andeifs/*. The onlyPutObjectoncomponents/v1/lives in the conditionalrelay policy, which exists only when
components_source_bucket_nameis set to a bucketdifferent from the EIF bucket, and even then it covers only
components/v1/blobs/sha256/*andcomponents/v1/manifests/sha256/*, nevercomponents/v1/builds/*.The publisher always writes all three:
publish_build(store.rs:273-303) writes blobsand
components/v1/builds/<spec>.json, andpublish_setwritescomponents/v1/manifests/sha256/<digest>.json. The documented default target bucket isCOMPONENTS_S3_BUCKETorEIF_S3_BUCKETorcaution-eif-storage-<account>(
scripts/prepare-components.py:104-109), i.e. the same EIF bucket, where the platformuser has no write grant. Impact: the first
make up/make prepare-componentson adeployment provisioned by this repo's own bootstrap fails
AccessDenied, so the featurecannot be used as documented. This contradicts
env.exampleandcontainerfiles/README.md, which state that existing deployment credentials may bereused and no new role is required. Relay mode also fails when a new build index must be
published. Fix: grant
s3:PutObject(ands3:PutObjectTaggingif needed) on<eif_bucket>/components/v1/*unconditionally (the code already writes create-only withIf-None-Match: *), or drop the same-bucket default and require a separate, publishedcomponent bucket with documented IAM.
K6. Released CLIs cannot verify new deploys. (confirmed by coordinator; raised in
prior review, still open)
The merge-base template had the STEVE/LOCKSMITH stage bodies inline and only
{{TAP_FRAMER_CONTAINERFILE}}as a placeholder; the merge-base renderer substituted onlythat marker plus
{{*_COMMIT}}tokens. Head moves STEVE/LOCKSMITH to{{STEVE_CONTAINERFILE}}/{{LOCKSMITH_CONTAINERFILE}}and adds{{INIT_CONTAINERFILE}}/{{BOOTPROOF_CONTAINERFILE}}outside any# {BLOCK(
templates/Containerfile.eif:26,28). Verification reproduces an enclave using theframework checkout's templates directory (
lib.rs:716-741), then renders it with theCLI's own copy of the renderer, so every CLI released before this PR leaves the two
unconditional placeholders literal and
docker buildfails with a Dockerfile parseerror. New CLI plus old deploy still works. The user-facing hint
(
src/cli/src/verify.rs:1240) still says "install a CLI containing platform PR #464",which misattributes the break. Needs a decision: accept a forced CLI upgrade with a
version-based hint and release note, or keep the EIF template self-contained.
Medium
R3-2. Non-optional systemd
EnvironmentFilefor a file that only exists after the firstmake up. (code facts confirmed; systemd mechanism not executed here)systemd/caution-api.service:9setsEnvironmentFile=%h/.config/caution/components.envwith no leading-. That file iswritten only by
scripts/prepare-components.py:157. Installing the updated unit andrestarting or rebooting before the first successful
make upfails the unit (a missingnon-optional EnvironmentFile fails the unit), and
${API_COMPONENT_IMAGE}is empty thenanyway.
tests/test_prepare_components.py:195asserts the non-optional literal, so thetest locks the behaviour in. Suggested fix:
EnvironmentFile=-%h/.config/caution/components.env, letting the API's own fail-closedcheck produce the error.
S7 / prior K8. Tap-framer is keyed to the whole platform commit, so every API release
forces a tap-framer rebuild. (confirmed design issue)
prepare-components.rs:239-240builds the TapFramer spec with&args.framework_commit,which is the platform HEAD passed by
scripts/prepare-components.py:142. The commit ispart of the content-addressed spec, so every platform commit yields a new build key and a
full tap-framer recompile, and
validate_startup_pinsadditionally requires tap-framersource_commit == PLATFORM_GIT_SHA. The adjacent per-componentrecipe_sha256check alsoties tap-framer to the platform template.
make upalways publishes a fresh set, so thehappy path works, but the "reuse" benefit does not hold for tap-framer across releases.
Consider keying tap-framer on its source subtree hash, or document the paired release
step in the runbook.
Low and nits (all confirmed unless noted)
component_stage.rs:68:unwrap()onrecipe::source_subdir(...)inproduction code. Infallible (TapFramer always has a subdir) but undocumented, contrary
to the repo error-handling rule.
recipe.rs:271-325: standalone-recipe validation (source imports, commit pins,{{check) runs only on the compiler stage; the*-exportstage is only name andFROM scratchchecked. Limited impact: recipe_sha256 covers the whole recipe and sourcereproduction asserts compiler-stage hashes.
BuildComponentArtifacts::{component_set_sha256,tap_framer_s3_key}(
component_artifacts.rs:305,308) are used only from#[cfg(test)], socargo check -p apiwarns "methods are never used". New code should not add warnings.scripts/prepare-components.py:84-92passes the operator's entire.env(database, JWT/signing, billing keys) to the publisher container, which also holds the
Docker socket and runs buildx over third-party images. Only
AWS_*, bucket names,COMPONENT_BUILD_MODE, and*_COMMITare needed.Makefile:580makesmake uphard-fail on any tracked local modification(duplicated in
scripts/prepare-components.py:76-77). Intentional but an undocumentedcontract change for operators who edit tracked files.
Makefile:494-497sources a possibly absentcomponents.envwith notest -fguard, yielding a misleading Docker "invalid reference format" error. Failclosed, UX only.
scripts/prepare-components.py:140creates abuild-XXXXXXlog directory perrun and never removes it, and the Rust publisher keeps full buildx logs for successful
components too. Every run leaks an unbounded log tree. Retention is documented as
out of scope, hence low.
COMPONENT_BUILD_MODE=source(
Makefile:18,736-737), and the E2E scripts never reference COMPONENT*, so the prebuiltstaging/userdata-download/relay path has no end-to-end coverage; only the
#[ignore]dprebuilt_componentstest touches it.tests/e2e/test_acceptance_contracts.py:37's fakecurlignores flags, so thecurl -fsSbehaviour it claims to guard is not exercised.download_script...test depends on hostjqwith no skip guard, failingwith a confusing
NotFoundpanic where jq is absent (see the test section above).Operational notes (not defects, worth stating in the PR / runbook)
store.rs:349-360: BYOC relay always GETs the source and re-PUTs withIf-None-Matchplus read-back for every blob, even when the destination already has it.Deliberate (avoids needing destination
ListBucket), but it means uncached deploys pay afull round trip per blob.
s3:PutObjectTaggingfix (aa6c8ed); not verifiable in this repo.src/tap-framer/host.sha256stays current with the shipped helper.main(conflicts listed above).Prior review items, classified at this head
retries preserved), K5 (only a 404 with
NoSuchKeyis a cache miss; 403 is never amiss; no destination pre-read), K7 (billing/auth fixes are no longer bundled: the diff
touches no
meteringorgatewayfile).--check-componentspreflight now runs before DB access,but the whole-platform-commit pin remains: see S7), K4 (relay grants added, but the
default same-bucket publish path still has no write grant: see R3-1).
Process notes
user's permission. After the user objected, all later rounds ran on the inherited model
(
deepseek/deepseek-v4.1-flash), and the stopping rule was counted only from thoserounds. Elapsed wall clock was dominated by the two large unapproved-model rounds.
falsification of all open items plus residual coverage) and round 8 (final whole-diff
sweep at a medium-or-worse bar).
predates the PR, which is the intended use of that check.
Coverage and limits
composition); API-side selection, startup preflight, provenance persistence, migration
054; packaging, host lifecycle, IAM, operator tooling; the publisher binary and small
diffs; component recipes and released-CLI compatibility; BYOC/deploy paths,
docs-vs-code, and a mechanical conformance sweep (only R1-1's
unwrap, no new#[from],todo!,unimplemented!, orunsafe; no missing instrumentation).attestation/reproduction, live managed/BYOC deploys, DB-backed tests, and systemd unit
execution. The PR body itself states the same limitation for this head.
every blob at startup (the code intentionally verifies pins/recipes and defers blob
verification to staging) and the exact Docker merge precedence between
-e VARand--env-file(unverifiable without Docker; the worst case fails closed).e7c9b933d9tof07ef753c6f07ef753c6tocf3fc1a8b3cf3fc1a8b3to62166ca60662166ca606tocc2d9c1a7b@ryan — update against
cc2d9c1a:curl -f.make -j1 test PADDLE_API_KEY= PADDLE_API_URL=passed on this exact head. This excludes live Paddle coverage. Earlier managed/BYOC live runs passed, but final-head live qualification remains pending; Paddle permissions and apex DNS/CAA/TLS qualification are deferred.Still explicit: the host test requires
jq(S4); relay round trips remain deliberate (K9); BYOC needs the compatible provisioner (K10); no CI host-pin check (K11) or build-log GC (R4-1) was added.Are you happy with these fixes/decisions, and which remaining items do you consider merge blockers?
anton referenced this pull request2026-10-17 01:57:16 +00:00
cc2d9c1a7btoea4bfd3aadea4bfd3aadto3485f577cc