Skip to content
Report library
Purpose / Other

Lark Event Skill Security Audit

What the author says it does (original text)

Lark/Feishu real-time event listening / subscribing / consuming: stream events as NDJSON via `lark-cli event consume <EventKey>` (covers IM messages/reactions/chat changes, Approval status changes, Task updates, VC meeting started/joined/ended, Minutes generated, Whiteboard updated, etc.). Use for Lark bots, real-time message processing, long-running subscribers, streaming webhook/push handlers. S

Independent security check

Security risks found

Files checked
8
Risks found
3
Could it run dangerous commands?Looks for programs run straight after downloading, remote control of your computer, and hidden commands.No risks found
Could it expose your files or keys?Looks for uploads of files containing passwords or keys, and keys written directly in the code.Risks found: 1
Medium risk

Examples write potentially sensitive message content and identifiers from unbounded streams into plaintext local files

Source references: 4
What we found

The Skill shows two background IM consumers with no event limit or timeout and redirects them directly to receive.ndjson and reaction.ndjson. Received-message content can contain readable message text, while sender_id and chat_id identify senders and conversations.

Why this matters

For as long as the processes run, chat content, reactions, and identifiers accumulate in workspace files. Other local users, backup or synchronization tools, and later processes may read them, while disk usage continues to grow.

The risk is supported, although the commands are examples and take effect only if an agent follows them. They redirect two background event streams to plaintext NDJSON files without an event or time bound; the documented defaults make the run unlimited. Message events can expose readable content plus sender and chat identifiers, so sensitive communications may accumulate on disk. Users can require bounds, minimal field projection, and advance confirmation of storage location, permissions, retention, and deletion.

SKILL.md:54In the instructions
# Consume multiple EventKeys concurrently (one shape per process, no dispatcher)lark-cli event consume im.message.receive_v1          --as bot > receive.ndjson &lark-cli event consume im.message.reaction.created_v1 --as bot > reaction.ndjson &wait
Show 3 other places
SKILL.md:32In the instructions
| `--jq <expr>` | jq expression to filter / transform each event; empty output skips the event || `--max-events N` | Exit after N events. Default 0 = unlimited || `--timeout D` | Exit after duration D (e.g. `30s`, `2m`). Default 0 = no timeout. Whichever of `--max-events` / `--timeout` fires first wins || `--output-dir <dir>` | Write each event as a file (relative paths only; prevents traversal) || `--quiet` | Suppress ready/exit markers and per-event stderr diagnostics, including drop warnings. This can hide event loss. **AI should not use this** — it removes readiness and integrity signals || `--as user\|bot\|auto` | Identity for the session (see lark-shared) |
references/lark-event-im.md:28In the instructions
**sender_id is open_id only**: the event payload carries no display name. Call the contact API separately if you need the sender's name.**`.content` shape depends on `message_type`** (this key uses a flat Custom schema; see [`events/im/message_receive.go`](../../../events/im/message_receive.go)):| message_type | `.content` shape | How to read ||---|---|---|| `text` / `post` / `image` / `file` / `audio` / `sticker` / `share_chat` / `share_user` / `media` / `system` | Human-readable text (convertlib-processed; `@mentions` resolved to display names) | Use `.content` directly || `interactive` (card) | Raw card JSON string (structured actions can't be losslessly flattened) | `.content \| fromjson` to get the card object |
references/lark-event-im.md:62In the instructions
# group onlylark-cli event consume im.message.receive_v1 --as bot \  --jq 'select(.chat_type=="group") | {chat: .chat_id, from: .sender_id, msg: .content}'```
Could it delete files or keep running?Looks for broad file deletion, disk overwrites, and programs set to start automatically.No risks found
Could it bypass safety checks?Looks for skipped website security checks, excessive file access, or actions that skip your approval.Risks found: 2
Medium risk

An ambiguous approval-listening request can create broad server subscriptions that are not automatically removed

Source references: 4
What we found

When the user does not specify an approval relationship, the Skill recommends omitting subscription_type, which registers both approvals involving the user and approvals managed by the user. These subscriptions remain after the process exits and must be canceled outside event consume.

Why this matters

A temporary listening request can permanently broaden the approval events delivered for that user. Stopping the process, timing out, or reaching an event limit does not restore the prior state, and approval status and participant identifiers may continue to be delivered.

The risk is supported. The live guidance permits omitting `subscription_type` when the relation is ambiguous, which registers both “involved” and “managed” server-side relations for an EventKey. Those relations remain even after a normal consumer exit. A vague request to monitor approval status can therefore create broader, persistent delivery than the user expects. Users can require explicit scope confirmation before registration and a documented, verifiable cancellation method.

