Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/workflows/cli-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,29 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
go-version: '1.27.2'
cache: true
- name: Verify security toolchain
shell: bash
run: ./scripts/check-toolchain.sh
- name: Install vulnerability scanner
shell: bash
run: go install golang.org/x/vuln/cmd/govulncheck@v1.9.0
- name: Build release
shell: bash
env:
GOOS: ${{ matrix.target }}
GOARCH: ${{ matrix.arch }}
CGO_ENABLED: ${{ matrix.cgo }}
run: |
./scripts/check-toolchain.sh
govulncheck ./cmd/line ./pkg/line/... ./pkg/e2ee ./pkg
mkdir -p dist
extension=""
if [ "$GOOS" = "windows" ]; then extension=".exe"; fi
go build -trimpath -ldflags "-s -w -X main.version=$GITHUB_REF_NAME -X main.distribution=standalone" -o "dist/line$extension" ./cmd/line
go build -trimpath -ldflags "-w -X main.version=$GITHUB_REF_NAME -X main.distribution=standalone" -o "dist/line$extension" ./cmd/line
go version -m "dist/line$extension"
govulncheck -mode=binary "dist/line$extension"
cp LICENSE README.md CLI.md dist/
cd dist
if command -v sha256sum >/dev/null; then
Expand Down
24 changes: 21 additions & 3 deletions .github/workflows/cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ on:
permissions:
contents: read

env:
LINE_CLI_LIVE_READ: "0"
LINE_CLI_LIVE_WATCH: "0"
LINE_CLI_LIVE_SECURITY_READ: "0"
LINE_CLI_LIVE_SECURITY_SEND: "0"

jobs:
crypto:
name: Runner and LTSM regressions
Expand All @@ -19,8 +25,11 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
go-version: '1.27.2'
cache: true
- name: Verify security toolchain
shell: bash
run: ./scripts/check-toolchain.sh
- name: Test signing and encryption runtime
run: go test -count=1 -timeout=20m ./pkg ./pkg/ltsm

Expand All @@ -34,8 +43,11 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
go-version: '1.27.2'
cache: true
- name: Verify security toolchain
shell: bash
run: ./scripts/check-toolchain.sh
- name: Test CLI and protocol packages
shell: bash
run: |
Expand Down Expand Up @@ -77,7 +89,13 @@ jobs:
run: go test -v -count=1 ./internal/session -run '^TestDPAPI'
- name: Build CLI
shell: bash
run: go build -trimpath -o bin/ ./cmd/line
run: ./build.sh
- name: Scan source and built binary
shell: bash
run: |
binary=./bin/line
if [ "$RUNNER_OS" = "Windows" ]; then binary=./bin/line.exe; fi
./scripts/security-check.sh "$binary"
- name: Validate POSIX installer
shell: bash
run: sh -n scripts/install-release.sh
Expand Down
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This repository contains no Matrix connector or homeserver integration.

## Build and checks

Requirements: Go 1.26+. macOS needs CGO and Xcode Command Line Tools for Keychain.
Requirements: Go 1.27.2. macOS needs CGO and Xcode Command Line Tools for Keychain.
Linux and Windows support `CGO_ENABLED=0`. No libolm or ffmpeg is required.

