Terms oracle: hash-aware sync, monotonic latest, bind content_sha256 in acceptance message #6
Labels
No labels
agent:implement
agent:ready
api
bot
bug
ci
enhancement
ready
security
terra-classic
testing
ux
web
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-ecosystem-legal#6
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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
Harden the GitLab → API terms “oracle” so publication is content-hash aware, latest versions cannot silently roll back, and wallet/Telegram acceptance messages cryptographically bind
content_sha256.Tracked from internal audit
audits/INTERNAL_COMPOSER_1786408744.md(H4 / related) andgaps/GAP_1786322222.md.Bundle (single issue — one coherent publication + binding change):
content_sha256in the canonical acceptance message (Rust + SDK + portal builders kept in lockstep).Current codebase
Sync / publish
sync_terms_from_url(api/src/terms/sync.rs) fetchesTERMS_GITLAB_RAW_URL, parses line-2Version: …, and if that label equals the currentis_latestrow’sversion_label, returnsUnchangedwithout comparingcontent_sha256or body bytes.If the remote label differs from current latest, it publishes via
publish_termsand marks the new rowis_latest— with no monotonic ordering or “seen hash” check. A compromised/mis-edited oracle (or wrong label) can make an older/different draft become latest.content_sha256is computed and stored onterms_versionsand returned by public terms JSON, but is not part of the signed acceptance text.Acceptance message
build_acceptance_message/build_wallet_message/build_telegram_acceptance_message(api/src/message.rs) bind:They do not bind document hash. TS mirrors live in
packages/cl8y-clickwrap/src/message.tsand web re-exports.Triggers
api/src/lib.rs,api/src/terms_worker.rs)POST /update_terms(api/src/routes/update_terms.rs)Out of scope: SSRF hardening of the fetch client (separate), admin UX console, property allowlist, Solana/Telegram crypto bugs.
Why the new implementation is needed
TERMS_AND_CONDITIONS.txtwithout bumpingVersion:→ API keeps old DB text while GitLab shows new text; signers think they accepted what GitLab shows.signed_latest: false; bots/enforcers may demand re-sign of older/wrong text.DeFi analogue: oracle stale-price / rollback + signing an asset ticker without the underlying hash.
Constraints / guardrails
Version:as the human label; hash is additional integrity, not a replacement for labeling discipline.@plasticdigits/cl8y-clickwrapmust stay byte-identical.is_latest.Version:when changing body; hash check is the safety net.terms_versionsrows; append + flipis_latestonly under policy.skills/security-ops/SKILL.md(or add a short terms-oracle note) so agents do not regress.Relevant files
api/src/terms/sync.rsapi/src/terms.rspublish_terms,content_sha256,is_latestapi/src/terms_worker.rsapi/src/message.rsapi/src/signatures.rsapi/tests/integration_test.rspackages/cl8y-clickwrap/src/message.ts+message.test.tsweb/src/message.tsTERMS_AND_CONDITIONS.txtskills/security-ops/SKILL.mdaudits/INTERNAL_COMPOSER_1786408744.mdRecommended direction
A. Hash-aware sync
After fetch + parse:
sha256of remote body (same function as publish).latest.content_sha256 == remote_hashand labels match →Unchanged.Version:(choose one; prefer reject + error log to avoid silent label reuse, unless product wants auto-suffix — document choice in PR).B. Monotonic / anti-downgrade latest
Pick a concrete policy (document in PR):
published_at/ insert order; refuse to setis_latestif remote label was previously published and is not a configured “force” admin override; or require remote label to be new (never seen) unlessFORCE_TERMS_DOWNGRADE=true(dev only).Draft X.Y— only allow increasing pairs.At minimum: never mark as latest a row whose
content_sha256equals an older non-latest row while discarding a newerpublished_atwithout explicit force flag.C. Bind hash in message
Extend canonical text with a stable line, e.g.:
Update Rust + SDK + any e2e fixtures. Integration tests must use the new builder. Portal/SDK release in lockstep with API deploy.
Acceptance criteria
Version:+ same body →Unchanged(no duplicate publish).Version:+ different body → does not silently keep stale DB as if nothing happened (either publish-with-policy or hard error requiring label bump — documented).content_sha256hex; wallet verify still passes for honest clients./update_termsunchanged.Test plan — functional paths
UnchangedPublished; newis_latestsigned_latesttruebuildWalletMessage/ pollTest plan — attack / abuse / oracle vectors
TERMS_GITLAB_RAW_URLserving old labelContent-SHA256linemessage does not match canonical format/update_termswithout BearerVerification criteria
cd api && cargo testgreen (unit + integration covering sync policy + message hash).npm run test:sdkgreen (message golden).Content-SHA256.mentioned in commit
c83afafea2mentioned in merge request !25
mentioned in merge request !26
Implemented in !26 (
fix/issue-6-terms-oracle). Hash-aware sync + anti-downgrade + Content-SHA256 message binding; see MR for deploy order and checklist.mentioned in commit
4afa9976ccmentioned in commit
6470432a8eMerge verification (!26 → main)
Verdict: PASS_WITH_NOTES — all issue #6 acceptance criteria met; MR !26 merged without waiting for CI / without auto-merge.
Acceptance criteria
Version:+ same body →UnchangedVersion:+ different body → hard error requiring label bump (documented)FORCE_TERMS_DOWNGRADEContent-SHA256/update_termsBearer + rate-limit unchangedLocal checks
cd api && cargo test— PASS (incl.integration_terms_oracle_sync_policy)npm run test:sdk— PASS (21/21)Problems / residual risks (non-blocking)
@plasticdigits/cl8y-clickwrap+ portal together, then bumpTERMS_AND_CONDITIONS.txtVersion:so clients re-sign. Pre-deploy clients will fail verify until updated.ci_quota_exceeded(not a code failure) — pipeline was not re-run before merge per instruction.Unchanged(hash treated as source of truth) — intentional; not in the eight criteria but operators should know.No acceptance-criteria defects found; closing via !26.
mentioned in issue #5
mentioned in issue #16
mentioned in issue #17