references/lark-event-approval.md:49In the instructions
User-intent inference:| User intent | EventKey(s) | `subscription_type` ||---|---|---|| Mentions approval instances, approval forms, approval order/status, or "instance status" | `approval.instance.status_changed_v4` | infer from relation words below || Mentions approval tasks, approval todo items, approver operations, or "task status" | `approval.task.status_changed_v4` | infer from relation words below || Says "approval status changes/events" without saying task vs instance | both EventKeys | infer from relation words below || Says "my approvals", "approvals involving me", "I requested/approved", "待我审批", "我发起/我参与" | requested EventKey(s) | `INVOLVED_APPROVAL` || Says "approvals I manage", "managed definitions", "definitions managed by me", "我管理的审批定义" | requested EventKey(s) | `MANAGED_APPROVAL` || Explicitly asks for both involved and managed, or says "all approval subscriptions" | requested EventKey(s), or both if EventKey is also ambiguous | omit `subscription_type`, or pass both values in one `-p` || Relation is ambiguous and the user wants broad coverage | requested EventKey(s), or both if EventKey is also ambiguous | omit `subscription_type` so PreConsume registers both |If the user's wording omits the relation and broad listening is acceptable, omit `subscription_type`. Ask only when registering both relations would be materially harmful.
Show 3 other places
references/lark-event-approval.md:79In the instructions
For each resolved `subscription_type`, PreConsume sends one request body:```json{"subscription_type":"INVOLVED_APPROVAL"}```If `subscription_type` is omitted, PreConsume sends two registration requests for that EventKey: one with `INVOLVED_APPROVAL`, then one with `MANAGED_APPROVAL`. If listening to both instance and task events, run two consumers; each consumer may omit `subscription_type` to register both relations for its own EventKey.
references/lark-event-approval.md:89In the instructions
Shutdown behavior:`event consume` does not call the Approval unsubscribe APIs when it exits. This applies to graceful exit, Ctrl+C / SIGTERM, stdin EOF, `--timeout`, and `--max-events`.To stop future delivery for a user, cancel the Approval subscription relation outside this consumer. The unsubscribe APIs are separate operations and are not called by `event consume`.
references/lark-event-approval.md:58In the instructions
| Says "approvals I manage", "managed definitions", "definitions managed by me", "我管理的审批定义" | requested EventKey(s) | `MANAGED_APPROVAL` || Explicitly asks for both involved and managed, or says "all approval subscriptions" | requested EventKey(s), or both if EventKey is also ambiguous | omit `subscription_type`, or pass both values in one `-p` || Relation is ambiguous and the user wants broad coverage | requested EventKey(s), or both if EventKey is also ambiguous | omit `subscription_type` so PreConsume registers both |If the user's wording omits the relation and broad listening is acceptable, omit `subscription_type`. Ask only when registering both relations would be materially harmful.
Medium risk

Task listening leaves a server-side subscription that this CLI cannot clean up

Source references: 3
What we found

The task consumer calls a subscription API at startup, but the documentation says the current CLI metadata has no matching unsubscribe endpoint. Graceful exit performs no cleanup, and rerunning sends the subscribe request again.

Why this matters

A user may believe ending the listener reversed its effect while the server relationship remains. Future task updates may continue to be delivered, and repeated subscriptions can leave persistent account state that is difficult to verify.

The risk is supported. Starting the task consumer invokes a remote subscription endpoint as the selected user or bot identity. The documentation states that this CLI has no matching unsubscribe operation, normal exit performs no cleanup, and reruns repeat the request. Thus, stopping the local process may leave the server-side relation in place. Users can require the author to explain idempotency, inspection and cancellation procedures, and restrict the identity and task visibility to the minimum needed.

references/lark-event-task.md:28In the instructions
On startup, `event consume` calls:```textPOST /open-apis/task/v2/task_v2/task_subscription?user_id_type=open_id```The Task subscription API has no matching unsubscribe endpoint in the current CLI metadata, so graceful exit has no cleanup call for this EventKey. Re-running the consumer repeats the subscribe call for the selected identity.This EventKey is single-consumer per local bus subscription: start one `event consume task.task.update_user_access_v2` process for a given app/profile/identity at a time.
Show 2 other places
references/lark-event-task.md:17In the instructions
|---|---|---|| `task.task.update_user_access_v2` | `task:task:read` | user, bot |Supports `--as user` or `--as bot`.- `--as user`: receive task updates visible to the current user through authorship, assignment, following, or other access.- `--as bot`: receive task updates for tasks the application is responsible for.
references/lark-event-task.md:36In the instructions
This EventKey is single-consumer per local bus subscription: start one `event consume task.task.update_user_access_v2` process for a given app/profile/identity at a time.
Could it mislead the AI or hide text?Checks the skill instructions for requests to ignore you, influence the report, or hide text in invisible characters.No risks found
Could it change links or payment recipients without asking?Looks for forced referral or payment changes combined with instructions to hide the change.No risks found

Inside this skill

7 instruction sections

This Skill directs lark-cli to subscribe to Lark events and emit each event as NDJSON. By default, neither event count nor runtime is bounded; automatic termination requires an explicit --max-events or --timeout.

