mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix: Hook ingress token unlocks password-mode gateway auth (#86453)
Summary: - The PR expands security audit, CLI docs, and tests so `hooks.token` reuse of active Gateway token/password auth is reported while password-mode Gateway startup remains compatible. - PR surface: Source +178, Tests +311, Docs +14. Total +503 across 14 files. - Reproducibility: yes. from source inspection: current main forwards a bearer token as both token and passwor ... ecause this review was read-only, but the linked issue and code path make the reproduction high confidence. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(cr-fmi-hook-ingress-token-unlocks-password-mode-gateway-auth): ap… - PR branch already contained follow-up commit before automerge: fix: include trusted proxy password in hooks token reuse check - PR branch already contained follow-up commit before automerge: fix(gateway): audit hooks password reuse without blocking startup - PR branch already contained follow-up commit before automerge: fix: Hook ingress token unlocks password-mode gateway auth Validation: - ClawSweeper review passed for head7c796b22ec. - Required merge gates passed before the squash merge. Prepared head SHA:7c796b22ecReview: https://github.com/openclaw/openclaw/pull/86453#issuecomment-4533831028 Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com> Co-authored-by: jesse-merhi <79823012+jesse-merhi@users.noreply.github.com> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: jesse-merhi
This commit is contained in:
co-authored by
Coy Geek
jesse-merhi
clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
parent
d6b7fe8615
commit
d51f26850d
@@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Models: show OAuth delegation markers as configured `models.json` auth while keeping runtime route usability checks strict. (#86378) Thanks @rohitjavvadi.
|
||||
- Cron: seed active scheduled and manual cron task rows with a progress summary so status surfaces do not look blank while jobs run. (#86313) Thanks @ferminquant.
|
||||
- Updater: exclude prerelease git tags from stable channel resolution so source updates do not check out newer alpha/rc/preview/canary tags. (#86260) Thanks @stevenepalmer.
|
||||
- Security/Audit: flag webhook `hooks.token` reuse of active Gateway password auth in `openclaw security audit` while keeping password-mode startup compatibility. (#84338) Thanks @coygeek.
|
||||
- Agents/heartbeat: stop heartbeat turns after the first valid `heartbeat_respond` so repeated response loops do not burn tokens. (#86357) Thanks @udaymanish6.
|
||||
- Tasks: keep retained lost tasks out of default status health counts, explain their cleanup window during maintenance, and prune lost task records after 24 hours instead of the general 7-day terminal retention.
|
||||
- Memory-core: keep REM dreaming focused on live light-staged memories and mark staged entries as considered so old recall history no longer dominates fresh candidates. (#86302) Thanks @SebTardif.
|
||||
|
||||
+13
-1
@@ -32,7 +32,19 @@ This is for cooperative/shared inbox hardening. A single Gateway shared by mutua
|
||||
It also emits `security.trust_model.multi_user_heuristic` when config suggests likely shared-user ingress (for example open DM/group policy, configured group targets, or wildcard sender rules), and reminds you that OpenClaw is a personal-assistant trust model by default.
|
||||
For intentional shared-user setups, the audit guidance is to sandbox all sessions, keep filesystem access workspace-scoped, and keep personal/private identities or credentials off that runtime.
|
||||
It also warns when small models (`<=300B`) are used without sandboxing and with web/browser tools enabled.
|
||||
For webhook ingress, it warns when `hooks.token` reuses the Gateway token, when `hooks.token` is short, when `hooks.path="/"`, when `hooks.defaultSessionKey` is unset, when `hooks.allowedAgentIds` is unrestricted, when request `sessionKey` overrides are enabled, and when overrides are enabled without `hooks.allowedSessionKeyPrefixes`.
|
||||
For webhook ingress, it warns when:
|
||||
|
||||
- `hooks.token` reuses an active Gateway shared-secret auth value (`gateway.auth.token` / `OPENCLAW_GATEWAY_TOKEN` or `gateway.auth.password` / `OPENCLAW_GATEWAY_PASSWORD`)
|
||||
- `hooks.token` is short
|
||||
- `hooks.path="/"`
|
||||
- `hooks.defaultSessionKey` is unset
|
||||
- `hooks.allowedAgentIds` is unrestricted
|
||||
- request `sessionKey` overrides are enabled
|
||||
- overrides are enabled without `hooks.allowedSessionKeyPrefixes`
|
||||
|
||||
If Gateway password auth is supplied only at startup, pass the same value to `openclaw security audit --auth password --password <password>` so the audit can check it against `hooks.token`.
|
||||
Password-mode reuse is an audit finding for compatibility; rotate one of the secrets instead of expecting Gateway startup to reject that configuration.
|
||||
|
||||
It also warns when sandbox Docker settings are configured while sandbox mode is off, when `gateway.nodes.denyCommands` uses ineffective pattern-like/unknown entries (exact node command-name matching only, not shell-text filtering), when `gateway.nodes.allowCommands` explicitly enables dangerous node commands, when global `tools.profile="minimal"` is overridden by agent tool profiles, when write/edit tools are disabled but `exec` is still available without a constraining sandbox filesystem boundary, when open groups expose runtime/filesystem tools without sandbox/workspace guards, and when installed plugin tools may be reachable under permissive tool policy.
|
||||
It also flags `gateway.allowRealIpFallback=true` (header-spoofing risk if proxies are misconfigured) and `discovery.mdns.mode="full"` (metadata leakage via mDNS TXT records).
|
||||
It also warns when sandbox browser uses Docker `bridge` network without `sandbox.browser.cdpSourceRange`.
|
||||
|
||||
@@ -687,7 +687,8 @@ Query-string hook tokens are rejected.
|
||||
Validation and safety notes:
|
||||
|
||||
- `hooks.enabled=true` requires a non-empty `hooks.token`.
|
||||
- `hooks.token` must be **distinct** from `gateway.auth.token`; reusing the Gateway token is rejected.
|
||||
- `hooks.token` must be distinct from `gateway.auth.token` / `OPENCLAW_GATEWAY_TOKEN`; reusing the Gateway token fails startup validation.
|
||||
- `openclaw security audit` also flags `hooks.token` reuse of active Gateway password auth (`gateway.auth.password` / `OPENCLAW_GATEWAY_PASSWORD`, or `--auth password --password <password>`) as a critical finding; password-mode reuse stays startup-compatible and should be repaired by rotating one of the secrets.
|
||||
- `hooks.path` cannot be `/`; use a dedicated subpath such as `/hooks`.
|
||||
- If `hooks.allowRequestSessionKey=true`, constrain `hooks.allowedSessionKeyPrefixes` (for example `["hook:"]`).
|
||||
- If a mapping or preset uses a templated `sessionKey`, set `hooks.allowedSessionKeyPrefixes` and `hooks.allowRequestSessionKey=true`. Static mapping keys do not require that opt-in.
|
||||
|
||||
@@ -461,7 +461,7 @@ candidate contains redacted secret placeholders such as `***`.
|
||||
|
||||
Security note:
|
||||
- Treat all hook/webhook payload content as untrusted input.
|
||||
- Use a dedicated `hooks.token`; do not reuse the shared Gateway token.
|
||||
- Use a dedicated `hooks.token`; do not reuse active Gateway auth secrets (`gateway.auth.token` / `OPENCLAW_GATEWAY_TOKEN` or `gateway.auth.password` / `OPENCLAW_GATEWAY_PASSWORD`).
|
||||
- Hook auth is header-only (`Authorization: Bearer ...` or `x-openclaw-token`); query-string tokens are rejected.
|
||||
- `hooks.path` cannot be `/`; keep webhook ingress on a dedicated subpath such as `/hooks`.
|
||||
- Keep unsafe-content bypass flags disabled (`hooks.gmail.allowUnsafeExternalContent`, `hooks.mappings[].allowUnsafeExternalContent`) unless doing tightly scoped debugging.
|
||||
|
||||
@@ -64,7 +64,7 @@ exhaustive):
|
||||
| `security.audit.suppressions.active` | info | Audit output has configured suppressions and may be filtered | `security.audit.suppressions` | no |
|
||||
| `config.secrets.gateway_password_in_config` | warn | Gateway password is stored directly in config | `gateway.auth.password` | no |
|
||||
| `config.secrets.hooks_token_in_config` | warn | Hook bearer token is stored directly in config | `hooks.token` | no |
|
||||
| `hooks.token_reuse_gateway_token` | critical | Hook ingress token also unlocks Gateway auth | `hooks.token`, `gateway.auth.token` | no |
|
||||
| `hooks.token_reuse_gateway_token` | critical | Hook ingress token also unlocks Gateway auth | `hooks.token`, `gateway.auth.token`, `gateway.auth.password` | no |
|
||||
| `hooks.token_too_short` | warn | Easier brute force on hook ingress | `hooks.token` | no |
|
||||
| `hooks.default_session_key_unset` | warn | Hook agent runs fan out into generated per-request sessions | `hooks.defaultSessionKey` | no |
|
||||
| `hooks.allowed_agent_ids_unrestricted` | warn/critical | Authenticated hook callers may route to any configured agent | `hooks.allowedAgentIds` | no |
|
||||
|
||||
@@ -170,11 +170,19 @@ describe("security CLI", () => {
|
||||
title: "forwards --token to deep probe auth without altering command-level resolver mode",
|
||||
argv: ["--token", "explicit-token"],
|
||||
deepProbeAuth: { token: "explicit-token" },
|
||||
auditGatewayAuthOverride: undefined,
|
||||
},
|
||||
{
|
||||
title: "forwards --password to deep probe auth without altering command-level resolver mode",
|
||||
argv: ["--password", "explicit-password"],
|
||||
deepProbeAuth: { password: "explicit-password" },
|
||||
auditGatewayAuthOverride: undefined,
|
||||
},
|
||||
{
|
||||
title: "forwards --auth with explicit gateway password",
|
||||
argv: ["--auth", "password", "--password", "explicit-password"],
|
||||
deepProbeAuth: { password: "explicit-password" },
|
||||
auditGatewayAuthOverride: { mode: "password", password: "explicit-password" },
|
||||
},
|
||||
{
|
||||
title: "forwards both --token and --password to deep probe auth",
|
||||
@@ -183,8 +191,9 @@ describe("security CLI", () => {
|
||||
token: "explicit-token",
|
||||
password: "explicit-password",
|
||||
},
|
||||
auditGatewayAuthOverride: undefined,
|
||||
},
|
||||
])("$title", async ({ argv, deepProbeAuth }) => {
|
||||
])("$title", async ({ argv, deepProbeAuth, auditGatewayAuthOverride }) => {
|
||||
primeDeepAuditConfig();
|
||||
|
||||
await createProgram().parseAsync(["security", "audit", "--deep", ...argv, "--json"], {
|
||||
@@ -194,5 +203,27 @@ describe("security CLI", () => {
|
||||
expect(lastSecretResolverOptions()?.mode).toBe("read_only_status");
|
||||
expect(lastSecurityAuditOptions()?.deep).toBe(true);
|
||||
expect(lastSecurityAuditOptions()?.deepProbeAuth).toEqual(deepProbeAuth);
|
||||
expect(lastSecurityAuditOptions()?.auditGatewayAuthOverride).toEqual(auditGatewayAuthOverride);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
argv: ["--auth", "token"],
|
||||
message: /pass --token <token>/i,
|
||||
},
|
||||
{
|
||||
argv: ["--auth", "password"],
|
||||
message: /pass --password <password>/i,
|
||||
},
|
||||
])(
|
||||
"rejects shared-secret auth override without the matching secret",
|
||||
async ({ argv, message }) => {
|
||||
primeDeepAuditConfig();
|
||||
|
||||
await expect(
|
||||
createProgram().parseAsync(["security", "audit", ...argv, "--json"], { from: "user" }),
|
||||
).rejects.toThrow(message);
|
||||
expect(runSecurityAudit).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
+59
-2
@@ -1,9 +1,13 @@
|
||||
import type { Command } from "commander";
|
||||
import { getRuntimeConfig } from "../config/config.js";
|
||||
import type { GatewayAuthMode } from "../config/types.gateway.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { runSecurityAudit } from "../security/audit.js";
|
||||
import { fixSecurityFootguns } from "../security/fix.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { formatDocsLink } from "../terminal/links.js";
|
||||
import { isRich, theme } from "../terminal/theme.js";
|
||||
import { shortenHomeInString, shortenHomePath } from "../utils.js";
|
||||
@@ -16,10 +20,45 @@ type SecurityAuditOptions = {
|
||||
json?: boolean;
|
||||
deep?: boolean;
|
||||
fix?: boolean;
|
||||
auth?: string;
|
||||
token?: string;
|
||||
password?: string;
|
||||
};
|
||||
|
||||
function parseGatewayAuthMode(value: string | undefined): GatewayAuthMode | undefined {
|
||||
const mode = normalizeOptionalLowercaseString(value);
|
||||
if (!mode) {
|
||||
return undefined;
|
||||
}
|
||||
if (mode === "none" || mode === "token" || mode === "password" || mode === "trusted-proxy") {
|
||||
return mode;
|
||||
}
|
||||
throw new Error(
|
||||
'Invalid --auth value. Expected "none", "token", "password", or "trusted-proxy".',
|
||||
);
|
||||
}
|
||||
|
||||
function buildAuditGatewayAuthOverride(params: {
|
||||
mode?: GatewayAuthMode;
|
||||
token?: string;
|
||||
password?: string;
|
||||
}) {
|
||||
if (!params.mode) {
|
||||
return undefined;
|
||||
}
|
||||
if (params.mode === "token" && !params.token) {
|
||||
throw new Error("Invalid --auth token: pass --token <token> for audit auth override.");
|
||||
}
|
||||
if (params.mode === "password" && !params.password) {
|
||||
throw new Error("Invalid --auth password: pass --password <password> for audit auth override.");
|
||||
}
|
||||
return {
|
||||
mode: params.mode,
|
||||
...(params.token ? { token: params.token } : {}),
|
||||
...(params.password ? { password: params.password } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function formatSummary(summary: { critical: number; warn: number; info: number }): string {
|
||||
const rich = isRich();
|
||||
const c = summary.critical;
|
||||
@@ -50,6 +89,10 @@ export function registerSecurityCli(program: Command) {
|
||||
"openclaw security audit --deep --password <password>",
|
||||
"Use explicit password for deep probe.",
|
||||
],
|
||||
[
|
||||
"openclaw security audit --auth password --password <password>",
|
||||
"Audit a runtime-only password-mode Gateway secret.",
|
||||
],
|
||||
["openclaw security audit --fix", "Apply safe remediations and file-permission fixes."],
|
||||
["openclaw security audit --json", "Output machine-readable JSON."],
|
||||
])}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/security", "docs.openclaw.ai/cli/security")}\n`,
|
||||
@@ -59,13 +102,23 @@ export function registerSecurityCli(program: Command) {
|
||||
.command("audit")
|
||||
.description("Audit config + local state for common security foot-guns")
|
||||
.option("--deep", "Attempt live Gateway probes and plugin-owned collector checks", false)
|
||||
.option(
|
||||
"--auth <mode>",
|
||||
'Runtime gateway auth mode ("none"|"token"|"password"|"trusted-proxy")',
|
||||
)
|
||||
.option("--token <token>", "Use explicit gateway token for deep probe auth")
|
||||
.option("--password <password>", "Use explicit gateway password for deep probe auth")
|
||||
.option("--fix", "Apply safe fixes (tighten defaults + chmod state/config)", false)
|
||||
.option("--json", "Print JSON", false)
|
||||
.action(async (opts: SecurityAuditOptions) => {
|
||||
const authMode = parseGatewayAuthMode(opts.auth);
|
||||
const token = normalizeOptionalString(opts.token);
|
||||
const password = normalizeOptionalString(opts.password);
|
||||
const auditGatewayAuthOverride = buildAuditGatewayAuthOverride({
|
||||
mode: authMode,
|
||||
token,
|
||||
password,
|
||||
});
|
||||
const fixResult = opts.fix ? await fixSecurityFootguns().catch((_err) => null) : null;
|
||||
|
||||
const sourceConfig = getRuntimeConfig();
|
||||
@@ -84,8 +137,12 @@ export function registerSecurityCli(program: Command) {
|
||||
includeChannelSecurity: true,
|
||||
deepProbeAuth:
|
||||
token || password
|
||||
? { ...(token ? { token } : {}), ...(password ? { password } : {}) }
|
||||
? {
|
||||
...(token ? { token } : {}),
|
||||
...(password ? { password } : {}),
|
||||
}
|
||||
: undefined,
|
||||
auditGatewayAuthOverride,
|
||||
});
|
||||
|
||||
if (opts.json) {
|
||||
|
||||
@@ -414,6 +414,27 @@ describe("ensureGatewayStartupAuth", () => {
|
||||
).rejects.toThrow(/hooks\.token must not match gateway auth token/i);
|
||||
});
|
||||
|
||||
it("does not block startup when hooks token reuses gateway password auth", async () => {
|
||||
const result = await ensureGatewayStartupAuth({
|
||||
cfg: {
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "shared-gateway-password-1234567890",
|
||||
},
|
||||
gateway: {
|
||||
auth: {
|
||||
mode: "password",
|
||||
password: "shared-gateway-password-1234567890", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
});
|
||||
|
||||
expect(result.auth.mode).toBe("password");
|
||||
expect(result.generatedToken).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each(KNOWN_WEAK_GATEWAY_TOKEN_PLACEHOLDERS)(
|
||||
"rejects the published placeholder token %s supplied via environment",
|
||||
async (token) => {
|
||||
@@ -580,19 +601,58 @@ describe("assertHooksTokenSeparateFromGatewayAuth", () => {
|
||||
).toThrow(/hooks\.token must not match gateway auth token/i);
|
||||
});
|
||||
|
||||
it("allows hooks token when gateway auth is not token mode", () => {
|
||||
it("allows hooks token reuse of gateway password auth", () => {
|
||||
expect(
|
||||
assertHooksTokenSeparateFromGatewayAuth({
|
||||
cfg: {
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "shared-gateway-token-1234567890",
|
||||
token: "shared-gateway-password-1234567890",
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
mode: "password",
|
||||
modeSource: "config",
|
||||
password: "pw", // pragma: allowlist secret
|
||||
password: "shared-gateway-password-1234567890", // pragma: allowlist secret
|
||||
allowTailscale: false,
|
||||
},
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("allows hooks token reuse of trusted-proxy local password fallback", () => {
|
||||
expect(
|
||||
assertHooksTokenSeparateFromGatewayAuth({
|
||||
cfg: {
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "trusted-proxy-local-password-1234567890",
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
mode: "trusted-proxy",
|
||||
modeSource: "config",
|
||||
trustedProxy: { userHeader: "x-forwarded-user" },
|
||||
password: "trusted-proxy-local-password-1234567890", // pragma: allowlist secret
|
||||
allowTailscale: false,
|
||||
},
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("allows distinct hooks token when gateway auth is password mode", () => {
|
||||
expect(
|
||||
assertHooksTokenSeparateFromGatewayAuth({
|
||||
cfg: {
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "hook-token-1234567890",
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
mode: "password",
|
||||
modeSource: "config",
|
||||
password: "gateway-password-1234567890", // pragma: allowlist secret
|
||||
allowTailscale: false,
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -6,9 +6,10 @@ import { getBlockedBindReason } from "../agents/sandbox/validate-sandbox-securit
|
||||
import { isToolAllowedByPolicies } from "../agents/tool-policy-match.js";
|
||||
import { resolveToolProfilePolicy } from "../agents/tool-policy.js";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import type { GatewayAuthConfig } from "../config/types.gateway.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { AgentToolsConfig } from "../config/types.tools.js";
|
||||
import { resolveGatewayAuth } from "../gateway/auth.js";
|
||||
import { resolveGatewayAuth, type ResolvedGatewayAuth } from "../gateway/auth.js";
|
||||
import { resolveAllowedAgentIds } from "../gateway/hooks-policy.js";
|
||||
import {
|
||||
DEFAULT_DANGEROUS_NODE_COMMANDS,
|
||||
@@ -37,6 +38,20 @@ export type SecurityAuditFinding = {
|
||||
remediation?: string;
|
||||
};
|
||||
|
||||
export type HooksHardeningAuditOptions = {
|
||||
gatewayAuthOverride?: Pick<GatewayAuthConfig, "mode" | "token" | "password">;
|
||||
};
|
||||
|
||||
export type GatewayHttpNoAuthAuditOptions = {
|
||||
gatewayAuthOverride?: Pick<GatewayAuthConfig, "mode" | "token" | "password">;
|
||||
};
|
||||
|
||||
type GatewayAuthSharedSecretLabel = "gateway auth token" | "gateway auth password";
|
||||
type ActiveGatewaySharedSecret = {
|
||||
label: GatewayAuthSharedSecretLabel;
|
||||
value?: string;
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -66,6 +81,73 @@ function isGatewayRemotelyExposed(cfg: OpenClawConfig): boolean {
|
||||
return tailscaleMode === "serve" || tailscaleMode === "funnel";
|
||||
}
|
||||
|
||||
function formatGatewayAuthDisplayLabel(label: GatewayAuthSharedSecretLabel): string {
|
||||
if (label === "gateway auth password") {
|
||||
return "Gateway password";
|
||||
}
|
||||
return "Gateway token";
|
||||
}
|
||||
|
||||
function formatHooksTokenReuseDetail(reusedGatewayAuthLabel: GatewayAuthSharedSecretLabel): string {
|
||||
if (reusedGatewayAuthLabel === "gateway auth password") {
|
||||
return "hooks.token matches gateway.auth password; compromise of hooks expands blast radius to Gateway password auth.";
|
||||
}
|
||||
return "hooks.token matches gateway.auth token; compromise of hooks expands blast radius to the Gateway API.";
|
||||
}
|
||||
|
||||
function listActiveGatewaySharedSecrets(auth: ResolvedGatewayAuth): ActiveGatewaySharedSecret[] {
|
||||
if (auth.mode === "token") {
|
||||
return [{ label: "gateway auth token", value: auth.token }];
|
||||
}
|
||||
if (auth.mode === "password" || auth.mode === "trusted-proxy") {
|
||||
return [{ label: "gateway auth password", value: auth.password }];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function findGatewayAuthLabelMatchingHooksToken(params: {
|
||||
hooksToken: string;
|
||||
auth: ResolvedGatewayAuth;
|
||||
}): GatewayAuthSharedSecretLabel | undefined {
|
||||
return listActiveGatewaySharedSecrets(params.auth).find(
|
||||
(candidate) => normalizeOptionalString(candidate.value) === params.hooksToken,
|
||||
)?.label;
|
||||
}
|
||||
|
||||
function findHooksTokenGatewayAuthReuseLabel(params: {
|
||||
hooksToken: string;
|
||||
configGatewayAuth: ResolvedGatewayAuth;
|
||||
overrideGatewayAuth?: ResolvedGatewayAuth;
|
||||
}): GatewayAuthSharedSecretLabel | undefined {
|
||||
const configReuseLabel = findGatewayAuthLabelMatchingHooksToken({
|
||||
hooksToken: params.hooksToken,
|
||||
auth: params.configGatewayAuth,
|
||||
});
|
||||
if (configReuseLabel) {
|
||||
return configReuseLabel;
|
||||
}
|
||||
|
||||
return params.overrideGatewayAuth
|
||||
? findGatewayAuthLabelMatchingHooksToken({
|
||||
hooksToken: params.hooksToken,
|
||||
auth: params.overrideGatewayAuth,
|
||||
})
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function hasResolvedGatewayHttpAuth(auth: ResolvedGatewayAuth): boolean {
|
||||
if (auth.mode === "token") {
|
||||
return Boolean(normalizeOptionalString(auth.token));
|
||||
}
|
||||
if (auth.mode === "password") {
|
||||
return Boolean(normalizeOptionalString(auth.password));
|
||||
}
|
||||
if (auth.mode === "trusted-proxy") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const LEGACY_MODEL_PATTERNS: Array<{ id: string; re: RegExp; label: string }> = [
|
||||
{ id: "openai.gpt35", re: /\bgpt-3\.5\b/i, label: "GPT-3.5 family" },
|
||||
{ id: "anthropic.claude2", re: /\bclaude-(instant|2)\b/i, label: "Claude 2/Instant family" },
|
||||
@@ -497,6 +579,7 @@ export function collectSecretsInConfigFindings(cfg: OpenClawConfig): SecurityAud
|
||||
export function collectHooksHardeningFindings(
|
||||
cfg: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
options: HooksHardeningAuditOptions = {},
|
||||
): SecurityAuditFinding[] {
|
||||
const findings: SecurityAuditFinding[] = [];
|
||||
if (cfg.hooks?.enabled !== true) {
|
||||
@@ -513,31 +596,32 @@ export function collectHooksHardeningFindings(
|
||||
});
|
||||
}
|
||||
|
||||
const gatewayAuth = resolveGatewayAuth({
|
||||
const configGatewayAuth = resolveGatewayAuth({
|
||||
authConfig: cfg.gateway?.auth,
|
||||
tailscaleMode: cfg.gateway?.tailscale?.mode ?? "off",
|
||||
env,
|
||||
});
|
||||
const openclawGatewayToken =
|
||||
typeof env.OPENCLAW_GATEWAY_TOKEN === "string" && env.OPENCLAW_GATEWAY_TOKEN.trim()
|
||||
? env.OPENCLAW_GATEWAY_TOKEN.trim()
|
||||
: null;
|
||||
const gatewayToken =
|
||||
gatewayAuth.mode === "token" &&
|
||||
typeof gatewayAuth.token === "string" &&
|
||||
gatewayAuth.token.trim()
|
||||
? gatewayAuth.token.trim()
|
||||
: openclawGatewayToken
|
||||
? openclawGatewayToken
|
||||
: null;
|
||||
if (token && gatewayToken && token === gatewayToken) {
|
||||
const overrideGatewayAuth = options.gatewayAuthOverride
|
||||
? resolveGatewayAuth({
|
||||
authConfig: cfg.gateway?.auth,
|
||||
authOverride: options.gatewayAuthOverride,
|
||||
tailscaleMode: cfg.gateway?.tailscale?.mode ?? "off",
|
||||
env,
|
||||
})
|
||||
: undefined;
|
||||
const reusedGatewayAuthLabel = findHooksTokenGatewayAuthReuseLabel({
|
||||
hooksToken: token,
|
||||
configGatewayAuth,
|
||||
overrideGatewayAuth,
|
||||
});
|
||||
if (reusedGatewayAuthLabel) {
|
||||
findings.push({
|
||||
checkId: "hooks.token_reuse_gateway_token",
|
||||
severity: "critical",
|
||||
title: "Hooks token reuses the Gateway token",
|
||||
detail:
|
||||
"hooks.token matches gateway.auth token; compromise of hooks expands blast radius to the Gateway API.",
|
||||
remediation: "Use a separate hooks.token dedicated to hook ingress.",
|
||||
title: `Hooks token reuses the ${formatGatewayAuthDisplayLabel(reusedGatewayAuthLabel)}`,
|
||||
detail: formatHooksTokenReuseDetail(reusedGatewayAuthLabel),
|
||||
remediation:
|
||||
"Use a separate hooks.token dedicated to hook ingress and keep it distinct from Gateway token/password shared-secret auth.",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -642,11 +726,17 @@ export function collectGatewayHttpSessionKeyOverrideFindings(
|
||||
export function collectGatewayHttpNoAuthFindings(
|
||||
cfg: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
options: GatewayHttpNoAuthAuditOptions = {},
|
||||
): SecurityAuditFinding[] {
|
||||
const findings: SecurityAuditFinding[] = [];
|
||||
const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off";
|
||||
const auth = resolveGatewayAuth({ authConfig: cfg.gateway?.auth, tailscaleMode, env });
|
||||
if (auth.mode !== "none") {
|
||||
const auth = resolveGatewayAuth({
|
||||
authConfig: cfg.gateway?.auth,
|
||||
authOverride: options.gatewayAuthOverride,
|
||||
tailscaleMode,
|
||||
env,
|
||||
});
|
||||
if (hasResolvedGatewayHttpAuth(auth)) {
|
||||
return findings;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isIP } from "node:net";
|
||||
import type { GatewayAuthConfig } from "../config/types.gateway.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { hasConfiguredSecretInput } from "../config/types.secrets.js";
|
||||
import { resolveGatewayAuth } from "../gateway/auth-resolve.js";
|
||||
@@ -15,6 +16,7 @@ type CollectDangerousConfigFlags = (cfg: OpenClawConfig) => string[];
|
||||
|
||||
export type CollectGatewayConfigFindingsOptions = {
|
||||
collectDangerousConfigFlags?: CollectDangerousConfigFlags;
|
||||
gatewayAuthOverride?: Pick<GatewayAuthConfig, "mode" | "token" | "password">;
|
||||
};
|
||||
|
||||
function hasNonEmptyString(value: unknown): value is string {
|
||||
@@ -31,7 +33,12 @@ export function collectGatewayConfigFindings(
|
||||
|
||||
const bind = typeof cfg.gateway?.bind === "string" ? cfg.gateway.bind : "loopback";
|
||||
const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off";
|
||||
const auth = resolveGatewayAuth({ authConfig: cfg.gateway?.auth, tailscaleMode, env });
|
||||
const auth = resolveGatewayAuth({
|
||||
authConfig: cfg.gateway?.auth,
|
||||
authOverride: options.gatewayAuthOverride,
|
||||
tailscaleMode,
|
||||
env,
|
||||
});
|
||||
const controlUiEnabled = cfg.gateway?.controlUi?.enabled !== false;
|
||||
const controlUiAllowedOrigins = (cfg.gateway?.controlUi?.allowedOrigins ?? [])
|
||||
.map((value) => value.trim())
|
||||
@@ -57,7 +64,7 @@ export function collectGatewayConfigFindings(
|
||||
sourceConfig.gateway?.remote?.token,
|
||||
sourceConfig.secrets?.defaults,
|
||||
);
|
||||
const explicitAuthMode = sourceConfig.gateway?.auth?.mode;
|
||||
const explicitAuthMode = options.gatewayAuthOverride?.mode ?? sourceConfig.gateway?.auth?.mode;
|
||||
const tokenCanWin =
|
||||
hasToken || envTokenConfigured || tokenConfiguredFromConfig || remoteTokenConfigured;
|
||||
const passwordCanWin =
|
||||
|
||||
@@ -62,6 +62,45 @@ describe("security audit gateway HTTP auth findings", () => {
|
||||
expectedNoFinding: "gateway.http.no_auth",
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
},
|
||||
{
|
||||
name: "does not report gateway.http.no_auth with runtime password auth override",
|
||||
cfg: {
|
||||
gateway: {
|
||||
bind: "loopback",
|
||||
auth: { mode: "none" },
|
||||
http: {
|
||||
endpoints: {
|
||||
chatCompletions: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies OpenClawConfig,
|
||||
expectedNoFinding: "gateway.http.no_auth",
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
gatewayAuthOverride: {
|
||||
mode: "password" as const,
|
||||
password: "runtime-gateway-password-1234567890", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reports gateway.http.no_auth when runtime password mode lacks a password",
|
||||
cfg: {
|
||||
gateway: {
|
||||
bind: "loopback",
|
||||
auth: { mode: "none" },
|
||||
http: {
|
||||
endpoints: {
|
||||
chatCompletions: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies OpenClawConfig,
|
||||
expectedFinding: { checkId: "gateway.http.no_auth", severity: "warn" as const },
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
gatewayAuthOverride: {
|
||||
mode: "password" as const,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reports HTTP API session-key override surfaces when enabled",
|
||||
cfg: {
|
||||
@@ -79,23 +118,26 @@ describe("security audit gateway HTTP auth findings", () => {
|
||||
severity: "info" as const,
|
||||
},
|
||||
},
|
||||
])("$name", ({ cfg, expectedFinding, expectedNoFinding, detailIncludes, env }) => {
|
||||
const findings = [
|
||||
...collectGatewayHttpNoAuthFindings(cfg, env ?? process.env),
|
||||
...collectGatewayHttpSessionKeyOverrideFindings(cfg),
|
||||
];
|
||||
])(
|
||||
"$name",
|
||||
({ cfg, expectedFinding, expectedNoFinding, detailIncludes, env, gatewayAuthOverride }) => {
|
||||
const findings = [
|
||||
...collectGatewayHttpNoAuthFindings(cfg, env ?? process.env, { gatewayAuthOverride }),
|
||||
...collectGatewayHttpSessionKeyOverrideFindings(cfg),
|
||||
];
|
||||
|
||||
if (expectedFinding) {
|
||||
const finding = requireFinding(findings, expectedFinding.checkId);
|
||||
expect(finding.severity).toBe(expectedFinding.severity);
|
||||
if (detailIncludes) {
|
||||
for (const text of detailIncludes) {
|
||||
expect(finding.detail, `${expectedFinding.checkId}:${text}`).toContain(text);
|
||||
if (expectedFinding) {
|
||||
const finding = requireFinding(findings, expectedFinding.checkId);
|
||||
expect(finding.severity).toBe(expectedFinding.severity);
|
||||
if (detailIncludes) {
|
||||
for (const text of detailIncludes) {
|
||||
expect(finding.detail, `${expectedFinding.checkId}:${text}`).toContain(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expectedNoFinding) {
|
||||
expect(findings.map((entry) => entry.checkId)).not.toContain(expectedNoFinding);
|
||||
}
|
||||
});
|
||||
if (expectedNoFinding) {
|
||||
expect(findings.map((entry) => entry.checkId)).not.toContain(expectedNoFinding);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -112,6 +112,29 @@ describe("security audit gateway config findings", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("honors runtime password auth override for bind auth checks", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
gateway: {
|
||||
bind: "lan",
|
||||
auth: {},
|
||||
},
|
||||
};
|
||||
|
||||
const findings = collectGatewayConfigFindings(
|
||||
cfg,
|
||||
cfg,
|
||||
{},
|
||||
{
|
||||
gatewayAuthOverride: {
|
||||
mode: "password",
|
||||
password: "runtime-gateway-password-1234567890", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(hasFinding("gateway.bind_no_auth", findings)).toBe(false);
|
||||
});
|
||||
|
||||
it("warns when OPENCLAW_GATEWAY_TOKEN shadows a different configured token source", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
gateway: { auth: { token: "config-token" } },
|
||||
|
||||
@@ -10,6 +10,10 @@ function hasFinding(
|
||||
return findings.some((finding) => finding.checkId === checkId && finding.severity === severity);
|
||||
}
|
||||
|
||||
function getFinding(findings: ReturnType<typeof collectHooksHardeningFindings>, checkId: string) {
|
||||
return findings.find((finding) => finding.checkId === checkId);
|
||||
}
|
||||
|
||||
describe("security audit hooks ingress findings", () => {
|
||||
it("evaluates hooks ingress auth and routing findings", () => {
|
||||
const unrestrictedBaseHooks = {
|
||||
@@ -114,4 +118,155 @@ describe("security audit hooks ingress findings", () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("flags hooks token reuse of gateway password auth as critical", () => {
|
||||
const findings = collectHooksHardeningFindings({
|
||||
gateway: {
|
||||
auth: {
|
||||
mode: "password",
|
||||
password: "shared-gateway-password-1234567890", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "shared-gateway-password-1234567890",
|
||||
},
|
||||
});
|
||||
|
||||
expect(hasFinding(findings, "hooks.token_reuse_gateway_token", "critical")).toBe(true);
|
||||
|
||||
const finding = getFinding(findings, "hooks.token_reuse_gateway_token");
|
||||
expect(finding?.title).toContain("Gateway password");
|
||||
expect(finding?.detail).toContain("gateway.auth password");
|
||||
expect(finding?.remediation).toContain("Gateway token/password");
|
||||
});
|
||||
|
||||
it("flags hooks token reuse of trusted-proxy local password fallback as critical", () => {
|
||||
const findings = collectHooksHardeningFindings({
|
||||
gateway: {
|
||||
auth: {
|
||||
mode: "trusted-proxy",
|
||||
trustedProxy: { userHeader: "x-forwarded-user" },
|
||||
password: "trusted-proxy-local-password-1234567890", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "trusted-proxy-local-password-1234567890",
|
||||
},
|
||||
});
|
||||
|
||||
expect(hasFinding(findings, "hooks.token_reuse_gateway_token", "critical")).toBe(true);
|
||||
|
||||
const finding = getFinding(findings, "hooks.token_reuse_gateway_token");
|
||||
expect(finding?.title).toContain("Gateway password");
|
||||
expect(finding?.detail).toContain("gateway.auth password");
|
||||
});
|
||||
|
||||
it("flags hooks token reuse of an explicit audit password override as critical", () => {
|
||||
const findings = collectHooksHardeningFindings(
|
||||
{
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "runtime-only-gateway-password-1234567890",
|
||||
},
|
||||
},
|
||||
{} as NodeJS.ProcessEnv,
|
||||
{
|
||||
gatewayAuthOverride: {
|
||||
password: "runtime-only-gateway-password-1234567890", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(hasFinding(findings, "hooks.token_reuse_gateway_token", "critical")).toBe(true);
|
||||
|
||||
const finding = getFinding(findings, "hooks.token_reuse_gateway_token");
|
||||
expect(finding?.title).toContain("Gateway password");
|
||||
expect(finding?.detail).toContain("gateway.auth password");
|
||||
});
|
||||
|
||||
it("does not flag inactive explicit audit password when config mode is token", () => {
|
||||
const findings = collectHooksHardeningFindings(
|
||||
{
|
||||
gateway: {
|
||||
auth: {
|
||||
mode: "token",
|
||||
token: "config-gateway-token-1234567890", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "runtime-only-gateway-password-1234567890",
|
||||
},
|
||||
},
|
||||
{} as NodeJS.ProcessEnv,
|
||||
{
|
||||
gatewayAuthOverride: {
|
||||
password: "runtime-only-gateway-password-1234567890", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(hasFinding(findings, "hooks.token_reuse_gateway_token", "critical")).toBe(false);
|
||||
});
|
||||
|
||||
it("flags explicit audit password reuse when config mode is token", () => {
|
||||
const findings = collectHooksHardeningFindings(
|
||||
{
|
||||
gateway: {
|
||||
auth: {
|
||||
mode: "token",
|
||||
token: "config-gateway-token-1234567890", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "runtime-only-gateway-password-1234567890",
|
||||
},
|
||||
},
|
||||
{} as NodeJS.ProcessEnv,
|
||||
{
|
||||
gatewayAuthOverride: {
|
||||
mode: "password",
|
||||
password: "runtime-only-gateway-password-1234567890", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(hasFinding(findings, "hooks.token_reuse_gateway_token", "critical")).toBe(true);
|
||||
|
||||
const finding = getFinding(findings, "hooks.token_reuse_gateway_token");
|
||||
expect(finding?.title).toContain("Gateway password");
|
||||
expect(finding?.detail).toContain("gateway.auth password");
|
||||
});
|
||||
|
||||
it("keeps config password reuse finding when explicit audit password differs", () => {
|
||||
const findings = collectHooksHardeningFindings(
|
||||
{
|
||||
gateway: {
|
||||
auth: {
|
||||
mode: "password",
|
||||
password: "config-gateway-password-1234567890", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
enabled: true,
|
||||
token: "config-gateway-password-1234567890",
|
||||
},
|
||||
},
|
||||
{} as NodeJS.ProcessEnv,
|
||||
{
|
||||
gatewayAuthOverride: {
|
||||
password: "different-runtime-password-1234567890", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(hasFinding(findings, "hooks.token_reuse_gateway_token", "critical")).toBe(true);
|
||||
|
||||
const finding = getFinding(findings, "hooks.token_reuse_gateway_token");
|
||||
expect(finding?.title).toContain("Gateway password");
|
||||
expect(finding?.detail).toContain("gateway.auth password");
|
||||
});
|
||||
});
|
||||
|
||||
+29
-5
@@ -4,6 +4,7 @@ import { resolveSandboxConfigForAgent } from "../agents/sandbox/config.js";
|
||||
import type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
|
||||
import type { ConfigFileSnapshot, OpenClawConfig } from "../config/config.js";
|
||||
import { resolveConfigPath, resolveStateDir } from "../config/paths.js";
|
||||
import type { GatewayAuthConfig } from "../config/types.gateway.js";
|
||||
import type { SecurityAuditSuppression } from "../config/types.openclaw.js";
|
||||
import { isInterpreterLikeAllowlistPattern } from "../infra/command-analysis/inline-eval.js";
|
||||
import { type ExecApprovalsFile, loadExecApprovals } from "../infra/exec-approvals.js";
|
||||
@@ -36,6 +37,11 @@ import type { ExecFn } from "./windows-acl.js";
|
||||
|
||||
type ExecDockerRawFn = typeof import("../agents/sandbox/docker.js").execDockerRaw;
|
||||
type ProbeGatewayFn = typeof import("../gateway/probe.js").probeGateway;
|
||||
type SecurityAuditExplicitGatewayAuth = {
|
||||
token?: string;
|
||||
password?: string;
|
||||
};
|
||||
type SecurityAuditGatewayAuthOverride = Pick<GatewayAuthConfig, "mode" | "token" | "password">;
|
||||
|
||||
export type {
|
||||
SecurityAuditFinding,
|
||||
@@ -71,7 +77,9 @@ export type SecurityAuditOptions = {
|
||||
/** Optional cache for code-safety summaries across repeated deep audits. */
|
||||
codeSafetySummaryCache?: Map<string, Promise<unknown>>;
|
||||
/** Optional explicit auth for deep gateway probe. */
|
||||
deepProbeAuth?: { token?: string; password?: string };
|
||||
deepProbeAuth?: SecurityAuditExplicitGatewayAuth;
|
||||
/** Optional explicit Gateway auth mode/secret for config-only audit checks. */
|
||||
auditGatewayAuthOverride?: SecurityAuditGatewayAuthOverride;
|
||||
/** Override workspace used for workspace plugin discovery. */
|
||||
workspaceDir?: string;
|
||||
/** Dependency injection for tests. */
|
||||
@@ -96,7 +104,8 @@ export type AuditExecutionContext = {
|
||||
loadPluginSecurityCollectors: boolean;
|
||||
configSnapshot: ConfigFileSnapshot | null;
|
||||
codeSafetySummaryCache: Map<string, Promise<unknown>>;
|
||||
deepProbeAuth?: { token?: string; password?: string };
|
||||
deepProbeAuth?: SecurityAuditExplicitGatewayAuth;
|
||||
auditGatewayAuthOverride?: SecurityAuditGatewayAuthOverride;
|
||||
workspaceDir?: string;
|
||||
};
|
||||
|
||||
@@ -399,9 +408,11 @@ export function collectGatewayConfigFindings(
|
||||
cfg: OpenClawConfig,
|
||||
sourceConfig: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
options: { gatewayAuthOverride?: SecurityAuditGatewayAuthOverride } = {},
|
||||
): SecurityAuditFinding[] {
|
||||
return collectGatewayConfigFindingsBase(cfg, sourceConfig, env, {
|
||||
collectDangerousConfigFlags: collectEnabledInsecureOrDangerousFlags,
|
||||
gatewayAuthOverride: options.gatewayAuthOverride,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1032,6 +1043,7 @@ async function createAuditExecutionContext(
|
||||
configSnapshot,
|
||||
codeSafetySummaryCache: opts.codeSafetySummaryCache ?? new Map<string, Promise<unknown>>(),
|
||||
deepProbeAuth: opts.deepProbeAuth,
|
||||
auditGatewayAuthOverride: opts.auditGatewayAuthOverride,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1044,13 +1056,25 @@ export async function runSecurityAudit(opts: SecurityAuditOptions): Promise<Secu
|
||||
findings.push(...auditNonDeep.collectAttackSurfaceSummaryFindings(cfg));
|
||||
findings.push(...auditNonDeep.collectSyncedFolderFindings({ stateDir, configPath }));
|
||||
|
||||
findings.push(...collectGatewayConfigFindings(cfg, context.sourceConfig, env));
|
||||
findings.push(
|
||||
...collectGatewayConfigFindings(cfg, context.sourceConfig, env, {
|
||||
gatewayAuthOverride: context.auditGatewayAuthOverride,
|
||||
}),
|
||||
);
|
||||
findings.push(...(await collectPluginSecurityAuditFindings(context)));
|
||||
findings.push(...collectLoggingFindings(cfg));
|
||||
findings.push(...collectElevatedFindings(cfg));
|
||||
findings.push(...collectExecRuntimeFindings(cfg));
|
||||
findings.push(...auditNonDeep.collectHooksHardeningFindings(cfg, env));
|
||||
findings.push(...auditNonDeep.collectGatewayHttpNoAuthFindings(cfg, env));
|
||||
findings.push(
|
||||
...auditNonDeep.collectHooksHardeningFindings(cfg, env, {
|
||||
gatewayAuthOverride: context.auditGatewayAuthOverride,
|
||||
}),
|
||||
);
|
||||
findings.push(
|
||||
...auditNonDeep.collectGatewayHttpNoAuthFindings(cfg, env, {
|
||||
gatewayAuthOverride: context.auditGatewayAuthOverride,
|
||||
}),
|
||||
);
|
||||
findings.push(...auditNonDeep.collectGatewayHttpSessionKeyOverrideFindings(cfg));
|
||||
findings.push(...auditNonDeep.collectSandboxDockerNoopFindings(cfg));
|
||||
findings.push(...auditNonDeep.collectSandboxDangerousConfigFindings(cfg));
|
||||
|
||||
Reference in New Issue
Block a user