mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-05-11 14:05:52 -03:00
imessage: drop SMS/RCS by default, opt-in via IMESSAGE_ALLOW_SMS
SMS sender IDs are spoofable; iMessage is Apple-ID-authenticated and end-to-end encrypted. The plugin previously treated both identically, so a forged SMS from the owner's own number would match SELF, bypass the access gate, and inherit owner-level trust — including permission approval. handleInbound now drops anything with service != 'iMessage' unless IMESSAGE_ALLOW_SMS=true. Default is the safe path; users who want SMS can opt in after reading the warning in README.
This commit is contained in:
parent
c4274521de
commit
60c3fc36ed
@ -62,6 +62,7 @@ Handles are phone numbers (`+15551234567`) or Apple ID emails (`them@icloud.com`
|
||||
| Variable | Default | Effect |
|
||||
| --- | --- | --- |
|
||||
| `IMESSAGE_APPEND_SIGNATURE` | `true` | Appends `\nSent by Claude` to outbound messages. Set to `false` to disable. |
|
||||
| `IMESSAGE_ALLOW_SMS` | `false` | Accept inbound SMS/RCS in addition to iMessage. **Off by default because SMS sender IDs are spoofable** — a forged SMS from your own number would otherwise bypass access control. Only enable if you understand the risk. |
|
||||
| `IMESSAGE_ACCESS_MODE` | — | Set to `static` to disable runtime pairing and read `access.json` only. |
|
||||
| `IMESSAGE_STATE_DIR` | `~/.claude/channels/imessage` | Override where `access.json` and pairing state live. |
|
||||
|
||||
|
||||
@ -32,6 +32,10 @@ import { join, basename, sep } from 'path'
|
||||
|
||||
const STATIC = process.env.IMESSAGE_ACCESS_MODE === 'static'
|
||||
const APPEND_SIGNATURE = process.env.IMESSAGE_APPEND_SIGNATURE !== 'false'
|
||||
// SMS sender IDs are spoofable; iMessage is Apple-ID-authenticated. Default
|
||||
// drops SMS/RCS so a forged sender can't reach the gate. Opt in only if you
|
||||
// understand the risk.
|
||||
const ALLOW_SMS = process.env.IMESSAGE_ALLOW_SMS === 'true'
|
||||
const SIGNATURE = '\nSent by Claude'
|
||||
const CHAT_DB = join(homedir(), 'Library', 'Messages', 'chat.db')
|
||||
|
||||
@ -104,6 +108,7 @@ type Row = {
|
||||
date: number
|
||||
is_from_me: number
|
||||
cache_has_attachments: number
|
||||
service: string | null
|
||||
handle_id: string | null
|
||||
chat_guid: string
|
||||
chat_style: number | null
|
||||
@ -113,7 +118,7 @@ const qWatermark = db.query<{ max: number | null }, []>('SELECT MAX(ROWID) AS ma
|
||||
|
||||
const qPoll = db.query<Row, [number]>(`
|
||||
SELECT m.ROWID AS rowid, m.guid, m.text, m.attributedBody, m.date, m.is_from_me,
|
||||
m.cache_has_attachments, h.id AS handle_id, c.guid AS chat_guid, c.style AS chat_style
|
||||
m.cache_has_attachments, m.service, h.id AS handle_id, c.guid AS chat_guid, c.style AS chat_style
|
||||
FROM message m
|
||||
JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
|
||||
JOIN chat c ON c.ROWID = cmj.chat_id
|
||||
@ -124,7 +129,7 @@ const qPoll = db.query<Row, [number]>(`
|
||||
|
||||
const qHistory = db.query<Row, [string, number]>(`
|
||||
SELECT m.ROWID AS rowid, m.guid, m.text, m.attributedBody, m.date, m.is_from_me,
|
||||
m.cache_has_attachments, h.id AS handle_id, c.guid AS chat_guid, c.style AS chat_style
|
||||
m.cache_has_attachments, m.service, h.id AS handle_id, c.guid AS chat_guid, c.style AS chat_style
|
||||
FROM message m
|
||||
JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
|
||||
JOIN chat c ON c.ROWID = cmj.chat_id
|
||||
@ -710,6 +715,7 @@ function expandTilde(p: string): string {
|
||||
|
||||
function handleInbound(r: Row): void {
|
||||
if (!r.chat_guid) return
|
||||
if (!ALLOW_SMS && r.service !== 'iMessage') return
|
||||
|
||||
// style 45 = DM, 43 = group. Drop unknowns rather than risk routing a
|
||||
// group message through the DM gate and leaking a pairing code.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user