View source
SKILL.md:21In the instructions
| `lark-cli event schema <EventKey> [--json]` | Show an EventKey's params and output schema || `lark-cli event consume <EventKey> [flags]` | Blocking consume; events → stdout NDJSON || `lark-cli event status [--json] [--fail-on-orphan]` | Inspect the local bus daemon status |
SKILL.md:32In the instructions
| `--jq <expr>` | jq expression to filter / transform each event; empty output skips the event || `--max-events N` | Exit after N events. Default 0 = unlimited || `--timeout D` | Exit after duration D (e.g. `30s`, `2m`). Default 0 = no timeout. Whichever of `--max-events` / `--timeout` fires first wins || `--output-dir <dir>` | Write each event as a file (relative paths only; prevents traversal) |

Different events use a user or bot identity and require permissions to read messages, approvals, tasks, meetings, minutes, or whiteboards. Some event types also call Lark APIs to create server-side subscriptions before listening.

View source
references/lark-event-approval.md:14In the instructions
Both keys carry a **PreConsume hook** that subscribes the current authorized user through the Approval subscription APIs before listening. The consumer intentionally does **not** unsubscribe on exit; the server-side Approval subscription relation remains until it is canceled outside `event consume`. These keys require `--as user`.
references/lark-event-task.md:15In the instructions
| EventKey | Scope | Auth ||---|---|---|| `task.task.update_user_access_v2` | `task:task:read` | user, bot |Supports `--as user` or `--as bot`.
references/lark-event-vc.md:23In the instructions
|---|---|---|| `vc.meeting.participant_meeting_started_v1` | `vc:meeting.meetingevent:read` | user || `vc.meeting.participant_meeting_joined_v1` | `vc:meeting.meetingevent:read` | user || `vc.meeting.participant_meeting_ended_v1` | `vc:meeting.meetingevent:read` | user || `vc.note.generated_v1` | `vc:note:read` | user || `vc.recording.recording_started_v1` | `vc:recording:read` | user || `vc.recording.recording_transcript_generated_v1` | `vc:recording:read` | user || `vc.recording.recording_ended_v1` | `vc:recording:read` | user |

The Skill supports jq filtering and transformation, but a runtime jq error drops the affected event without stopping the listener; diagnosis appears only as a stderr warning. A user relying on the stream for approvals, tasks, or messages must also monitor stderr.

View source
SKILL.md:139In the instructions
`event consume` runs Process hooks that may pre-decode some payload fields (flattening V2 envelopes, rendering `.content` to plain text, etc.) — behavior differs from raw OAPI. **Always read the field's `description` before writing jq**, especially for generic field names like `content` / `data` / `body` / `payload`.**Why it matters**: blindly applying `fromjson` to an already-decoded text field makes jq error on every event and silently drop it — the consumer looks alive but emits nothing, with only a single `WARN` line buried on stderr. (This is the general behavior: any jq runtime error skips the event with a one-line WARN; the loop does not abort.)
Start here · InstructionsSKILL.md
lark-event
Lines connect the instruction file to its sections, not an observed execution order. Select a section to read the source.

File reference map

References: 13
Files making referencesReferenced content
Lines show actual file references, not execution order. Select a node to highlight its connections and inspect the files and source locations. Dashed lines include files that still need locating.
Files and check records8 files

Coverage and gaps

Content covered in each file

These are the source ranges included in this check, not a guarantee that every issue has been resolved.

  • SKILL.mdFull text included
  • references/lark-event-application.mdFull text included
  • references/lark-event-approval.mdFull text included
  • references/lark-event-im.mdFull text included
  • references/lark-event-minutes.mdFull text included
  • references/lark-event-task.mdFull text included
  • references/lark-event-vc.mdFull text included
  • references/lark-event-whiteboard.mdFull text included

This report is for the version above. We read the available code and instructions without running the skill or checking extra packages it installs. This is not a promise of safety: a different version or setup may behave differently.

  • SKILL.mdInstructions
  • references/lark-event-application.mdSupporting file
  • references/lark-event-approval.mdSupporting file
  • references/lark-event-im.mdSupporting file
  • references/lark-event-minutes.mdSupporting file
  • references/lark-event-task.mdSupporting file
  • references/lark-event-vc.mdSupporting file
  • references/lark-event-whiteboard.mdSupporting file

Operations mentioned in code and instructions

Run commands
SKILL.md:41In the instructions
```bash# Default: stream every event for the key (no filter, no projection)
SKILL.md:67In the instructions
## Subprocess contract
SKILL.md:75In the instructions
`event consume` treats stdin close as a shutdown signal (wired for AI subprocess callers). **Bounded runs are exempt: when `--max-events` or `--timeout` is set (> 0), stdin EOF is ignored and the run exits only via its own bound, timeout, or SIGTERM.** For unbounded runs, `< /dev/null` / `nohup` / systemd's default `StandardInput=null` will cause an immediate graceful exit (stderr `reason: signal`). To keep an unbounded run alive:
Connect to websites
references/lark-event-whiteboard.md:27In the instructions
Whiteboard token can be obtained via the docs OAPI [list document blocks](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/document-docx/docx-v1/document-block/list): the block whose `block_type=43` is a whiteboard, and `block.token` is the whiteboard token.
Lines read
772
File checksum (to compare versions)
fdbffcb11bfa8007ad83591383621cdef8b7ac0a4ab3e8ca62a5dad1c34ea8da