mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 20:00:47 +00:00
fix: cli tool page to update the config correctly
This commit is contained in:
@@ -6,6 +6,7 @@ import { promisify } from "util";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { redactSecrets } from "@/lib/security/redactSecrets";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
@@ -68,7 +69,7 @@ export async function GET() {
|
||||
|
||||
return NextResponse.json({
|
||||
installed: true,
|
||||
settings: settings,
|
||||
settings: redactSecrets(settings),
|
||||
has9Router: has9Router,
|
||||
settingsPath: getClaudeSettingsPath(),
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { parseTOML, stringifyTOML } from "confbox";
|
||||
import { redactSecretsInText } from "@/lib/security/redactSecrets";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
@@ -96,7 +97,7 @@ export async function GET() {
|
||||
|
||||
return NextResponse.json({
|
||||
installed: true,
|
||||
config,
|
||||
config: redactSecretsInText(config),
|
||||
has9Router: has9RouterConfig(config),
|
||||
configPath: getCodexConfigPath(),
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { NextResponse } from "next/server";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { redactSecrets } from "@/lib/security/redactSecrets";
|
||||
|
||||
// Resolve chatLanguageModels.json path per OS
|
||||
const getConfigPath = () => {
|
||||
@@ -48,7 +49,7 @@ export async function GET() {
|
||||
|
||||
return NextResponse.json({
|
||||
installed: true,
|
||||
config,
|
||||
config: redactSecrets(config),
|
||||
has9Router: has9RouterConfig(config),
|
||||
configPath: getConfigPath(),
|
||||
currentModel: entry?.models?.[0]?.id || null,
|
||||
|
||||
@@ -8,6 +8,7 @@ import crypto from "crypto";
|
||||
import { DEFAULT_PLUGINS, LOCAL_STDIO_PLUGINS, buildManagedMcpServers } from "@/shared/constants/coworkPlugins";
|
||||
import { UPDATER_CONFIG } from "@/shared/constants/config";
|
||||
import { getConsistentMachineId } from "@/shared/utils/machineId";
|
||||
import { redactSecrets } from "@/lib/security/redactSecrets";
|
||||
|
||||
const APP_PORT = UPDATER_CONFIG.appPort;
|
||||
const CLI_TOKEN_HEADER = "x-9r-cli-token";
|
||||
@@ -273,7 +274,7 @@ export async function GET() {
|
||||
|
||||
return NextResponse.json({
|
||||
installed: true,
|
||||
config,
|
||||
config: redactSecrets(config),
|
||||
has9Router,
|
||||
configPath,
|
||||
cowork: {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { promisify } from "util";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { redactSecretsInText } from "@/lib/security/redactSecrets";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
@@ -112,7 +113,7 @@ export async function GET() {
|
||||
const config = parseToml(toml);
|
||||
return NextResponse.json({
|
||||
installed: true,
|
||||
settings: config,
|
||||
settings: redactSecretsInText(config),
|
||||
has9Router: has9RouterConfig(config),
|
||||
configPath: getDeepSeekConfigPath(),
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { promisify } from "util";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { redactSecrets } from "@/lib/security/redactSecrets";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
@@ -69,7 +70,7 @@ export async function GET() {
|
||||
|
||||
return NextResponse.json({
|
||||
installed: true,
|
||||
settings,
|
||||
settings: redactSecrets(settings),
|
||||
has9Router: has9RouterConfig(settings),
|
||||
settingsPath: getDroidSettingsPath(),
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import os from "os";
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import { parseTOML, stringifyTOML } from "confbox";
|
||||
import { redactSecrets } from "@/lib/security/redactSecrets";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
@@ -122,7 +123,7 @@ export async function GET() {
|
||||
|
||||
return NextResponse.json({
|
||||
installed: true,
|
||||
config,
|
||||
config: redactSecrets(config),
|
||||
has9Router,
|
||||
configPath: getConfigPath(),
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { promisify } from "util";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { redactSecrets } from "@/lib/security/redactSecrets";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
@@ -103,8 +104,8 @@ export async function GET() {
|
||||
|
||||
return NextResponse.json({
|
||||
installed: true,
|
||||
settings,
|
||||
agents: enrichedAgents,
|
||||
settings: redactSecrets(settings),
|
||||
agents: redactSecrets(enrichedAgents),
|
||||
has9Router: has9RouterConfig(settings),
|
||||
settingsPath: getOpenClawSettingsPath(),
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import { promisify } from "util";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { redactSecrets } from "@/lib/security/redactSecrets";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
@@ -72,7 +73,7 @@ export async function GET() {
|
||||
|
||||
return NextResponse.json({
|
||||
installed: true,
|
||||
config,
|
||||
config: redactSecrets(config),
|
||||
has9Router: has9RouterConfig(config),
|
||||
configPath: getConfigPath(),
|
||||
opencode: {
|
||||
|
||||
+18
-5
@@ -45,12 +45,14 @@ const ALWAYS_PROTECTED = [
|
||||
];
|
||||
|
||||
// User administration is never exposed to normal users, even if dashboard login
|
||||
// is disabled for local single-user deployments.
|
||||
const ADMIN_ONLY_PATHS = ["/api/users", "/api/tunnel", "/api/headroom", "/api/pxpipe"];
|
||||
// is disabled for local single-user deployments. CLI Tools directly read and
|
||||
// mutate the account running 9Router's local CLI configuration, so they are
|
||||
// host administration rather than per-user dashboard preferences.
|
||||
const ADMIN_ONLY_PATHS = ["/api/users", "/api/tunnel", "/api/headroom", "/api/pxpipe", "/api/cli-tools"];
|
||||
|
||||
// Dashboard paths requiring an administrator. Combo access is handled by its
|
||||
// owner-scoped API routes and is available to authenticated users.
|
||||
const ADMIN_ONLY_DASHBOARD_PATHS = ["/dashboard/token-saver", "/dashboard/pxpipe"];
|
||||
const ADMIN_ONLY_DASHBOARD_PATHS = ["/dashboard/token-saver", "/dashboard/pxpipe", "/dashboard/cli-tools"];
|
||||
|
||||
// Require auth, but allow through if requireLogin is disabled
|
||||
const PROTECTED_API_PATHS = [
|
||||
@@ -75,8 +77,6 @@ const PROTECTED_API_PATHS = [
|
||||
|
||||
// Routes that spawn child processes or read host secrets — restrict to localhost.
|
||||
const LOCAL_ONLY_PATHS = [
|
||||
"/api/cli-tools/cowork-settings",
|
||||
"/api/cli-tools/antigravity-mitm",
|
||||
"/api/mcp/",
|
||||
"/api/tunnel/tailscale-install",
|
||||
"/api/tunnel/tailscale-enable",
|
||||
@@ -210,6 +210,19 @@ export const __test__ = {
|
||||
export async function proxy(request) {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
// CLI Tools access the server process's home directory and, in the MITM
|
||||
// case, can change privileged system networking. Do not allow a shared CLI
|
||||
// bearer token to become a remote host-administration credential: only a
|
||||
// local, authenticated administrator may use these endpoints.
|
||||
if (pathname === "/api/cli-tools" || pathname.startsWith("/api/cli-tools/")) {
|
||||
if (!isLocalRequest(request)) {
|
||||
return NextResponse.json({ error: "CLI Tools are available only from the local machine" }, { status: 403 });
|
||||
}
|
||||
if (!(await isAdmin(request))) {
|
||||
return NextResponse.json({ error: "Administrator access required" }, { status: 403 });
|
||||
}
|
||||
}
|
||||
|
||||
// Local-only gate for spawn-capable / host-secret routes.
|
||||
if (LOCAL_ONLY_PATHS.some((p) => pathname.startsWith(p))) {
|
||||
if (!(await canAccessLocalOnlyRoute(request))) {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const SECRET_KEY_PATTERN = /(?:api[_-]?key|auth[_-]?token|access[_-]?token|refresh[_-]?token|password|secret|credential|authorization|bearer|cookie|private[_-]?key)/i;
|
||||
const REDACTED_VALUE = "[REDACTED]";
|
||||
|
||||
/**
|
||||
* Return a deep copy suitable for an administrative status response.
|
||||
* Configuration values are retained, but values stored under known credential
|
||||
* keys are redacted before crossing the server-to-browser boundary.
|
||||
*/
|
||||
export function redactSecrets(value) {
|
||||
if (Array.isArray(value)) return value.map(redactSecrets);
|
||||
if (!value || typeof value !== "object") return value;
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, child]) => [
|
||||
key,
|
||||
SECRET_KEY_PATTERN.test(key) ? REDACTED_VALUE : redactSecrets(child),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
/** Redact credential assignments embedded in text-based configuration formats. */
|
||||
export function redactSecretsInText(value) {
|
||||
if (typeof value !== "string") return value;
|
||||
|
||||
return value
|
||||
.replace(/^(\s*(?:[A-Za-z0-9_.-]*?(?:api[_-]?key|auth[_-]?token|access[_-]?token|refresh[_-]?token|password|secret|credential|authorization|private[_-]?key)[A-Za-z0-9_.-]*)\s*[=:]\s*)([^\r\n#]+)/gim, `$1${REDACTED_VALUE}`)
|
||||
.replace(/("(?:api[_-]?key|auth[_-]?token|access[_-]?token|refresh[_-]?token|password|secret|credential|authorization|private[_-]?key)"\s*:\s*")[^"]*(")/gim, `$1${REDACTED_VALUE}$2`);
|
||||
}
|
||||
|
||||
export { REDACTED_VALUE };
|
||||
Reference in New Issue
Block a user