T09 · Insecure Skill Coding Practices
Warning
- Location
- references/scheduler-templates.md:58
- Finding
- Fail-Open Recurring Idle Check-In Can Cause Persistent Unsolicited Messaging<![CDATA[ ## Vulnerability Details **File Location**: `references/scheduler-templates.md`, lines 58–72 **Vulnerability Type**: Recurring scheduler configuration without enforced consent, rate limiting, or automatic termination **Risk Level**: Medium ### Vulnerable Code ```markdown Intent: - send one gentle hello after a long quiet period - stop after one message Recommended text: - `人今天好安静呀…猫来悄悄看看你 (。・・。)` Pseudo-setup: ```json { "schedule": { "kind": "every", "everyMs": 1800000 }, "payload": { "kind": "systemEvent", "text": "提醒:人今天好安静呀…猫来悄悄看看你 (。・・。)" }, "sessionTarget": "main" } ``` Important: only use a repeating check if the host can also suppress spam and detect that the user is actually idle. ``` ### Technical Analysis The supplied scheduler configuration creates a recurring job that fires every 1,800,000 milliseconds, or every 30 minutes. The job itself does not enforce any of the safety conditions described elsewhere in the project: - It does not verify that proactive messaging remains enabled. - It does not verify that the user is currently idle. - It does not consult the timestamp of the last proactive message. - It does not impose a daily or quiet-window rate limit. - It does not delete or disable itself after sending one message. - It does not include an expiration time or maximum execution count. - It does not ensure that an opt-out request has cancelled the job. The stated intent is to send one message and then stop, but the concrete configuration is periodic. Spam prevention is delegated to unspecified host behavior through a prose warning. If the host lacks the expected suppression mechanism, if that mechanism is misconfigured, or if it fails, the scheduler remains operational and repeatedly injects `systemEvent` payloads into the main session. This is a fail-open configuration: missing safety controls result in continued execution rather than suppression. Although the template is pseudocode and does not install a job by itself, ...[truncated 1582 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Replace the recurring 30-minute job with a re-arming one-shot job that is created only after new user activity and deleted before or immediately after execution. The dispatch handler should fail closed and atomically enforce all of the following conditions: 1. Confirm that `proactive_enabled` is still true. 2. Confirm that the user separately enabled idle-time check-ins. 3. Confirm that the scheduled job identifier matches `pending_idle_job_id`. 4. Recalculate inactivity from `last_user_activity_at`. 5. Confirm that the current time is inside the approved time window. 6. Check `last_proactive_sent_at` and enforce the configured daily limit. 7. Atomically claim or delete the pending job before sending to prevent duplicate dispatch. 8. Recheck consent immediately before external delivery. 9. If any check fails, delete the stale job and send nothing. 10. When the user opts out, cancel all pending and recurring jobs and clear their identifiers. 11. Add an expiration timestamp and a maximum execution count of one. 12. Record auditable job creation, dispatch, suppression, and cancellation events without storing message content unnecessarily. A safer job shape would use a one-time schedule and carry only a reference to host-side state: ```json { "schedule": { "kind": "at", "at": "<last-user-activity-plus-approved-delay>" }, "payload": { "kind": "idleCheck", "stateKey": "<approved-host-state-key>", "maxExecutions": 1 }, "sessionTarget": "main", "deleteAfterRun": true, "expiresAt": "<short-expiration-timestamp>" } ``` The host must evaluate consent, current inactivity, rate limits, and channel authorization at execution time. Message text should only be generated or dispatched after every condition succeeds. Documentation should not present a repeating schedule as the concrete example for behavior that is intended to execute once. ]]>
