fix: 收敛NapCat运行态检查脚本边界

This commit is contained in:
sunlei 2026-06-18 13:12:08 +08:00
parent bfb10c50cb
commit 2a890d894d
9 changed files with 57 additions and 37 deletions

View File

@ -26,7 +26,7 @@ export class QqbotNapcatAccountRuntimeService implements QqbotAccountNapcatRunti
/** /**
* Creates the account-list runtime adapter that joins persisted bindings, container status, and optional profile summaries. * Creates the account-list runtime adapter that joins persisted bindings, container status, and optional profile summaries.
* @param accountNapcatRepository - Binding repository used to pick the primary NapCat container for each QQBot account. * @param accountNapcatRepository - Binding repository used to pick the primary NapCat container for each QQBot account.
* @param napcatContainerRepository - Container repository used for cached Docker/WebUI status and non-secret metadata. * @param napcatContainerRepository - Container repository used for cached runtime/WebUI status and non-secret metadata.
* @param napcatContainerService - Runtime integration service for bounded NapCat/WebUI status probes and auto-login. * @param napcatContainerService - Runtime integration service for bounded NapCat/WebUI status probes and auto-login.
* @param toolsService - Shared helpers for status text normalization and NapCat offline-message classification. * @param toolsService - Shared helpers for status text normalization and NapCat offline-message classification.
* @param runtimeProfileInspector - Optional profile reader that enriches list rows without changing login state. * @param runtimeProfileInspector - Optional profile reader that enriches list rows without changing login state.

View File

@ -1,8 +1,9 @@
import { Injectable } from '@nestjs/common'; import { Injectable, Optional } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm'; import { In, Repository } from 'typeorm';
import { ToolsService } from '@/common'; import { ToolsService } from '@/common';
import { NapcatRuntimeProfileInspectionScriptService } from '../../infrastructure/integration/container/napcat-runtime-profile-inspection-script.service';
import { NapcatProtocolProfile } from '../../infrastructure/persistence/napcat-protocol-profile.entity'; import { NapcatProtocolProfile } from '../../infrastructure/persistence/napcat-protocol-profile.entity';
import { NapcatRuntimeProfile } from '../../infrastructure/persistence/napcat-runtime-profile.entity'; import { NapcatRuntimeProfile } from '../../infrastructure/persistence/napcat-runtime-profile.entity';
@ -22,11 +23,12 @@ export type NapcatRuntimeProfileSummary = {
@Injectable() @Injectable()
export class NapcatRuntimeProfileInspectorService { export class NapcatRuntimeProfileInspectorService {
/** /**
* Initializes runtime inspection over the existing SSH-managed container model. * Initializes runtime inspection over the existing remote-managed runtime model.
* @param runtimeProfileRepository - Runtime profile repository updated with latest Docker and desktop evidence. * @param runtimeProfileRepository - Runtime profile repository updated with latest container and desktop evidence.
* @param protocolProfileRepository - Protocol profile repository updated with config hashes and drift state. * @param protocolProfileRepository - Protocol profile repository updated with config hashes and drift state.
* @param configService - Runtime config provider used for SSH target and inspection timeout defaults. * @param configService - Runtime config provider used for inspection timeout defaults.
* @param toolsService - Shared helper used to normalize string evidence before redaction. * @param toolsService - Shared helper used to normalize string evidence before redaction.
* @param inspectionScriptService - Infrastructure helper that owns remote script construction.
*/ */
constructor( constructor(
@InjectRepository(NapcatRuntimeProfile) @InjectRepository(NapcatRuntimeProfile)
@ -35,25 +37,26 @@ export class NapcatRuntimeProfileInspectorService {
private readonly protocolProfileRepository: Repository<NapcatProtocolProfile>, private readonly protocolProfileRepository: Repository<NapcatProtocolProfile>,
private readonly configService: ConfigService, private readonly configService: ConfigService,
private readonly toolsService: ToolsService, private readonly toolsService: ToolsService,
) {} @Optional()
private readonly inspectionScriptService?: NapcatRuntimeProfileInspectionScriptService,
) {
this.inspectionScriptService =
inspectionScriptService ||
new NapcatRuntimeProfileInspectionScriptService();
}
/** /**
* Builds the remote inspection script for Docker and in-container profile evidence. * Delegates remote profile evidence script creation to the infrastructure helper.
* @param containerName - Docker container name selected from the persisted NapCat container row. * @param containerName - Runtime container name selected from the persisted NapCat container row.
* @returns Shell script that collects runtime evidence without reading secret env values. * @returns Read-only profile evidence script without secret environment reads.
*/ */
buildInspectScript(containerName: string) { buildInspectScript(containerName: string) {
return ` return this.inspectionScriptService.buildInspectScript(containerName);
set -eu
NAME=${this.sh(containerName)}
docker inspect "$NAME"
docker exec "$NAME" sh -lc 'locale -a; locale; date +%Z; fc-match "Noto Sans CJK SC"; test ! -e /.dockerenv; cat /proc/1/cgroup; id; ps -eo user,args | grep -E "qq|NapCat|Xvfb" | grep -v grep || true'
`;
} }
/** /**
* Redacts secrets before evidence is stored, logged, or returned to Admin. * Redacts secrets before evidence is stored, logged, or returned to Admin.
* @param value - Evidence object or primitive produced by Docker, NapCat, or config writers. * @param value - Evidence object or primitive produced by runtime, NapCat, or config writers.
* @returns Evidence with sensitive keys and token query values replaced by placeholders. * @returns Evidence with sensitive keys and token query values replaced by placeholders.
*/ */
sanitizeEvidence(value: unknown): unknown { sanitizeEvidence(value: unknown): unknown {
@ -140,7 +143,7 @@ docker exec "$NAME" sh -lc 'locale -a; locale; date +%Z; fc-match "Noto Sans CJK
/** /**
* Reads the bounded runtime profile inspection timeout. * Reads the bounded runtime profile inspection timeout.
* @returns Positive timeout in milliseconds for future SSH inspection calls. * @returns Positive timeout in milliseconds for future remote inspection calls.
*/ */
private getInspectionTimeoutMs() { private getInspectionTimeoutMs() {
const value = Number( const value = Number(
@ -173,13 +176,4 @@ docker exec "$NAME" sh -lc 'locale -a; locale; date +%Z; fc-match "Noto Sans CJK
private redactString(value: string) { private redactString(value: string) {
return value.replace(/token=[^&\s]+/gi, 'token=[REDACTED]'); return value.replace(/token=[^&\s]+/gi, 'token=[REDACTED]');
} }
/**
* Quotes shell literals used by read-only inspection scripts.
* @param value - Container name selected from trusted persistence.
* @returns POSIX-safe single-quoted shell literal.
*/
private sh(value: string) {
return `'${`${value}`.replace(/'/g, `'\\''`)}'`;
}
} }

View File

@ -5,7 +5,7 @@ import type { NapcatRuntimeProfileSnapshot } from '../../domain/runtime/napcat-p
@Injectable() @Injectable()
export class NapcatRuntimeProfileService { export class NapcatRuntimeProfileService {
/** /**
* Initializes the profile resolver used before Docker script generation. * Initializes the profile resolver used before managed runtime script generation.
* @param configService - Nest config provider that supplies image ref, UID/GID, shm size, and profile version. * @param configService - Nest config provider that supplies image ref, UID/GID, shm size, and profile version.
*/ */
constructor(private readonly configService: ConfigService) {} constructor(private readonly configService: ConfigService) {}
@ -13,7 +13,7 @@ export class NapcatRuntimeProfileService {
/** /**
* Resolves the runtime profile for an account-owned NapCat container. * Resolves the runtime profile for an account-owned NapCat container.
* @param input - Account, container, data directory, and device identity ids that tie generated profile evidence to persistence. * @param input - Account, container, data directory, and device identity ids that tie generated profile evidence to persistence.
* @returns Runtime profile snapshot used by Docker script generation and later persistence. * @returns Runtime profile snapshot used by managed runtime script generation and later persistence.
*/ */
resolveRuntimeProfile(input: { resolveRuntimeProfile(input: {
accountId: string; accountId: string;
@ -49,7 +49,7 @@ export class NapcatRuntimeProfileService {
* Reads a trimmed string config value for profile generation. * Reads a trimmed string config value for profile generation.
* @param key - Environment key that controls NapCat runtime profile generation. * @param key - Environment key that controls NapCat runtime profile generation.
* @param defaultValue - Value used when the key is absent from runtime config. * @param defaultValue - Value used when the key is absent from runtime config.
* @returns Trimmed string value consumed by Docker script generation. * @returns Trimmed string value consumed by managed runtime script generation.
*/ */
private getString(key: string, defaultValue: string) { private getString(key: string, defaultValue: string) {
return `${this.configService.get<string>(key) || defaultValue}`.trim(); return `${this.configService.get<string>(key) || defaultValue}`.trim();

View File

@ -41,7 +41,7 @@ export class NapcatSessionBehaviorService {
/** /**
* Converts housekeeping failure into an evidence-only action. * Converts housekeeping failure into an evidence-only action.
* @param input - Account and failure summary from a low-side-effect housekeeping call. * @param input - Account and failure summary from a low-side-effect housekeeping call.
* @returns Decision that disables behavior extensions without resetting login, retrying password, recreating Docker, or refreshing QR. * @returns Decision that disables behavior extensions without resetting login, retrying password, recreating runtime, or refreshing QR.
*/ */
handleHousekeepingFailure(input: { handleHousekeepingFailure(input: {
accountId: string; accountId: string;

View File

@ -28,7 +28,7 @@ export const NAPCAT_REJECTED_VIRTUAL_OUI_PREFIXES = [
] as const; ] as const;
/** /**
* Checks whether a generated MAC starts with a Docker or VM-style prefix. * Checks whether a generated MAC starts with a container or VM-style prefix.
* @param macAddress - Stable MAC candidate generated from account/device seed. * @param macAddress - Stable MAC candidate generated from account/device seed.
* @returns True when the prefix belongs to a rejected container or virtualization range. * @returns True when the prefix belongs to a rejected container or virtualization range.
*/ */

View File

@ -19,4 +19,5 @@ export * from './application/runtime/napcat-login-event.service';
export * from './application/runtime/napcat-runtime-profile-inspector.service'; export * from './application/runtime/napcat-runtime-profile-inspector.service';
export * from './application/runtime/napcat-session-behavior.service'; export * from './application/runtime/napcat-session-behavior.service';
export * from './infrastructure/persistence'; export * from './infrastructure/persistence';
export * from './infrastructure/integration/container/napcat-runtime-profile-inspection-script.service';
export * from './infrastructure/integration/container/qqbot-napcat-container.service'; export * from './infrastructure/integration/container/qqbot-napcat-container.service';

View File

@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class NapcatRuntimeProfileInspectionScriptService {
/**
* Builds the remote inspection script for Docker and in-container profile evidence.
* @param containerName - Docker container name selected from the persisted NapCat container row.
* @returns Shell script that collects runtime evidence without reading secret environment values.
*/
buildInspectScript(containerName: string) {
return `
set -eu
NAME=${this.sh(containerName)}
docker inspect "$NAME"
docker exec "$NAME" sh -lc 'locale -a; locale; date +%Z; fc-match "Noto Sans CJK SC"; test ! -e /.dockerenv; cat /proc/1/cgroup; id; ps -eo user,args | grep -E "qq|NapCat|Xvfb" | grep -v grep || true'
`;
}
/**
* Quotes shell literals used by read-only inspection scripts.
* @param value - Container name selected from trusted persistence.
* @returns POSIX-safe single-quoted shell literal.
*/
private sh(value: string) {
return `'${`${value}`.replace(/'/g, `'\\''`)}'`;
}
}

View File

@ -14,6 +14,7 @@ import { NapcatRuntimeProfileService } from './application/runtime/napcat-runtim
import { NapcatSessionBehaviorService } from './application/runtime/napcat-session-behavior.service'; import { NapcatSessionBehaviorService } from './application/runtime/napcat-session-behavior.service';
import { QqbotNapcatLoginController } from './contract/qqbot-napcat-login.controller'; import { QqbotNapcatLoginController } from './contract/qqbot-napcat-login.controller';
import { QqbotNapcatRuntimeController } from './contract/qqbot-napcat-runtime.controller'; import { QqbotNapcatRuntimeController } from './contract/qqbot-napcat-runtime.controller';
import { NapcatRuntimeProfileInspectionScriptService } from './infrastructure/integration/container/napcat-runtime-profile-inspection-script.service';
import { QqbotNapcatContainerService } from './infrastructure/integration/container/qqbot-napcat-container.service'; import { QqbotNapcatContainerService } from './infrastructure/integration/container/qqbot-napcat-container.service';
import { NapcatDeviceIdentityService } from './infrastructure/integration/device/napcat-device-identity.service'; import { NapcatDeviceIdentityService } from './infrastructure/integration/device/napcat-device-identity.service';
import { NAPCAT_RUNTIME_ENTITIES } from './infrastructure/persistence'; import { NAPCAT_RUNTIME_ENTITIES } from './infrastructure/persistence';
@ -32,6 +33,7 @@ export const QQBOT_NAPCAT_PROVIDERS = [
NapcatLoginStateStoreService, NapcatLoginStateStoreService,
NapcatLoginEventService, NapcatLoginEventService,
NapcatRuntimeProfileInspectorService, NapcatRuntimeProfileInspectorService,
NapcatRuntimeProfileInspectionScriptService,
NapcatRuntimeProfileService, NapcatRuntimeProfileService,
NapcatSessionBehaviorService, NapcatSessionBehaviorService,
QqbotNapcatAccountRuntimeService, QqbotNapcatAccountRuntimeService,

View File

@ -9,6 +9,7 @@ import { JwtAuthGuard } from '../../../../src/modules/admin/identity/auth/jwt-au
import { NapcatConfigWriterService } from '../../../../src/modules/qqbot/napcat/application/runtime/napcat-config-writer.service'; import { NapcatConfigWriterService } from '../../../../src/modules/qqbot/napcat/application/runtime/napcat-config-writer.service';
import { NapcatRuntimeProfileService } from '../../../../src/modules/qqbot/napcat/application/runtime/napcat-runtime-profile.service'; import { NapcatRuntimeProfileService } from '../../../../src/modules/qqbot/napcat/application/runtime/napcat-runtime-profile.service';
import { NapcatRuntimeProfileInspectorService } from '../../../../src/modules/qqbot/napcat/application/runtime/napcat-runtime-profile-inspector.service'; import { NapcatRuntimeProfileInspectorService } from '../../../../src/modules/qqbot/napcat/application/runtime/napcat-runtime-profile-inspector.service';
import { NapcatRuntimeProfileInspectionScriptService } from '../../../../src/modules/qqbot/napcat/infrastructure/integration/container/napcat-runtime-profile-inspection-script.service';
import { QqbotNapcatRuntimeController } from '../../../../src/modules/qqbot/napcat/contract/qqbot-napcat-runtime.controller'; import { QqbotNapcatRuntimeController } from '../../../../src/modules/qqbot/napcat/contract/qqbot-napcat-runtime.controller';
import { import {
NapcatLoginEvent, NapcatLoginEvent,
@ -178,12 +179,7 @@ describe('NapCat runtime profile generation', () => {
describe('NapCat runtime profile inspector', () => { describe('NapCat runtime profile inspector', () => {
it('builds a bounded SSH inspection script without exposing secrets', () => { it('builds a bounded SSH inspection script without exposing secrets', () => {
const service = new NapcatRuntimeProfileInspectorService( const service = new NapcatRuntimeProfileInspectionScriptService();
{} as any,
{} as any,
{} as any,
new ToolsService(),
) as any;
const script = service.buildInspectScript('kt-qqbot-napcat-10001'); const script = service.buildInspectScript('kt-qqbot-napcat-10001');