Bot: fail-closed compliance when Legal API errors (replace unwrap_or(false)) #5
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#5
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
Replace fail-open
unwrap_or(false)on Legal API status checks in the Telegram terms bot so API outages/rate-limits do not mark signed users non-compliant or kick them.Tracked from internal audit
audits/INTERNAL_COMPOSER_1786408744.md(H1) andgaps/GAP_1786322222.md.Current codebase
The bot decides compliance by calling
LegalApi::is_signed_latest(HTTPGET /api/v1/signatures/status). On anyErr(timeout, 5xx, 429, parse failure), several paths treat the user as unsigned:handle_group_messagemark_non_compliant(starts/resets grace clock)announce_terms_updatemember looprun_kicksban_chat_memberif still “unsigned”LegalApi(bot/src/api_client.rs) has no retries, circuit breaker, or authenticated channel to the API. There are no bot enforcement unit/integration tests in CI for these failure paths.Related (in scope of this issue as guardrails, not separate products):
Out of scope for this issue (file separately if desired): grace reset on rejoin, admin/creator kick exemption, lurker tracking, property upsert, Telegram crypto HMAC.
Why the new implementation is needed
Fail-open into punishment is unsafe:
run_kicks) can wrongfully kick users who already signed latest terms.Correct posture for enforcement: fail-closed — when status is unknown, do not escalate (no new non-compliance marks that advance kicks; never kick on unknown).
Constraints / guardrails
is_signed_latestreturnsError when the bot has no fresh successful status for that user.mark_non_compliantsolely because of an API error (that starts grace and leads to kicks).Ok(true); onOk(false)keep current mark/clear semantics.warn/errorwithchat_id,user_id, and error cause when failing closed.skills/if one exists for the bot later).Relevant files
bot/src/enforcement.rshandle_group_message,announce_terms_update,run_kicksbot/src/api_client.rsstatus,is_signed_latestbot/src/scheduler.rsbot/src/handlers.rsbot/src/db.rsmark_non_compliant,clear_compliant, overdue queriesbot/Cargo.toml.gitlab-ci.ymlcargo testruns forbot/Recommended direction
ComplianceCheck::{SignedLatest, NotSignedLatest, Unknown}, mappingResult<bool, _>→ enum (never coerceErr→ false).SignedLatest→clear_compliantNotSignedLatest→mark_non_compliant(existing semantics)Unknown→ no DB escalation; skip kick; increment/log metricrun_kicks: only kick when status is explicitlyNotSignedLatest(and member still active). OnUnknown,continuewithout ban.botcargo testjob in CI (lint alone is insufficient).Acceptance criteria
unwrap_or(false)(or equivalent) on compliance status in enforcement/kick paths.handle_group_messagedoes not insert/updatebot_member_complianceas non-compliant.run_kicksdoes not ban/kick the user.Ok(true)still clears compliance;Ok(false)still marks non-compliant.cargo testforbot/.Test plan — functional paths
signed_latest: truesigned_latest: falseTest plan — attack / abuse / failure vectors
LEGAL_API_BASE_URLreturning errorssigned_latestfrom hostile API is a separate trust issue, out of scope)Verification criteria
cd bot && cargo testpasses locally and in CI.ban_chat_memberfor previously signed fixture users; compliance rows unchanged for error path.unwrap_or(false)onis_signed_latest/ status inbot/src.mentioned in commit
90b3b79786Implemented fail-closed compliance on branch
fix/issue-5-fail-closed-compliance(MR linked).Done vs acceptance criteria
unwrap_or(false)on status in enforcement/kick paths — centralized inbot/src/compliance.rshandle_group_message→ Hold (nomark_non_compliant)run_kicks→ skip banannounce_terms_updateloop → Hold per memberOk(true)clears;Ok(false)markswarnon Unknown (chat_id,user_id,error,context)test:rust-botskills/bot-enforcement/SKILL.mdNot done (explicitly out of scope for #5)
Verify locally
mentioned in merge request !27
MR !27 now has the fix commits (source branch updated). Local tests green; CI blocked by project
ci_quota_exceeded— re-run when minutes available.mentioned in commit
6470432a8ementioned in commit
9f45ac6e58Merge verification (!27 → main)
Verdict: PASS_WITH_NOTES — all issue #5 acceptance criteria met; MR !27 merged without waiting for CI / without auto-merge.
Conflict fix
After !26 landed, merging
maininto the !27 branch conflicted only inskills/security-ops/SKILL.md(out-of-scope paragraph). Resolved by keeping both the #5bot-enforcementcross-link and the #6 SSRF out-of-scope note. Push:6470432.Acceptance criteria
unwrap_or(false)on compliance status in enforcement/kick/announce pathshandle_group_messagedoes not mark non-compliantrun_kicksdoes not ban/kickOk(true)clears;Ok(false)marksUnknown(chat_id/user_id/context)test:rust-botCI job presentLocal checks (post-conflict resolution)
cd bot && cargo test— PASS (8/8)cargo fmt --check+cargo clippy --all-targets -- -D warningscleanProblems / residual risks (non-blocking)
ci_quota_exceeded;test:rust-bot/lint:rust-botwere not re-run before merge (per instruction). Re-run when minutes available.lint:rust-botCI usescargo clippywithout--all-targets(local used--all-targets); minor inconsistency withapi/lint job.ban_chat_member/ unchanged compliance rows for previously signed users.No acceptance-criteria defects found; closing via !27.