```sh
Expand Down Expand Up @@ -43,7 +43,10 @@ integration requirements, and CI coverage.

## Behavior and validation

Preserve explicit capability checks before plaintext fallback. Missing or malformed
Require encryption by default; plaintext needs per-command `--allow-plaintext`
or an affirmative interactive confirmation plus explicit account/server capability
evidence. Scripts, JSON output/errors, and `--stdin` never prompt. Preserve explicit
capability checks before plaintext fallback. Missing or malformed
keys and transport failures must not silently downgrade encryption. Only an
explicit send may register a group key, and it needs complete membership.
Remote mutations are attempted once with a persisted request sequence; reads can
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ See [JSON fields and exit codes](docs/CLI.md#json-output-and-exit-codes) and

- **Encryption:** Letter Sealing is used when supported. Missing or malformed keys
and transport failures never silently downgrade an encrypted send to plaintext.
JSON send results report encryption status.
When LINE explicitly reports encryption unavailable, terminal sends ask for
confirmation; scripts require `--allow-plaintext`. JSON send results report
encryption status.
- **Storage:** macOS Keychain, Linux AES-GCM session files with keys in Secret
Service, or Windows current-user DPAPI. Headless Linux uses host-key storage.
- **Session recovery:** access tokens refresh automatically when saved refresh
Expand Down Expand Up @@ -240,7 +242,7 @@ error. Omit passwords, tokens, QR login values, and private messages.

## Build and contribute

Source builds require Git and **Go 1.26+**. macOS also needs Xcode Command Line
Source builds require Git and **Go 1.27.2**. macOS also needs Xcode Command Line
Tools and CGO for Keychain support. On macOS or Linux:

```sh
Expand Down
3 changes: 2 additions & 1 deletion build.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/bin/sh
set -eu
cd "$(dirname "$0")"
./scripts/check-toolchain.sh
version=${VERSION:-$(git describe --tags --match 'v*' --match 'cli-v*' --always --dirty 2>/dev/null || printf dev)}
mkdir -p bin
"${GO:-go}" build -trimpath -ldflags "-s -w -X main.version=$version" -o bin/ "$@" ./cmd/line
"${GO:-go}" build -trimpath -ldflags "-w -X main.version=$version" -o bin/ "$@" ./cmd/line
6 changes: 3 additions & 3 deletions cmd/line/signals_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func TestSignalInputHelper(t *testing.T) {
manager := session.NewManager(store)
manager.NewClient = func(string) session.API { return &cancelledSendAPI{cancel: func() {}, confirmed: true} }
a := &cli.App{Context: s.ctx, Out: os.Stdout, Err: os.Stderr, Manager: manager, Lock: func() (func(), error) { return func() {}, nil }}
err = a.Run([]string{"--error-format=json", "send", "id:c-group", "--text", "synthetic", "--json"})
err = a.Run([]string{"--error-format=json", "send", "id:c-group", "--allow-plaintext", "--text", "synthetic", "--json"})
case "poll":
io.WriteString(os.Stderr, "Waiting for scan\n")
<-s.ctx.Done()
Expand Down Expand Up @@ -179,10 +179,10 @@ func TestCommandExitPreservesCancelledMutationDetails(t *testing.T) {
}
var want string
if confirmed {
_, err = client.SendFileContext(ctx, "c-group", messaging.Attachment{Name: "test.txt"}, "")
_, err = client.SendFileWithOptionsContext(ctx, "c-group", messaging.Attachment{Name: "test.txt"}, "", messaging.SendOptions{AllowPlaintext: true})
want = "message 123 was created"
} else {
_, err = client.SendContext(ctx, "c-group", "synthetic")
_, err = client.SendMentionsWithOptionsContext(ctx, "c-group", "synthetic", "", nil, messaging.SendOptions{AllowPlaintext: true})
want = "delivery may have occurred"
}
var out bytes.Buffer
Expand Down
47 changes: 43 additions & 4 deletions docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,20 +369,59 @@ video, and audio sent with `--file` appear as ordinary files.
20 MiB, 40 million decoded pixels, and 16384 pixels on either axis. It applies
EXIF orientation, removes metadata, flattens transparency onto white, and converts
to JPEG at quality 60 with at most 1280×1280 total pixels (aspect ratio preserved).
Encrypted sends also upload a JPEG preview fitting 384×384 at quality 30.
Use `--file` to preserve original bytes. GIF, animated PNG, BMP, WebP, and native
audio/video sends are unsupported. Image preparation is local and precedes session
access. `--image` cannot be combined with text, stdin, file, mentions, or
`--reply-to`: Chrome's image path does not establish reply support.

Encrypted `--image` sends are disabled because the current full-image/preview
format reuses an encryption stream. They fail before uploads or sends. Generic
`--file` sends preserve original bytes and remain available with Letter Sealing.
Image sends use the server's explicit encrypted/plaintext media capability.
A missing or invalid capability fails locally before mutation. Full encrypted
upload, preview upload, and message send each happen once. A failed upload after
plaintext message creation retains that confirmed message ID in structured errors;
A missing or invalid capability fails locally before mutation. Plaintext-capable
images require interactive confirmation or `--allow-plaintext`; message creation
and upload each happen once.
A failed upload after message creation retains that confirmed ID in structured errors;
unknown outcomes require checking LINE before retrying. See
[image protocol evidence](protocol/endpoints/image-send.md) for details. Live
interoperability has not been verified.

Sending requires Letter Sealing by default. If the account or server explicitly
reports it unavailable, a terminal send asks for consent before any remote action:

```text
This chat sends files without end-to-end encryption. LINE can read the file.
Send this file? [y/N]:
```

Enter `y` or `yes` to permit this send; Enter, `n`, or any other answer cancels.
The confirmation applies only to the current command. The session lock is
released while waiting; the account and capabilities are checked again afterward.
Encrypted sends do not ask. The prompt names the message, file, or image.

Scripts, `--json`, `--error-format=json`, and `--stdin` never prompt. They return
`encryption_required` without sending unless `--allow-plaintext` was supplied.
Use that flag on a single command to permit content visible to LINE after
HTTPS termination. This flag applies to text, stdin, mentions, replies, files,
and plaintext-capable images; it still uses encryption when available. It does
not override the encrypted-image restriction or authorize fallback on errors.

For `--file`, the server's file-specific media capability must also permit
encryption. A chat can support encrypted text while allowing only plaintext
files. In that case, the default file send stops before upload or message creation;
Interactive confirmation or `--allow-plaintext` is required to send the file
without Letter Sealing.
Missing, unknown, or unreadable media capabilities never authorize plaintext.
The CLI names the affected message, file, or image and explains the next step
without protocol details. For example, a script without consent reports:

```text
This chat doesn't support end-to-end encryption for this file. Nothing was sent.
To send it anyway, add --allow-plaintext. LINE can read the file.
```

Send confirmations use `end-to-end encrypted` or `not end-to-end encrypted`.

The CLI uses Letter Sealing when the conversation supports it. Missing keys,
incomplete group membership, or a transport failure stop an encrypted send
instead of silently sending plaintext. An explicit group send can register a
Expand Down
8 changes: 8 additions & 0 deletions docs/ERRORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,11 @@ preview (`preview_upload`), and `send` separately. An upload may leave an object
without creating a message. Plain image upload follows message creation, so its
failure retains the confirmed message receipt just like a generic-file upload.
Never infer that an upload or send can safely be retried from a transport error.

Send policy errors: `encryption_required` means Letter Sealing is unavailable
and the command did not permit plaintext. Interactive terminal sends offer
per-command confirmation before remote actions; declining returns `cancelled`.
Scripts, JSON output/errors, and `--stdin` require `--allow-plaintext` and never
prompt. `encrypted_image_unavailable` means
native encrypted image sending is disabled until a secure compatible preview
format is established. Neither error authorizes an automatic resend or downgrade.
Loading
Loading