diff --git a/INSTALL.md b/INSTALL.md
index a48804c..0d8fd0c 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -131,6 +131,32 @@ Apprise sends a different JSON format (`title`, `body`, `type`, `attachments`)
- The webhook endpoint is public (no auth required) but validates the auth token from the URL path
- Admin settings endpoints (`/save-config`, `/rooms`) require admin login
- Images are stored in the bot user's files and purged after the retention period
+- **Debug endpoint disabled by default** — see below
- **Known limitations (to be fixed in a future release):**
- Auth tokens generated from the settings UI use client-side generation; for higher security, regenerate them via the server API
- The webhook endpoint has no rate limiting — consider placing it behind a reverse proxy rate limiter if exposing to untrusted sources
+
+### Debug endpoint
+
+The `/apps/ncdiscordhook/debug` endpoint exposes internal configuration,
+database schema, and bot credentials. It is **disabled by default**.
+
+```bash
+# Check status
+php occ ncdiscordhook:debug:status
+
+# Enable (WARNING: exposes sensitive data)
+php occ ncdiscordhook:debug:enable
+
+# Disable (default)
+php occ ncdiscordhook:debug:disable
+
+# Toggle current state
+php occ ncdiscordhook:debug:toggle
+```
+
+After troubleshooting, disable it immediately:
+
+```bash
+php occ ncdiscordhook:debug:disable
+```
diff --git a/README.md b/README.md
index e99650b..d7cb0c0 100644
--- a/README.md
+++ b/README.md
@@ -137,6 +137,34 @@ The apprise webhook maps `title`, `body`, and `attachments` to the same Talk mes
- Bot password is encrypted at rest using Nextcloud's crypto
- Image download uses Nextcloud's HTTP client with local address blocking
- Rate limiting should be handled at the web server or reverse proxy level
+- **Debug endpoint disabled by default** — see below
+
+### Debug endpoint
+
+The `/apps/ncdiscordhook/debug` endpoint exposes internal configuration,
+database schema, and bot credentials. It is **disabled by default** and must
+be explicitly enabled via the CLI:
+
+```bash
+# Check current status
+php occ ncdiscordhook:debug:status
+
+# Enable (WARNING: exposes sensitive data)
+php occ ncdiscordhook:debug:enable
+
+# Disable (default)
+php occ ncdiscordhook:debug:disable
+
+# Toggle current state
+php occ ncdiscordhook:debug:toggle
+```
+
+Never leave the debug endpoint enabled in production. After troubleshooting,
+disable it immediately:
+
+```bash
+php occ ncdiscordhook:debug:disable
+```
## Logging
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 0da1f6f..64f461b 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -5,7 +5,7 @@
NCdiscordhook
Discord webhook bridge for Nextcloud Talk with image support
Accepts Discord webhook-style JSON payloads and posts them into Nextcloud Talk rooms, preserving image attachments. Each room gets its own webhook URL with an auth token for security.
- 1.0.0
+ 1.1.0
AGPL-3.0-or-later
Kyle
NCdiscordhook
@@ -24,5 +24,9 @@
OCA\NCdiscordhook\Settings\Admin
+
+ OCA\NCdiscordhook\Command\DebugToggle
+
+
https://github.com/Mr-Newlove/nc_bot_webhooks/issues
diff --git a/lib/Command/DebugToggle.php b/lib/Command/DebugToggle.php
new file mode 100644
index 0000000..1696cb4
--- /dev/null
+++ b/lib/Command/DebugToggle.php
@@ -0,0 +1,101 @@
+appConfig = $appConfig;
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('ncdiscordhook:debug:toggle')
+ ->setDescription('Enable or disable the debug endpoint')
+ ->addOption('enable', 'e', InputOption::VALUE_NONE, 'Enable the debug endpoint')
+ ->addOption('disable', 'd', InputOption::VALUE_NONE, 'Disable the debug endpoint')
+ ->addOption('status', null, InputOption::VALUE_NONE, 'Show current debug endpoint status');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $io = new SymfonyStyle($input, $output);
+
+ if ($input->getOption('status')) {
+ $enabled = (bool) $this->appConfig->getValueBool(self::APP_ID, self::DEBUG_KEY, false);
+ $io->success($enabled ? 'Debug endpoint is ENABLED' : 'Debug endpoint is DISABLED (default)');
+ return Command::SUCCESS;
+ }
+
+ if ($input->getOption('enable') && $input->getOption('disable')) {
+ $io->error('Cannot use both --enable and --disable at the same time.');
+ return Command::INVALID;
+ }
+
+ if ($input->getOption('enable')) {
+ $this->appConfig->setValueBool(self::APP_ID, self::DEBUG_KEY, true);
+ $io->warning('Debug endpoint is now ENABLED. Anyone can access /apps/ncdiscordhook/debug.');
+ $io->note('To disable later, run: php occ ncdiscordhook:debug:disable');
+ return Command::SUCCESS;
+ }
+
+ if ($input->getOption('disable')) {
+ $this->appConfig->setValueBool(self::APP_ID, self::DEBUG_KEY, false);
+ $io->success('Debug endpoint is now DISABLED.');
+ return Command::SUCCESS;
+ }
+
+ // No flag: toggle
+ $current = (bool) $this->appConfig->getValueBool(self::APP_ID, self::DEBUG_KEY, false);
+ $this->appConfig->setValueBool(self::APP_ID, self::DEBUG_KEY, !$current);
+ $next = !$current ? 'enabled' : 'disabled';
+ $io->success("Debug endpoint is now $next.");
+ return Command::SUCCESS;
+ }
+}
diff --git a/lib/Controller/WebhookController.php b/lib/Controller/WebhookController.php
index ad24c34..6f54bbc 100644
--- a/lib/Controller/WebhookController.php
+++ b/lib/Controller/WebhookController.php
@@ -361,11 +361,26 @@ class WebhookController extends Controller {
* Query params:
* - webhook_url: Full webhook URL to diagnose (e.g. /apps/ncdiscordhook/discord-webhook/{room}/{token})
* - test_post=1: Actually POST a test message to the room
+ *
+ * This endpoint is disabled by default. Enable it via:
+ * php occ ncdiscordhook:debug:enable
+ *
+ * SECURITY: Never leave debug enabled in production. It exposes internal
+ * configuration, database schema, and bot credentials.
*/
#[PublicPage]
#[NoCSRFRequired]
#[NoAdminRequired]
public function debug(): DataResponse {
+ // Debug endpoint must be explicitly enabled via OCC command
+ $debugEnabled = $this->appConfig->getValueBool('ncdiscordhook', 'debug_enabled', false);
+ if (!$debugEnabled) {
+ return new DataResponse(
+ ['error' => 'Debug endpoint is disabled. Enable it with: php occ ncdiscordhook:debug:enable'],
+ Http::STATUS_FORBIDDEN,
+ );
+ }
+
$user = $this->userSession->getUser();
$uid = $user !== null ? $user->getUID() : null;
$isAdmin = $user !== null && $this->groupManager->isAdmin($uid);
@@ -376,6 +391,7 @@ class WebhookController extends Controller {
'user_is_admin' => $isAdmin,
'bot_user' => null,
'has_bot_password' => false,
+ 'debug_enabled' => true,
];
try {
diff --git a/lib/Service/TalkService.php b/lib/Service/TalkService.php
index 852cf73..21b5541 100644
--- a/lib/Service/TalkService.php
+++ b/lib/Service/TalkService.php
@@ -704,6 +704,22 @@ class TalkService {
$richObjects[] = $richObj;
}
}
+ } elseif (!empty($attachment['base64'] ?? '')) {
+ // Base64-encoded attachment (Apprise library JSON method)
+ $fileData = base64_decode($attachment['base64'], true);
+ if ($fileData === false) {
+ continue;
+ }
+
+ $fileName = $attachment['filename'] ?? $attachment['name'] ?? 'attachment';
+ $mimeType = $attachment['mimetype'] ?? $attachment['mimeType'] ?? 'application/octet-stream';
+ $uploadPath = $this->uploadImage($roomToken, $fileName, $fileData, $mimeType);
+ if ($uploadPath !== null) {
+ $richObj = $this->buildRichObject($uploadPath, $mimeType, $roomToken);
+ if ($richObj !== null) {
+ $richObjects[] = $richObj;
+ }
+ }
}
}
}
@@ -877,7 +893,7 @@ class TalkService {
// or the message is silently dropped.
$botDisplayName = $this->getBotDisplayNameForRoom($roomToken);
- // Build message body for Chat API v4
+ // Build message body for Chat API v1
$body = [
'message' => $message,
'actorType' => 'users',