Skip to content

feat: update tlon channel/plugin to be more fully featured#21208

Merged
jalehman merged 31 commits intoopenclaw:mainfrom
arthyn:feat/update-tlon-plugin
Mar 3, 2026
Merged

feat: update tlon channel/plugin to be more fully featured#21208
jalehman merged 31 commits intoopenclaw:mainfrom
arthyn:feat/update-tlon-plugin

Conversation

@arthyn
Copy link
Contributor

@arthyn arthyn commented Feb 19, 2026

Summary

  • Problem: Tlon plugin has gone through many changes since the original commit in this repo. Since then we've been working in a standalone repo https://github.com/tloncorp/openclaw-tlon
  • Why it matters: Tlon plugin was just barely ready, this is a full-fledged version that supports most of what the other plugins support
  • What changed: Rich text formatting support, image support, thread conversation support, permissioning changes
  • What did NOT change: Auth/onboarding flows, SSRF protection code, core message sending logic

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway
    / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

N/A

User-visible / Behavior Changes

  • Rich text formatting support
  • Image support
  • Thread conversation support
  • Slash command support
  • Bundled skill: allows for doing nearly all operations that you can do through the Tlon app like manage groups, creating channels, managing plugin permissions, modifying profile, etc.
  • Permissioning changes
    • autoAcceptDMInvites: allows the bot to automatically accept DM requests for more permissive scenarios
    • autoAcceptGroupInvites: allows the bot to automatically accept group request for more permissive scenarios
    • ownerShip: defines who "owns" the bot so that any other interactions can pass through the approval system

Security Impact (required)

  • New permissions/capabilities? Yes
  • Secrets/tokens handling changed? No
  • New/changed network calls? Yes - uploadFile makes calls to Tlon's storage API (Memex for hosted, S3 for self-hosted)
  • Command/tool execution surface changed? Yes added Tlon tool for bundled skill
  • Data access scope changed? No
  • If any Yes, explain risk +
    mitigation: Upload calls go through the existing authenticated Tlon session; no new credentials required. Falls back to original URL on failure.

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node.js
  • Model/provider: N/A
  • Integration/channel: Tlon
  • Relevant config: Tlon plugin with ship URL/code configured

Steps

  1. Configure Tlon plugin with valid ship credentials
  2. Send a message with an image URL via the message tool
  3. Verify image appears in Tlon chat
  4. Send a message requesting rich formatting
  5. Receive a message fully formatted

Expected

  • Image uploads to Tlon storage and displays correctly
  • Rich formatting comes through the message correctly

Actual

  • Image uploads and displays correctly ✅
  • Rich formatting comes through the message correctly ✅

Evidence

  • Trace/log snippets

Tests:
✓ extensions/tlon/src/urbit/send.test.ts (1 test)
✓ extensions/tlon/src/monitor/processed-messages.test.ts (1 test)
✓ extensions/tlon/src/urbit/base-url.test.ts (5 tests)
✓ extensions/tlon/src/config-schema.test.ts (2 tests)
✓ extensions/tlon/src/urbit/auth.ssrf.test.ts (2 tests)
Test Files 5 passed (5)
Tests 11 passed (11)

