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
2
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.No risks found
Could it delete files or keep running?Looks for broad file deletion, disk overwrites, and programs set to start automatically.Risks found: 1
Medium risk

Examples can write chat and approval events to unprotected local files indefinitely

Source references: 5
What we found

Examples background multiple consumers and redirect output to `.ndjson` files without an event limit or timeout, while the documented default is unlimited. Message content is converted to human-readable text, and Approval output includes statuses, user identifiers, and approval/task identifiers.

Why this matters

As events continue arriving, the files can grow until disk space is exhausted and can retain chat content and business approval metadata locally. These instructions specify no file permissions, encryption, rotation, or deletion.

The active examples redirect chat and approval streams to relative NDJSON files without event-count or timeout bounds; the documented defaults allow those consumers to run and accumulate data indefinitely. Records can include readable message content, user identifiers, approval/task identifiers, and status. The source does not establish the resulting file permissions, so “unprotected” is not proven, but persistent plaintext storage creates plausible privacy, disk-usage, and retention risks. The user can require bounds, a restricted directory, and an explicit permissions/cleanup policy.

SKILL.md:32In the instructionsOpen original file
| `--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) |
Show 4 other places
SKILL.md:54In the instructionsOpen original file
# 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
references/lark-event-im.md:30In the instructionsOpen original file
**`.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-approval.md:123In the instructionsOpen original file
|---|---|---|| `approval_code` | string | Approval definition code; not a subscription dimension || `instance_code` | string | Approval instance code || `task_id` | string | Approval task id || `external_id` | string | Third-party approval external id, when present || `task_external_id` | string | Third-party task external id, when emitted || `assigned_user` | object | Task assignee or operator user IDs, omitted for automatic flows without an operator || `assigned_user.open_id` | string (open_id) | Task assignee or operator open_id, when present || `assigned_user.union_id` | string (union_id) | Task assignee or operator union_id, when present || `assigned_user.user_id` | string (user_id) | Task assignee or operator tenant user_id, when present || `status` | string enum | `REVERTED`, `PENDING`, `APPROVED`, `REJECTED`, `TRANSFERRED`, `ROLLBACK`, `DONE`, `OVERTIME_CLOSE`, `OVERTIME_RECOVER` || `operate_time` | string (timestamp_ms) | Status change time |
references/lark-event-approval.md:152In the instructionsOpen original file
# Broad approval status listening:# run both EventKeys as separate processes; omit subscription_type so each registers both relations.lark-cli event consume approval.instance.status_changed_v4 \  --as user > approval-instance.ndjson &lark-cli event consume approval.task.status_changed_v4 \  --as user > approval-task.ndjson &wait
Could it bypass safety checks?Looks for skipped website security checks, excessive file access, or actions that skip your approval.Risks found: 1
Medium risk

An ambiguous Approval request can create broad account subscriptions that persist after exit

Source references: 5
What we found

When the user specifies neither instance versus task nor involved versus managed approvals, the instructions allow both EventKeys and default to both relations. They say to ask only if registering both would be materially harmful. Each consumer then sends two server-side registrations, and no normal exit mode removes them.

Why this matters

A seemingly temporary listener can make a lasting change to the user's Lark subscription state and broaden future delivery to both involved and managed approvals. Stopping the local process does not stop future delivery; separate cancellation is required.

This is active operational guidance, not a warning or counterexample. A vague request for approval-status events may be expanded to both instance and task EventKeys; if the relation is ambiguous and broad listening is deemed acceptable, omitting the parameter registers both involved and managed relations for each key. Normal exit does not remove them, so ambiguous authorization can leave persistent account subscriptions. The user can require explicit confirmation of each EventKey, relation, and persistence before registration.

references/lark-event-approval.md:55In the instructionsOpen original file
| 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 4 other places
references/lark-event-approval.md:79In the instructionsOpen original file
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 instructionsOpen original file
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:85In the instructionsOpen original file
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:91In the instructionsOpen original file
`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`.
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

The Skill instructs an agent to subscribe to Lark events through an installed `lark-cli` and continuously emit them as NDJSON; by default, neither the event count nor runtime is bounded.

View source
SKILL.md:21In the instructionsOpen original file
| `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 instructionsOpen original file
| `--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 require user or bot identity and read permissions. For example, Approval requires user identity, VC events require meeting, note, or recording read scopes, and whiteboard subscription additionally requires manage access to the target whiteboard.

View source
references/lark-event-approval.md:63In the instructionsOpen original file
## Scopes & auth| EventKey | Scope | Auth ||---|---|---|| `approval.instance.status_changed_v4` | `approval:instance:read` | user || `approval.task.status_changed_v4` | `approval:task:read` | user |
references/lark-event-vc.md:23In the instructionsOpen original file
|---|---|---|| `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 |
references/lark-event-whiteboard.md:17In the instructionsOpen original file
|---|---|---|| `board.whiteboard.updated_v1` | `board:whiteboard:node:read` | user, bot |Supports `--as user` or `--as bot`. The caller must have **manage** access to the target whiteboard, otherwise the subscribe OAPI returns 403 and `event consume` exits with an auth error before listening.

Some events call Lark APIs to create server-side subscriptions before listening. Minutes, VC, and whiteboard subscriptions are described as being removed on graceful exit, while Approval and Task subscriptions are not cleaned up by this command.

View source
references/lark-event-minutes.md:11In the instructionsOpen original file
This key uses a **Custom schema** (flat output at `.xxx`) and carries a **PreConsume hook** that auto-subscribes / unsubscribes via OAPI on first / last consumer.
references/lark-event-approval.md:91In the instructionsOpen original file
`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-task.md:28In the instructionsOpen original file
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.

The supplied material contains usage instructions and references but no implementation of `lark-cli`, so these lines do not independently verify how authentication is stored, which network destinations are contacted, or whether the stated path restriction is actually enforced.

View source
SKILL.md:6In the instructionsOpen original file
metadata:  requires:    bins: ["lark-cli"]  cliHelp: "lark-cli event --help"---
SKILL.md:34In the instructionsOpen original file
| `--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 |
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 instructionsOpen original file
```bash# Default: stream every event for the key (no filter, no projection)
SKILL.md:67In the instructionsOpen original file
## Subprocess contract
SKILL.md:75In the instructionsOpen original file
`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 instructionsOpen original file
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)
3d15881395e72a229f3b826d568699ae7bb50db31272e346ca881597a94fa116