Human Verification (required)

  • Verified scenarios: Rich text formatting (bold, italic, code blocks, headers, lists), image uploads via Wikipedia test image
  • Edge cases checked: N/A
  • What you did NOT verify: Other plugins and extensions (their files are untouched)

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? Yes
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Revert to previous version of extensions/tlon
  • Files/config to restore: extensions/tlon/*
  • Known bad symptoms: None

Risks and Mitigations

  • Risk: Anyone using Tlon plugin could see issues if there are unfound issues
    • Mitigation: Most Tlon users are in touch with us for support (or are getting an OpenClaw instance from us)

Note: This PR was AI-assisted (Claude/OpenClaw).

Tested locally with real Tlon ship locally.

Greptile Summary

Updated Tlon plugin from minimal implementation to full-featured integration with rich text formatting, image support, thread conversations, and bundled Tlon CLI skill. Added comprehensive approval system for managing DM/channel/group access with owner-based permissioning via ownerShip config.

Key changes:

  • Rich text formatting via new story.ts markdown parser supporting bold, italic, code blocks, links, and images
  • Image upload/download with integration to Tlon storage API (@tloncorp/api)
  • Thread conversation support with proper reply handling
  • Approval system (approval.ts) for gating bot access - owner receives notifications for DM requests, channel mentions, and group invites
  • New permissions: autoAcceptDmInvites, autoAcceptGroupInvites, ownerShip
  • Settings store integration for hot-reloadable config
  • Bundled Tlon CLI skill (@tloncorp/tlon-skill) for advanced operations

Critical security issues found:

  • SSRF vulnerabilities in upload.ts:15 and media.ts:59 - both use raw fetch() without the SSRF protection applied elsewhere in the codebase
  • Potential command injection in bundled skill (index.ts:81-84) - user input passed to spawn() without validation

Confidence Score: 2/5

  • PR contains critical security vulnerabilities (SSRF and potential command injection) that must be resolved before merge
  • Score reflects two critical security issues: (1) SSRF vulnerabilities in upload.ts and media.ts that bypass existing protections and could allow internal network access, and (2) potential command injection in the bundled skill. These are high-impact vulnerabilities given the PR's security hardening claims. The functional implementation appears solid with good test coverage, but security gaps are blocking issues.
  • Critical attention needed: extensions/tlon/src/urbit/upload.ts (SSRF), extensions/tlon/src/monitor/media.ts (SSRF), extensions/tlon/index.ts (command injection). Review the SSRF protection pattern from auth.ssrf.test.ts and apply consistently.

Last reviewed commit: 1524619

@openclaw-barnacle openclaw-barnacle bot added channel: tlon Channel integration: tlon size: XL labels Feb 19, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

23 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

@arthyn arthyn force-pushed the feat/update-tlon-plugin branch 2 times, most recently from f058525 to 22d5210 Compare February 20, 2026 04:05
@openclaw-barnacle openclaw-barnacle bot added the docs Improvements or additions to documentation label Feb 21, 2026
Copy link
Contributor

@jalehman jalehman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: PR #21208 — Tlon Extension Expansion

Recommendation: NEEDS WORK

TL;DR

The feature scope is strong, but there are two important issues to address before merge: (1) settings-store overrides cannot enforce empty/cleared authorization values, and (2) the plugin now depends on a mutable GitHub branch at runtime.

What changed and what is good?

  • Major functional expansion of the Tlon extension (rich text, media ingest, thread context, approvals/admin controls, settings hot-reload).
  • Good security direction in media handling: both inbound download and outbound image re-upload use urbitFetch with SSRF guard paths.
  • Added focused tests for SSRF, upload, and security helper behavior.

Security findings

  • Greptile SSRF claim: No direct SSRF bypass found in upload.ts or media.ts. Both paths use urbitFetchfetchWithSsrFGuard with DNS pinning and private-network blocking enabled by default.
  • Greptile command injection claim: No shell command injection in index.ts. Uses spawn(binary, args) (non-shell) with allowlisted subcommands.
  • The access-control override bug below is a real security concern — unintended authorization persistence when operators believe they tightened settings.

Findings

F1 (IMPORTANT): Settings overrides cannot enforce restrictive empty values

extensions/tlon/src/monitor/index.ts lines 304-307, 325-329, 1522-1526, 1549-1556, and migration checks at 255-263 treat empty arrays as "no setting" and fall back to file config. This breaks the documented semantics (dmAllowlist: [] means deny all) and can unintentionally keep broader access enabled.

Same pattern for ownerShip (lines 331-333, 1572-1578) — clearing owner in settings cannot reliably disable owner overrides if file config has one.

Action: Treat [] as an explicit override value (do not fall back to file defaults when the key is present). Distinguish "unset" from "explicitly empty/cleared" in settings values.

F2 (IMPORTANT): Runtime dependency points to mutable GitHub branch

extensions/tlon/package.json:7 adds \"@tloncorp/api\": \"github:tloncorp/api-beta#main\" as a runtime dependency.

Action: Pin to an immutable release/tag or commit SHA to avoid non-reproducible installs and unreviewed behavior drift.

Tests

Ran targeted tests — all pass:

  • extensions/tlon/src/urbit/send.test.ts
  • extensions/tlon/src/monitor/processed-messages.test.ts
  • extensions/tlon/src/urbit/base-url.test.ts
  • extensions/tlon/src/urbit/auth.ssrf.test.ts
  • extensions/tlon/src/config-schema.test.ts
  • extensions/tlon/src/urbit/upload.test.ts
  • extensions/tlon/src/security.test.ts

Gaps: Full repo test matrix not executed. No regression tests for settings override precedence.

Docs & Changelog

  • Docs: up to date (docs/channels/tlon.md updated).
  • Changelog: required — no entry detected.

@jalehman
Copy link
Contributor

Disclosure: the above comment was my overeager openclaw jumping straight to commenting before notifying me of the review comments for me to investigate — working on getting this running myself and will go over the above comments directly.

@jalehman jalehman self-assigned this Feb 27, 2026
@arthyn arthyn requested a review from jalehman February 27, 2026 22:06
@jalehman jalehman force-pushed the feat/update-tlon-plugin branch 2 times, most recently from 748d466 to 0c29d9b Compare March 3, 2026 00:06
arthyn added 17 commits March 2, 2026 16:23
- Add tlon CLI tool registration with binary lookup
- Add approval, media, settings, foreigns, story, upload modules
- Add http-api wrapper for Urbit connection patching
- Update types for defaultAuthorizedShips support
- Fix type compatibility with core plugin SDK
- Stub uploadFile (API not yet available in @tloncorp/api-beta)
- Remove incompatible test files (security, sse-client, upload)
Remove unused Urbit channel client files:
- channel-client.ts
- channel-ops.ts
- context.ts

These were not imported anywhere in the extension.
- Import configureClient and uploadFile from @tloncorp/api
- Implement uploadImageFromUrl using uploadFile
- Configure API client before media uploads
- Update dependency to github:tloncorp/api-beta#main
- Restore context.ts and channel-ops.ts for SSRF support
- Restore sse-client.ts with urbitFetch for SSRF-protected requests
- Add event ack tracking from openclaw-tlon (acks every 20 events)
- Pass ssrfPolicy through authenticate() and UrbitSSEClient
- Fixes security regression from sync with openclaw-tlon
The inlined payload building was missing allowPrivateNetwork field,
which would prevent the setting from being persisted to config.
- Restore channel-client.ts for UrbitChannelClient
- Use UrbitChannelClient with ssrfPolicy in probeAccount
- Ensures account probe respects allowPrivateNetwork setting
ownerShip should always be set as it controls who receives
approval requests and can approve/deny actions.
After restoring SSRF protection, probeAccount uses UrbitChannelClient
instead of @urbit/http-api. The http-api.ts wrapper is no longer needed.
No channel needed - just authenticate and GET /~/name.
Removes UrbitChannelClient, keeping only UrbitSSEClient for monitor.
- Fix SSRF in upload.ts: use urbitFetch with SSRF protection
- Fix SSRF in media.ts: use urbitFetch with SSRF protection
- Add command whitelist to tlon tool to prevent command injection
- Add getDefaultSsrFPolicy() helper for uploads/downloads
- Add authenticateWithRetry() helper with exponential backoff (restores lost logic from openclaw#39)
- Add onReconnect callback to re-authenticate when SSE stream reconnects
- Add UrbitSSEClient.updateCookie() method for proper cookie normalization on reauth
Instead of giving up after maxReconnectAttempts, wait 10 seconds then
reset the counter and keep trying. This ensures the monitor never
permanently disconnects due to temporary network issues.
- security.test.ts: DM allowlist, group invite, bot mention detection, ship normalization
- sse-client.test.ts: subscription handling, cookie updates, reconnection params
- upload.test.ts: image upload with SSRF protection, error handling
- Add extractDmPartnerShip() to extract partner from 'whom' field
- Use partner ship for routing (more reliable than essay.author)
- Explicitly ignore bot's own outbound DM events
- Log mismatch between author and partner for debugging
arthyn and others added 14 commits March 2, 2026 16:23
- Add stripBotMention for proper CommandBody parsing
- Add command authorization logic for owner-only slash commands
- Add CommandAuthorized and CommandSource to context payload
- Store validated account url/code before closure to fix type narrowing
- Fix test type annotations for mode rules
- Add proper Response type cast in sse-client mock
- Use optional chaining for init properties
- Document ownerShip for approval system
- Document autoAcceptDmInvites and autoAcceptGroupInvites
- Update status to reflect rich text and image support
- Add bundled skill section
- Update notes with formatting and image details
- Fix pnpm-lock.yaml conflict
…rk docs

- Correct dmAllowlist: empty means no DMs allowed (not allow all)
- Promote allowPrivateNetwork to its own section with examples
- Add warning about SSRF protection implications
- Add ownerShip to minimal config example (recommended)
- Document that owner is automatically allowed for DMs and channels
- No need to add owner to dmAllowlist or defaultAuthorizedShips
…ence

Align with Matrix docs format:
- Capabilities table for quick feature reference
- Troubleshooting section with common failures
- Configuration reference with all options
- Reactions ARE supported via bundled skill (not missing)
- Add link to skill GitHub repo
- List skill capabilities: contacts, channels, groups, DMs, reactions, settings
Fixes security test failure - Math.random is flagged as weak randomness.
- upload.ts: Use fetchWithSsrFGuard directly instead of urbitFetch to
  preserve full URL path when fetching external images; add release() call
- media.ts: Same fix - use fetchWithSsrFGuard for external media downloads;
  add release() call to clean up resources
- channel.ts: Use urbitFetch for poke API to maintain consistent SSRF
  protection (DNS pinning + redirect handling)
- upload.test.ts: Update mocks to use fetchWithSsrFGuard instead of urbitFetch

Addresses blocking issues from jalehman's review:
1. Fixed incorrect URL being fetched (validateUrbitBaseUrl was stripping path)
2. Fixed missing release() calls that could leak resources
3. Restored guarded fetch semantics for poke operations
@jalehman jalehman force-pushed the feat/update-tlon-plugin branch from 268d5c6 to 541a7f5 Compare March 3, 2026 00:23
@jalehman jalehman merged commit f468274 into openclaw:main Mar 3, 2026
9 checks passed
dawi369 pushed a commit to dawi369/davis that referenced this pull request Mar 3, 2026
…21208)

* feat(tlon): sync with openclaw-tlon master

- Add tlon CLI tool registration with binary lookup
- Add approval, media, settings, foreigns, story, upload modules
- Add http-api wrapper for Urbit connection patching
- Update types for defaultAuthorizedShips support
- Fix type compatibility with core plugin SDK
- Stub uploadFile (API not yet available in @tloncorp/api-beta)
- Remove incompatible test files (security, sse-client, upload)

* chore(tlon): remove dead code

Remove unused Urbit channel client files:
- channel-client.ts
- channel-ops.ts
- context.ts

These were not imported anywhere in the extension.

* feat(tlon): add image upload support via @tloncorp/api

- Import configureClient and uploadFile from @tloncorp/api
- Implement uploadImageFromUrl using uploadFile
- Configure API client before media uploads
- Update dependency to github:tloncorp/api-beta#main

* fix(tlon): restore SSRF protection with event ack tracking

- Restore context.ts and channel-ops.ts for SSRF support
- Restore sse-client.ts with urbitFetch for SSRF-protected requests
- Add event ack tracking from openclaw-tlon (acks every 20 events)
- Pass ssrfPolicy through authenticate() and UrbitSSEClient
- Fixes security regression from sync with openclaw-tlon

* fix(tlon): restore buildTlonAccountFields for allowPrivateNetwork

The inlined payload building was missing allowPrivateNetwork field,
which would prevent the setting from being persisted to config.

* fix(tlon): restore SSRF protection in probeAccount

- Restore channel-client.ts for UrbitChannelClient
- Use UrbitChannelClient with ssrfPolicy in probeAccount
- Ensures account probe respects allowPrivateNetwork setting

* feat(tlon): add ownerShip to setup flow

ownerShip should always be set as it controls who receives
approval requests and can approve/deny actions.

* chore(tlon): remove unused http-api.ts

After restoring SSRF protection, probeAccount uses UrbitChannelClient
instead of @urbit/http-api. The http-api.ts wrapper is no longer needed.

* refactor(tlon): simplify probeAccount to direct /~/name request

No channel needed - just authenticate and GET /~/name.
Removes UrbitChannelClient, keeping only UrbitSSEClient for monitor.

* chore(tlon): add logging for event acks

* chore(tlon): lower ack threshold to 5 for testing

* fix(tlon): address security review issues

- Fix SSRF in upload.ts: use urbitFetch with SSRF protection
- Fix SSRF in media.ts: use urbitFetch with SSRF protection
- Add command whitelist to tlon tool to prevent command injection
- Add getDefaultSsrFPolicy() helper for uploads/downloads

* fix(tlon): restore auth retry and add reauth on SSE reconnect

- Add authenticateWithRetry() helper with exponential backoff (restores lost logic from openclaw#39)
- Add onReconnect callback to re-authenticate when SSE stream reconnects
- Add UrbitSSEClient.updateCookie() method for proper cookie normalization on reauth

* fix(tlon): add infinite reconnect with reset after max attempts

Instead of giving up after maxReconnectAttempts, wait 10 seconds then
reset the counter and keep trying. This ensures the monitor never
permanently disconnects due to temporary network issues.

* test(tlon): restore security, sse-client, and upload tests

- security.test.ts: DM allowlist, group invite, bot mention detection, ship normalization
- sse-client.test.ts: subscription handling, cookie updates, reconnection params
- upload.test.ts: image upload with SSRF protection, error handling

* fix(tlon): restore DM partner ship extraction for proper routing

- Add extractDmPartnerShip() to extract partner from 'whom' field
- Use partner ship for routing (more reliable than essay.author)
- Explicitly ignore bot's own outbound DM events
- Log mismatch between author and partner for debugging

* chore(tlon): restore ack threshold to 20

* chore(tlon): sync slash commands support from upstream

- Add stripBotMention for proper CommandBody parsing
- Add command authorization logic for owner-only slash commands
- Add CommandAuthorized and CommandSource to context payload

* fix(tlon): resolve TypeScript errors in tests and monitor

- Store validated account url/code before closure to fix type narrowing
- Fix test type annotations for mode rules
- Add proper Response type cast in sse-client mock
- Use optional chaining for init properties

* docs(tlon): update docs for new config options and capabilities

- Document ownerShip for approval system
- Document autoAcceptDmInvites and autoAcceptGroupInvites
- Update status to reflect rich text and image support
- Add bundled skill section
- Update notes with formatting and image details
- Fix pnpm-lock.yaml conflict

* docs(tlon): fix dmAllowlist description and improve allowPrivateNetwork docs

- Correct dmAllowlist: empty means no DMs allowed (not allow all)
- Promote allowPrivateNetwork to its own section with examples
- Add warning about SSRF protection implications

* docs(tlon): clarify ownerShip is auto-authorized everywhere

- Add ownerShip to minimal config example (recommended)
- Document that owner is automatically allowed for DMs and channels
- No need to add owner to dmAllowlist or defaultAuthorizedShips

* docs(tlon): add capabilities table, troubleshooting, and config reference

Align with Matrix docs format:
- Capabilities table for quick feature reference
- Troubleshooting section with common failures
- Configuration reference with all options

* docs(tlon): fix reactions status and expand bundled skill section

- Reactions ARE supported via bundled skill (not missing)
- Add link to skill GitHub repo
- List skill capabilities: contacts, channels, groups, DMs, reactions, settings

* fix(tlon): use crypto.randomUUID instead of Math.random for channel ID

Fixes security test failure - Math.random is flagged as weak randomness.

* docs: fix markdown lint - add blank line before </Step>

* fix: address PR review issues for tlon plugin

- upload.ts: Use fetchWithSsrFGuard directly instead of urbitFetch to
  preserve full URL path when fetching external images; add release() call
- media.ts: Same fix - use fetchWithSsrFGuard for external media downloads;
  add release() call to clean up resources
- channel.ts: Use urbitFetch for poke API to maintain consistent SSRF
  protection (DNS pinning + redirect handling)
- upload.test.ts: Update mocks to use fetchWithSsrFGuard instead of urbitFetch

Addresses blocking issues from jalehman's review:
1. Fixed incorrect URL being fetched (validateUrbitBaseUrl was stripping path)
2. Fixed missing release() calls that could leak resources
3. Restored guarded fetch semantics for poke operations

* docs: add tlon changelog fragment

* style: format tlon monitor

* fix: align tlon lockfile and sse id generation

* docs: fix onboarding markdown list spacing

---------

Co-authored-by: Josh Lehman <josh@martian.engineering>
OWALabuy pushed a commit to kcinzgg/openclaw that referenced this pull request Mar 4, 2026
…21208)

* feat(tlon): sync with openclaw-tlon master

- Add tlon CLI tool registration with binary lookup
- Add approval, media, settings, foreigns, story, upload modules
- Add http-api wrapper for Urbit connection patching
- Update types for defaultAuthorizedShips support
- Fix type compatibility with core plugin SDK
- Stub uploadFile (API not yet available in @tloncorp/api-beta)
- Remove incompatible test files (security, sse-client, upload)

* chore(tlon): remove dead code

Remove unused Urbit channel client files:
- channel-client.ts
- channel-ops.ts
- context.ts

These were not imported anywhere in the extension.

* feat(tlon): add image upload support via @tloncorp/api

- Import configureClient and uploadFile from @tloncorp/api
- Implement uploadImageFromUrl using uploadFile
- Configure API client before media uploads
- Update dependency to github:tloncorp/api-beta#main

* fix(tlon): restore SSRF protection with event ack tracking

- Restore context.ts and channel-ops.ts for SSRF support
- Restore sse-client.ts with urbitFetch for SSRF-protected requests
- Add event ack tracking from openclaw-tlon (acks every 20 events)
- Pass ssrfPolicy through authenticate() and UrbitSSEClient
- Fixes security regression from sync with openclaw-tlon

* fix(tlon): restore buildTlonAccountFields for allowPrivateNetwork

The inlined payload building was missing allowPrivateNetwork field,
which would prevent the setting from being persisted to config.

* fix(tlon): restore SSRF protection in probeAccount

- Restore channel-client.ts for UrbitChannelClient
- Use UrbitChannelClient with ssrfPolicy in probeAccount
- Ensures account probe respects allowPrivateNetwork setting

* feat(tlon): add ownerShip to setup flow

ownerShip should always be set as it controls who receives
approval requests and can approve/deny actions.

* chore(tlon): remove unused http-api.ts

After restoring SSRF protection, probeAccount uses UrbitChannelClient
instead of @urbit/http-api. The http-api.ts wrapper is no longer needed.

* refactor(tlon): simplify probeAccount to direct /~/name request

No channel needed - just authenticate and GET /~/name.
Removes UrbitChannelClient, keeping only UrbitSSEClient for monitor.

* chore(tlon): add logging for event acks

* chore(tlon): lower ack threshold to 5 for testing

* fix(tlon): address security review issues

- Fix SSRF in upload.ts: use urbitFetch with SSRF protection
- Fix SSRF in media.ts: use urbitFetch with SSRF protection
- Add command whitelist to tlon tool to prevent command injection
- Add getDefaultSsrFPolicy() helper for uploads/downloads

* fix(tlon): restore auth retry and add reauth on SSE reconnect

- Add authenticateWithRetry() helper with exponential backoff (restores lost logic from openclaw#39)
- Add onReconnect callback to re-authenticate when SSE stream reconnects
- Add UrbitSSEClient.updateCookie() method for proper cookie normalization on reauth

* fix(tlon): add infinite reconnect with reset after max attempts

Instead of giving up after maxReconnectAttempts, wait 10 seconds then
reset the counter and keep trying. This ensures the monitor never
permanently disconnects due to temporary network issues.

* test(tlon): restore security, sse-client, and upload tests

- security.test.ts: DM allowlist, group invite, bot mention detection, ship normalization
- sse-client.test.ts: subscription handling, cookie updates, reconnection params
- upload.test.ts: image upload with SSRF protection, error handling

* fix(tlon): restore DM partner ship extraction for proper routing

- Add extractDmPartnerShip() to extract partner from 'whom' field
- Use partner ship for routing (more reliable than essay.author)
- Explicitly ignore bot's own outbound DM events
- Log mismatch between author and partner for debugging

* chore(tlon): restore ack threshold to 20

* chore(tlon): sync slash commands support from upstream

- Add stripBotMention for proper CommandBody parsing
- Add command authorization logic for owner-only slash commands
- Add CommandAuthorized and CommandSource to context payload

* fix(tlon): resolve TypeScript errors in tests and monitor

- Store validated account url/code before closure to fix type narrowing
- Fix test type annotations for mode rules
- Add proper Response type cast in sse-client mock
- Use optional chaining for init properties

* docs(tlon): update docs for new config options and capabilities

- Document ownerShip for approval system
- Document autoAcceptDmInvites and autoAcceptGroupInvites
- Update status to reflect rich text and image support
- Add bundled skill section
- Update notes with formatting and image details
- Fix pnpm-lock.yaml conflict

* docs(tlon): fix dmAllowlist description and improve allowPrivateNetwork docs

- Correct dmAllowlist: empty means no DMs allowed (not allow all)
- Promote allowPrivateNetwork to its own section with examples
- Add warning about SSRF protection implications

* docs(tlon): clarify ownerShip is auto-authorized everywhere

- Add ownerShip to minimal config example (recommended)
- Document that owner is automatically allowed for DMs and channels
- No need to add owner to dmAllowlist or defaultAuthorizedShips

* docs(tlon): add capabilities table, troubleshooting, and config reference

Align with Matrix docs format:
- Capabilities table for quick feature reference
- Troubleshooting section with common failures
- Configuration reference with all options

* docs(tlon): fix reactions status and expand bundled skill section

- Reactions ARE supported via bundled skill (not missing)
- Add link to skill GitHub repo
- List skill capabilities: contacts, channels, groups, DMs, reactions, settings

* fix(tlon): use crypto.randomUUID instead of Math.random for channel ID

Fixes security test failure - Math.random is flagged as weak randomness.

* docs: fix markdown lint - add blank line before </Step>

* fix: address PR review issues for tlon plugin

- upload.ts: Use fetchWithSsrFGuard directly instead of urbitFetch to
  preserve full URL path when fetching external images; add release() call
- media.ts: Same fix - use fetchWithSsrFGuard for external media downloads;
  add release() call to clean up resources
- channel.ts: Use urbitFetch for poke API to maintain consistent SSRF
  protection (DNS pinning + redirect handling)
- upload.test.ts: Update mocks to use fetchWithSsrFGuard instead of urbitFetch

Addresses blocking issues from jalehman's review:
1. Fixed incorrect URL being fetched (validateUrbitBaseUrl was stripping path)
2. Fixed missing release() calls that could leak resources
3. Restored guarded fetch semantics for poke operations

* docs: add tlon changelog fragment

* style: format tlon monitor

* fix: align tlon lockfile and sse id generation

* docs: fix onboarding markdown list spacing

---------

Co-authored-by: Josh Lehman <josh@martian.engineering>
AytuncYildizli pushed a commit to AytuncYildizli/openclaw that referenced this pull request Mar 4, 2026
…21208)

* feat(tlon): sync with openclaw-tlon master

- Add tlon CLI tool registration with binary lookup
- Add approval, media, settings, foreigns, story, upload modules
- Add http-api wrapper for Urbit connection patching
- Update types for defaultAuthorizedShips support
- Fix type compatibility with core plugin SDK
- Stub uploadFile (API not yet available in @tloncorp/api-beta)
- Remove incompatible test files (security, sse-client, upload)

* chore(tlon): remove dead code

Remove unused Urbit channel client files:
- channel-client.ts
- channel-ops.ts
- context.ts

These were not imported anywhere in the extension.

* feat(tlon): add image upload support via @tloncorp/api

- Import configureClient and uploadFile from @tloncorp/api
- Implement uploadImageFromUrl using uploadFile
- Configure API client before media uploads
- Update dependency to github:tloncorp/api-beta#main

* fix(tlon): restore SSRF protection with event ack tracking

- Restore context.ts and channel-ops.ts for SSRF support
- Restore sse-client.ts with urbitFetch for SSRF-protected requests
- Add event ack tracking from openclaw-tlon (acks every 20 events)
- Pass ssrfPolicy through authenticate() and UrbitSSEClient
- Fixes security regression from sync with openclaw-tlon

* fix(tlon): restore buildTlonAccountFields for allowPrivateNetwork

The inlined payload building was missing allowPrivateNetwork field,
which would prevent the setting from being persisted to config.

* fix(tlon): restore SSRF protection in probeAccount

- Restore channel-client.ts for UrbitChannelClient
- Use UrbitChannelClient with ssrfPolicy in probeAccount
- Ensures account probe respects allowPrivateNetwork setting

* feat(tlon): add ownerShip to setup flow

ownerShip should always be set as it controls who receives
approval requests and can approve/deny actions.

* chore(tlon): remove unused http-api.ts

After restoring SSRF protection, probeAccount uses UrbitChannelClient
instead of @urbit/http-api. The http-api.ts wrapper is no longer needed.

* refactor(tlon): simplify probeAccount to direct /~/name request

No channel needed - just authenticate and GET /~/name.
Removes UrbitChannelClient, keeping only UrbitSSEClient for monitor.

* chore(tlon): add logging for event acks

* chore(tlon): lower ack threshold to 5 for testing

* fix(tlon): address security review issues

- Fix SSRF in upload.ts: use urbitFetch with SSRF protection
- Fix SSRF in media.ts: use urbitFetch with SSRF protection
- Add command whitelist to tlon tool to prevent command injection
- Add getDefaultSsrFPolicy() helper for uploads/downloads

* fix(tlon): restore auth retry and add reauth on SSE reconnect

- Add authenticateWithRetry() helper with exponential backoff (restores lost logic from openclaw#39)
- Add onReconnect callback to re-authenticate when SSE stream reconnects
- Add UrbitSSEClient.updateCookie() method for proper cookie normalization on reauth

* fix(tlon): add infinite reconnect with reset after max attempts

Instead of giving up after maxReconnectAttempts, wait 10 seconds then
reset the counter and keep trying. This ensures the monitor never
permanently disconnects due to temporary network issues.

* test(tlon): restore security, sse-client, and upload tests

- security.test.ts: DM allowlist, group invite, bot mention detection, ship normalization
- sse-client.test.ts: subscription handling, cookie updates, reconnection params
- upload.test.ts: image upload with SSRF protection, error handling

* fix(tlon): restore DM partner ship extraction for proper routing

- Add extractDmPartnerShip() to extract partner from 'whom' field
- Use partner ship for routing (more reliable than essay.author)
- Explicitly ignore bot's own outbound DM events
- Log mismatch between author and partner for debugging

* chore(tlon): restore ack threshold to 20

* chore(tlon): sync slash commands support from upstream

- Add stripBotMention for proper CommandBody parsing
- Add command authorization logic for owner-only slash commands
- Add CommandAuthorized and CommandSource to context payload

* fix(tlon): resolve TypeScript errors in tests and monitor

- Store validated account url/code before closure to fix type narrowing
- Fix test type annotations for mode rules
- Add proper Response type cast in sse-client mock
- Use optional chaining for init properties

* docs(tlon): update docs for new config options and capabilities

- Document ownerShip for approval system
- Document autoAcceptDmInvites and autoAcceptGroupInvites
- Update status to reflect rich text and image support
- Add bundled skill section
- Update notes with formatting and image details
- Fix pnpm-lock.yaml conflict

* docs(tlon): fix dmAllowlist description and improve allowPrivateNetwork docs

- Correct dmAllowlist: empty means no DMs allowed (not allow all)
- Promote allowPrivateNetwork to its own section with examples
- Add warning about SSRF protection implications

* docs(tlon): clarify ownerShip is auto-authorized everywhere

- Add ownerShip to minimal config example (recommended)
- Document that owner is automatically allowed for DMs and channels
- No need to add owner to dmAllowlist or defaultAuthorizedShips

* docs(tlon): add capabilities table, troubleshooting, and config reference

Align with Matrix docs format:
- Capabilities table for quick feature reference
- Troubleshooting section with common failures
- Configuration reference with all options

* docs(tlon): fix reactions status and expand bundled skill section

- Reactions ARE supported via bundled skill (not missing)
- Add link to skill GitHub repo
- List skill capabilities: contacts, channels, groups, DMs, reactions, settings

* fix(tlon): use crypto.randomUUID instead of Math.random for channel ID

Fixes security test failure - Math.random is flagged as weak randomness.

* docs: fix markdown lint - add blank line before </Step>

* fix: address PR review issues for tlon plugin

- upload.ts: Use fetchWithSsrFGuard directly instead of urbitFetch to
  preserve full URL path when fetching external images; add release() call
- media.ts: Same fix - use fetchWithSsrFGuard for external media downloads;
  add release() call to clean up resources
- channel.ts: Use urbitFetch for poke API to maintain consistent SSRF
  protection (DNS pinning + redirect handling)
- upload.test.ts: Update mocks to use fetchWithSsrFGuard instead of urbitFetch

Addresses blocking issues from jalehman's review:
1. Fixed incorrect URL being fetched (validateUrbitBaseUrl was stripping path)
2. Fixed missing release() calls that could leak resources
3. Restored guarded fetch semantics for poke operations

* docs: add tlon changelog fragment

* style: format tlon monitor

* fix: align tlon lockfile and sse id generation

* docs: fix onboarding markdown list spacing

---------

Co-authored-by: Josh Lehman <josh@martian.engineering>
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
…21208)

* feat(tlon): sync with openclaw-tlon master

- Add tlon CLI tool registration with binary lookup
- Add approval, media, settings, foreigns, story, upload modules
- Add http-api wrapper for Urbit connection patching
- Update types for defaultAuthorizedShips support
- Fix type compatibility with core plugin SDK
- Stub uploadFile (API not yet available in @tloncorp/api-beta)
- Remove incompatible test files (security, sse-client, upload)

* chore(tlon): remove dead code

Remove unused Urbit channel client files:
- channel-client.ts
- channel-ops.ts
- context.ts

These were not imported anywhere in the extension.

* feat(tlon): add image upload support via @tloncorp/api

- Import configureClient and uploadFile from @tloncorp/api
- Implement uploadImageFromUrl using uploadFile
- Configure API client before media uploads
- Update dependency to github:tloncorp/api-beta#main

* fix(tlon): restore SSRF protection with event ack tracking

- Restore context.ts and channel-ops.ts for SSRF support
- Restore sse-client.ts with urbitFetch for SSRF-protected requests
- Add event ack tracking from openclaw-tlon (acks every 20 events)
- Pass ssrfPolicy through authenticate() and UrbitSSEClient
- Fixes security regression from sync with openclaw-tlon

* fix(tlon): restore buildTlonAccountFields for allowPrivateNetwork

The inlined payload building was missing allowPrivateNetwork field,
which would prevent the setting from being persisted to config.

* fix(tlon): restore SSRF protection in probeAccount

- Restore channel-client.ts for UrbitChannelClient
- Use UrbitChannelClient with ssrfPolicy in probeAccount
- Ensures account probe respects allowPrivateNetwork setting

* feat(tlon): add ownerShip to setup flow

ownerShip should always be set as it controls who receives
approval requests and can approve/deny actions.

* chore(tlon): remove unused http-api.ts

After restoring SSRF protection, probeAccount uses UrbitChannelClient
instead of @urbit/http-api. The http-api.ts wrapper is no longer needed.

* refactor(tlon): simplify probeAccount to direct /~/name request

No channel needed - just authenticate and GET /~/name.
Removes UrbitChannelClient, keeping only UrbitSSEClient for monitor.

* chore(tlon): add logging for event acks

* chore(tlon): lower ack threshold to 5 for testing

* fix(tlon): address security review issues

- Fix SSRF in upload.ts: use urbitFetch with SSRF protection
- Fix SSRF in media.ts: use urbitFetch with SSRF protection
- Add command whitelist to tlon tool to prevent command injection
- Add getDefaultSsrFPolicy() helper for uploads/downloads

* fix(tlon): restore auth retry and add reauth on SSE reconnect

- Add authenticateWithRetry() helper with exponential backoff (restores lost logic from #39)
- Add onReconnect callback to re-authenticate when SSE stream reconnects
- Add UrbitSSEClient.updateCookie() method for proper cookie normalization on reauth

* fix(tlon): add infinite reconnect with reset after max attempts

Instead of giving up after maxReconnectAttempts, wait 10 seconds then
reset the counter and keep trying. This ensures the monitor never
permanently disconnects due to temporary network issues.

* test(tlon): restore security, sse-client, and upload tests

- security.test.ts: DM allowlist, group invite, bot mention detection, ship normalization
- sse-client.test.ts: subscription handling, cookie updates, reconnection params
- upload.test.ts: image upload with SSRF protection, error handling

* fix(tlon): restore DM partner ship extraction for proper routing

- Add extractDmPartnerShip() to extract partner from 'whom' field
- Use partner ship for routing (more reliable than essay.author)
- Explicitly ignore bot's own outbound DM events
- Log mismatch between author and partner for debugging

* chore(tlon): restore ack threshold to 20

* chore(tlon): sync slash commands support from upstream

- Add stripBotMention for proper CommandBody parsing
- Add command authorization logic for owner-only slash commands
- Add CommandAuthorized and CommandSource to context payload

* fix(tlon): resolve TypeScript errors in tests and monitor

- Store validated account url/code before closure to fix type narrowing
- Fix test type annotations for mode rules
- Add proper Response type cast in sse-client mock
- Use optional chaining for init properties

* docs(tlon): update docs for new config options and capabilities

- Document ownerShip for approval system
- Document autoAcceptDmInvites and autoAcceptGroupInvites
- Update status to reflect rich text and image support
- Add bundled skill section
- Update notes with formatting and image details
- Fix pnpm-lock.yaml conflict

* docs(tlon): fix dmAllowlist description and improve allowPrivateNetwork docs

- Correct dmAllowlist: empty means no DMs allowed (not allow all)
- Promote allowPrivateNetwork to its own section with examples
- Add warning about SSRF protection implications

* docs(tlon): clarify ownerShip is auto-authorized everywhere

- Add ownerShip to minimal config example (recommended)
- Document that owner is automatically allowed for DMs and channels
- No need to add owner to dmAllowlist or defaultAuthorizedShips

* docs(tlon): add capabilities table, troubleshooting, and config reference

Align with Matrix docs format:
- Capabilities table for quick feature reference
- Troubleshooting section with common failures
- Configuration reference with all options

* docs(tlon): fix reactions status and expand bundled skill section

- Reactions ARE supported via bundled skill (not missing)
- Add link to skill GitHub repo
- List skill capabilities: contacts, channels, groups, DMs, reactions, settings

* fix(tlon): use crypto.randomUUID instead of Math.random for channel ID

Fixes security test failure - Math.random is flagged as weak randomness.

* docs: fix markdown lint - add blank line before </Step>

* fix: address PR review issues for tlon plugin

- upload.ts: Use fetchWithSsrFGuard directly instead of urbitFetch to
  preserve full URL path when fetching external images; add release() call
- media.ts: Same fix - use fetchWithSsrFGuard for external media downloads;
  add release() call to clean up resources
- channel.ts: Use urbitFetch for poke API to maintain consistent SSRF
  protection (DNS pinning + redirect handling)
- upload.test.ts: Update mocks to use fetchWithSsrFGuard instead of urbitFetch

Addresses blocking issues from jalehman's review:
1. Fixed incorrect URL being fetched (validateUrbitBaseUrl was stripping path)
2. Fixed missing release() calls that could leak resources
3. Restored guarded fetch semantics for poke operations

* docs: add tlon changelog fragment

* style: format tlon monitor

* fix: align tlon lockfile and sse id generation

* docs: fix onboarding markdown list spacing

---------

Co-authored-by: Josh Lehman <josh@martian.engineering>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: tlon Channel integration: tlon docs Improvements or additions to documentation size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants