Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion agents/langchain-deepagents-code/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,44 @@ ENV NPM_CONFIG_AUDIT=false \
NPM_CONFIG_UPDATE_NOTIFIER=false
WORKDIR /opt/mcp-tool-discovery-runtime
COPY tools/mcp-tool-discovery-runtime/package.json tools/mcp-tool-discovery-runtime/package-lock.json tools/mcp-tool-discovery-runtime/tsconfig.json tools/mcp-tool-discovery-runtime/install-reviewed-runtime.sh tools/mcp-tool-discovery-runtime/*.ts ./
RUN ./install-reviewed-runtime.sh \
# hadolint ignore=DL4006
RUN set -eu; \
if [ -n "${NEMOCLAW_CORPORATE_CA_B64}" ]; then \
{ printf '%s' "${NEMOCLAW_CORPORATE_CA_B64}" | base64 --decode > /tmp/nemoclaw-corporate-ca.pem 2>/dev/null \
|| { echo "[nemoclaw] NEMOCLAW_CORPORATE_CA_B64 is not valid base64; expected a single-line base64-encoded PEM (#6210)" >&2; exit 1; }; }; \
node -e 'const fs = require("node:fs"); const { X509Certificate } = require("node:crypto"); const pem = fs.readFileSync(process.argv[1], "utf8"); const certificates = pem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g); if (!certificates?.length) process.exit(1); for (const certificate of certificates) if (!new X509Certificate(certificate).ca) process.exit(1);' /tmp/nemoclaw-corporate-ca.pem \
|| { echo "[nemoclaw] NEMOCLAW_CORPORATE_CA_B64 did not decode to a bundle of valid X.509 certificates with basicConstraints CA:TRUE (#6210)" >&2; exit 1; }; \
export NODE_EXTRA_CA_CERTS=/tmp/nemoclaw-corporate-ca.pem; \
fi; \
./install-reviewed-runtime.sh \
&& rm -f /tmp/nemoclaw-corporate-ca.pem \
&& rm -f ./install-reviewed-runtime.sh
RUN chown -R root:root /opt/mcp-tool-discovery-runtime \
&& chmod -R a=rX /opt/mcp-tool-discovery-runtime

# hadolint ignore=DL3006
FROM ${BASE_IMAGE}

ARG NEMOCLAW_CORPORATE_CA_B64

# Decode the host corporate-proxy CA (#6210) for runtime trust when onboarding
# includes one in the final DCode image. Published or cached bases may not carry
# the host-specific CA, so decode the argument again when it is present.
# hadolint ignore=DL3059,DL4006
RUN if [ -n "${NEMOCLAW_CORPORATE_CA_B64}" ]; then \
command -v base64 >/dev/null 2>&1 || { echo "[nemoclaw] base64 is required to decode NEMOCLAW_CORPORATE_CA_B64 but is not installed in the build image" >&2; exit 1; }; \
install -d -o root -g root -m 0755 /usr/local/share/nemoclaw \
&& { printf '%s' "${NEMOCLAW_CORPORATE_CA_B64}" | base64 --decode > /tmp/nemoclaw-corporate-ca.decoded 2>/dev/null \
|| { echo "[nemoclaw] NEMOCLAW_CORPORATE_CA_B64 is not valid base64; expected a single-line base64-encoded PEM (#6210)" >&2; exit 1; }; } \
&& awk '/-----BEGIN CERTIFICATE-----/{f=1} f{print} /-----END CERTIFICATE-----/{f=0}' /tmp/nemoclaw-corporate-ca.decoded > /usr/local/share/nemoclaw/corporate-ca.pem \
&& rm -f /tmp/nemoclaw-corporate-ca.decoded \
&& { node -e 'const fs = require("node:fs"); const { X509Certificate } = require("node:crypto"); const pem = fs.readFileSync(process.argv[1], "utf8"); const certificates = pem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g); if (!certificates?.length) process.exit(1); for (const certificate of certificates) if (!new X509Certificate(certificate).ca) process.exit(1);' /usr/local/share/nemoclaw/corporate-ca.pem \
|| { echo "[nemoclaw] NEMOCLAW_CORPORATE_CA_B64 did not decode to a bundle of valid X.509 certificates with basicConstraints CA:TRUE (#6210)" >&2; exit 1; }; } \
&& chown root:root /usr/local/share/nemoclaw/corporate-ca.pem \
&& chmod 0444 /usr/local/share/nemoclaw/corporate-ca.pem \
&& echo "[nemoclaw] baked host corporate-proxy CA into DCode image trust (#6210)"; \
fi

COPY --from=mcp-tool-discovery-runtime /opt/mcp-tool-discovery-runtime/dist/ /usr/local/lib/nemoclaw/mcp-tool-discovery-runtime/
RUN discovery_contract="$(node /usr/local/lib/nemoclaw/mcp-tool-discovery-runtime/mcp-tool-discovery.mjs)" \
&& node -e 'const result = JSON.parse(process.argv[1]); if (result.protocol !== 1 || result.ok !== false || result.detail !== "tool discovery received invalid runtime arguments") process.exit(1);' "$discovery_contract" \
Expand Down
30 changes: 30 additions & 0 deletions agents/langchain-deepagents-code/Dockerfile.base
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ ARG PERL_VERSION=5.44.0
ARG PERL_SHA256=505cf43912e9480495c344c70260452e32aa2a73c546a026b3f100053b23ce91
ARG PERL_PACKAGE_REVISION=1nemoclaw1

ARG NEMOCLAW_CORPORATE_CA_B64=

FROM node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba AS native-security-builder

ARG NEMOCLAW_CORPORATE_CA_B64

# hadolint ignore=DL4006
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential=12.12 \
ca-certificates=20250419 \
Expand All @@ -22,6 +27,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
openssh-server=1:10.0p1-7+deb13u4 \
xz-utils=5.8.1-1+deb13u1 \
zlib1g-dev=1:1.3.dfsg+really1.3.1-1+b1 \
&& if [ -n "${NEMOCLAW_CORPORATE_CA_B64:-}" ]; then \
install -d -o root -g root -m 0755 /usr/local/share/nemoclaw /usr/local/share/ca-certificates; \
{ printf '%s' "${NEMOCLAW_CORPORATE_CA_B64}" | base64 --decode > /usr/local/share/nemoclaw/corporate-ca.pem 2>/dev/null \
|| { echo "[nemoclaw] NEMOCLAW_CORPORATE_CA_B64 is not valid base64; expected a single-line base64-encoded PEM (#6210)" >&2; exit 1; }; }; \
node -e 'const fs = require("node:fs"); const { X509Certificate } = require("node:crypto"); const pemPath = process.argv[1]; const anchorDir = process.argv[2]; const pem = fs.readFileSync(pemPath, "utf8"); const blocks = pem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g); if (!blocks?.length) process.exit(1); fs.writeFileSync(pemPath, blocks.map((block) => block.trim()).join("\n") + "\n"); blocks.forEach((block, index) => { if (!new X509Certificate(block).ca) process.exit(1); const name = anchorDir + "/nemoclaw-corporate-ca-" + String(index + 1).padStart(2, "0") + ".crt"; fs.writeFileSync(name, block.trim() + "\n"); });' /usr/local/share/nemoclaw/corporate-ca.pem /usr/local/share/ca-certificates \
|| { echo "[nemoclaw] NEMOCLAW_CORPORATE_CA_B64 did not decode to a bundle of valid X.509 certificates with basicConstraints CA:TRUE (#6210)" >&2; exit 1; }; \
chown root:root /usr/local/share/nemoclaw/corporate-ca.pem /usr/local/share/ca-certificates/nemoclaw-corporate-ca-*.crt; \
chmod 0444 /usr/local/share/nemoclaw/corporate-ca.pem /usr/local/share/ca-certificates/nemoclaw-corporate-ca-*.crt; \
update-ca-certificates; \
echo "[nemoclaw] trusted host corporate-proxy CA for native security builders (#6210)"; \
fi \
&& rm -rf /var/lib/apt/lists/*

COPY scripts/security/build-native-security-packages.sh /scripts/security/build-native-security-packages.sh
Expand All @@ -47,6 +63,8 @@ RUN bash /scripts/security/build-perl-security-packages.sh \

FROM node:22-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba

ARG NEMOCLAW_CORPORATE_CA_B64

COPY --from=perl-builder /out /tmp/nemoclaw-native-security

COPY scripts/lib/reviewed-npm-archive.mts /scripts/lib/reviewed-npm-archive.mts
Expand Down Expand Up @@ -76,6 +94,18 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
e2fsprogs=1.47.2-3+b11 \
openssh-sftp-server=1:10.0p1-7+deb13u4 \
ripgrep=14.1.1-1+b4 \
&& if [ -n "${NEMOCLAW_CORPORATE_CA_B64:-}" ]; then \
command -v base64 >/dev/null 2>&1 || { echo "[nemoclaw] base64 is required to decode NEMOCLAW_CORPORATE_CA_B64 but is not installed in the base build image" >&2; exit 1; }; \
install -d -o root -g root -m 0755 /usr/local/share/nemoclaw /usr/local/share/ca-certificates; \
{ printf '%s' "${NEMOCLAW_CORPORATE_CA_B64}" | base64 --decode > /usr/local/share/nemoclaw/corporate-ca.pem 2>/dev/null \
|| { echo "[nemoclaw] NEMOCLAW_CORPORATE_CA_B64 is not valid base64; expected a single-line base64-encoded PEM (#6210)" >&2; exit 1; }; }; \
node -e 'const fs = require("node:fs"); const { X509Certificate } = require("node:crypto"); const pemPath = process.argv[1]; const anchorDir = process.argv[2]; const pem = fs.readFileSync(pemPath, "utf8"); const blocks = pem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g); if (!blocks?.length) process.exit(1); fs.writeFileSync(pemPath, blocks.map((block) => block.trim()).join("\n") + "\n"); blocks.forEach((block, index) => { if (!new X509Certificate(block).ca) process.exit(1); const name = anchorDir + "/nemoclaw-corporate-ca-" + String(index + 1).padStart(2, "0") + ".crt"; fs.writeFileSync(name, block.trim() + "\n"); });' /usr/local/share/nemoclaw/corporate-ca.pem /usr/local/share/ca-certificates \
|| { echo "[nemoclaw] NEMOCLAW_CORPORATE_CA_B64 did not decode to a bundle of valid X.509 certificates with basicConstraints CA:TRUE (#6210)" >&2; exit 1; }; \
chown root:root /usr/local/share/nemoclaw/corporate-ca.pem /usr/local/share/ca-certificates/nemoclaw-corporate-ca-*.crt; \
chmod 0444 /usr/local/share/nemoclaw/corporate-ca.pem /usr/local/share/ca-certificates/nemoclaw-corporate-ca-*.crt; \
update-ca-certificates; \
echo "[nemoclaw] baked host corporate-proxy CA into base image trust (#6210)"; \
fi \
&& arch="$(dpkg --print-architecture)" \
&& case "$arch" in \
amd64) \
Expand Down
15 changes: 15 additions & 0 deletions ci/source-shape-test-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@
"test": "decodes the CA and exports NODE_EXTRA_CA_CERTS before the reinstall audit-signatures step",
"category": "security"
},
{
"file": "test/corporate-ca-build-tls-anchor.test.ts",
"test": "accepts the corporate CA build arg in the DCode base image before HTTPS fetches",
"category": "security"
},
{
"file": "test/corporate-ca-build-tls-anchor.test.ts",
"test": "decodes the corporate CA again in the DCode final image",
"category": "security"
},
{
"file": "test/corporate-ca-build-tls-anchor.test.ts",
"test": "trusts the corporate CA before the DCode discovery runtime npm install",
"category": "security"
},
{
"file": "test/dcode-base-image-workflow.test.ts",
"test": "accepts every discovered publisher and rejects supply-chain mutations",
Expand Down
3 changes: 3 additions & 0 deletions docs/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,9 @@ navigation:
- page: "Security Best Practices"
path: _build/agent-variants/security/best-practices.deepagents.generated.mdx
slug: best-practices
- page: "Configure Corporate CA Trust"
path: _build/agent-variants/security/configure-corporate-ca-trust.deepagents.generated.mdx
slug: configure-corporate-ca-trust
- page: "Credential Storage"
path: _build/agent-variants/security/credential-storage.deepagents.generated.mdx
slug: credential-storage
Expand Down
13 changes: 12 additions & 1 deletion docs/security/configure-corporate-ca-trust.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description-agent: "Configures corporate proxy CA trust for runtime TLS and supp
keywords: ["nemoclaw corporate ca", "corporate proxy tls", "certificate verification"]
content:
type: "how_to"
agent-variants: ["openclaw", "hermes"]
agent-variants: ["openclaw", "hermes", "deepagents"]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
---

Configure a corporate Certificate Authority (CA) before onboarding when an enterprise proxy re-signs external TLS with a root that OpenShell does not provide.
Expand Down Expand Up @@ -43,6 +43,13 @@ The Hermes Dockerfile decodes the bundle after its managed build-time dependency

</AgentOnly>

<AgentOnly variant="deepagents">

Deep Agents Code applies the corporate CA to its local base-image build before HTTPS dependency fetches and decodes it again in the final sandbox image.
This supports cold builds on hosts where the base image is not cached.

</AgentOnly>

At runtime, NemoClaw appends the corporate CA to the OpenShell trust bundle instead of replacing it.
It points `SSL_CERT_FILE`, `CURL_CA_BUNDLE`, `REQUESTS_CA_BUNDLE`, `GIT_SSL_CAINFO`, and `NODE_EXTRA_CA_CERTS` at the merged bundle so curl, Python, Git, and Node.js trust both roots.

Expand Down Expand Up @@ -91,6 +98,10 @@ Set `NEMOCLAW_CORPORATE_CA_IMPORT=0` to disable corporate CA import entirely.

## Related Topics

<AgentOnly variant="openclaw,hermes">

- [Troubleshooting](../reference/troubleshooting#external-channel-tls-fails-behind-a-corporate-mitm-proxy-netfail) for `NET:FAIL` symptoms and certificate diagnostics.

</AgentOnly>
- [Security Best Practices](best-practices) for the broader sandbox trust model.
- [Credential Storage](credential-storage) for the OpenShell provider credential boundary.
8 changes: 7 additions & 1 deletion src/lib/adapters/docker/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
} from "./run";

export type DockerBuildOptions = DockerRunOptions & {
buildArgs?: Record<string, string>;

labels?: Record<string, string>;
quiet?: boolean;
};
Expand All @@ -21,7 +23,7 @@ export function dockerBuild(
contextDir: string = ROOT,
opts: DockerBuildOptions = {},
): DockerRunResult {
const { labels, quiet, ...rest } = opts;
const { buildArgs, labels, quiet, ...rest } = opts;
// Dockerfile.base relies on `RUN --mount=type=bind`, which is BuildKit-only.
// Hosts whose Docker daemon defaults to the legacy builder (e.g. fresh
// Debian/Ubuntu Docker 29 without /etc/docker/daemon.json) abort the
Expand All @@ -33,6 +35,10 @@ export function dockerBuild(
const args = [
"build",
...(quiet ? ["--quiet"] : []),

...Object.entries(buildArgs ?? {})
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
.flatMap(([key, value]) => ["--build-arg", `${key}=${value}`]),
...Object.entries(labels ?? {})
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
.flatMap(([key, value]) => ["--label", `${key}=${value}`]),
Expand Down
24 changes: 24 additions & 0 deletions src/lib/adapters/docker/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,30 @@ describe("docker helpers", () => {
);
});

it("adds sorted build args to dockerBuild argv and drops them from options", () => {
dockerBuild("Dockerfile.base", "sandbox-base:latest", "/repo/root", {
buildArgs: { Z_ARG: "last", A_ARG: "first" },
ignoreError: true,
});

expect(runMock).toHaveBeenCalledWith(
[
"docker",
"build",
"--build-arg",
"A_ARG=first",
"--build-arg",
"Z_ARG=last",
"-f",
"Dockerfile.base",
"-t",
"sandbox-base:latest",
"/repo/root",
],
{ ignoreError: true, env: { DOCKER_BUILDKIT: "1" } },
);
});

it("adds sorted image labels to dockerBuild argv and drops them from options", () => {
dockerBuild("Dockerfile.base", "sandbox-base:latest", "/repo/root", {
labels: { "com.example.z": "last", "com.example.a": "first" },
Expand Down
64 changes: 64 additions & 0 deletions src/lib/agent/base-image.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import fs from "node:fs";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { makeAgent, withMockedDocker } from "../../../test/helpers/base-image-test-harness";

import { tmpDir, writeCa } from "../onboard/__test-helpers__/corporate-ca-fixtures";
import { testTimeout } from "../../../test/helpers/timeouts";
import {
createSandboxBaseImageBuildProvenanceKey,
Expand Down Expand Up @@ -50,6 +52,7 @@ function makeDifferingImageInspection(

describe("agent base image provisioning", () => {
beforeEach(() => {
vi.stubEnv("NEMOCLAW_CORPORATE_CA_ANCHOR_DIRS", "");
vi.restoreAllMocks();
});

Expand Down Expand Up @@ -369,6 +372,67 @@ describe("agent base image provisioning", () => {
});
});

it("passes the resolved corporate CA into local agent base image builds (#8119)", () => {
vi.stubEnv("NEMOCLAW_CORPORATE_CA_BUNDLE", writeCa(tmpDir()));
withMockedDocker(({ ensureAgentBaseImage, dockerBuildMock, resolveSandboxBaseImageMock }) => {
resolveSandboxBaseImageMock.mockReturnValue({
ref: "nemoclaw-dcode-sandbox-base-local:compatible",
digest: null,
source: "local",
glibcVersion: "2.41",
});

ensureAgentBaseImage(
makeAgent({
name: "langchain-deepagents-code",
displayName: "LangChain Deep Agents Code",
expectedVersion: "0.1.34",
dockerfileBasePath: "/test/root/agents/langchain-deepagents-code/Dockerfile.base",
dockerfilePath: "/test/root/agents/langchain-deepagents-code/Dockerfile",
}),
{ forceBaseImageRebuild: true },
);

const options = dockerBuildMock.mock.calls[0]?.[3] as {
buildArgs?: Record<string, string>;
};
const encoded = options.buildArgs?.NEMOCLAW_CORPORATE_CA_B64;
expect(encoded).toBeTypeOf("string");
expect(Buffer.from(encoded ?? "", "base64").toString("utf8")).toContain("BEGIN CERTIFICATE");
});
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

it("omits corporate CA build inputs when corporate CA import is disabled (#8119)", () => {
vi.stubEnv("NEMOCLAW_CORPORATE_CA_BUNDLE", writeCa(tmpDir()));
vi.stubEnv("NEMOCLAW_CORPORATE_CA_IMPORT", "0");
withMockedDocker(({ ensureAgentBaseImage, dockerBuildMock, resolveSandboxBaseImageMock }) => {
resolveSandboxBaseImageMock.mockReturnValue({
ref: "nemoclaw-dcode-sandbox-base-local:compatible",
digest: null,
source: "local",
glibcVersion: "2.41",
});

ensureAgentBaseImage(
makeAgent({
name: "langchain-deepagents-code",
displayName: "LangChain Deep Agents Code",
expectedVersion: "0.1.34",
dockerfileBasePath: "/test/root/agents/langchain-deepagents-code/Dockerfile.base",
dockerfilePath: "/test/root/agents/langchain-deepagents-code/Dockerfile",
}),
{ forceBaseImageRebuild: true },
);

expect(resolveSandboxBaseImageMock).toHaveBeenCalledWith(
expect.objectContaining({ buildArgs: undefined }),
);
expect(dockerBuildMock.mock.calls[0]?.[3]).toEqual(
expect.objectContaining({ buildArgs: undefined }),
);
});
});

it("fails closed when the Deep Agents Code manifest omits its base-image version", () => {
withMockedDocker(({ ensureAgentBaseImage, resolveSandboxBaseImageMock }) => {
expect(() =>
Expand Down
16 changes: 16 additions & 0 deletions src/lib/agent/base-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
dockerTag,
} from "../adapters/docker";
import { createCustomBuildContextFilter } from "../onboard/custom-build-context";

import { encodeCorporateCaArg, resolveCorporateCa } from "../onboard/corporate-ca";
import { ROOT } from "../runner";
import { SANDBOX_BUILD_CONTEXT_PREFIX } from "../sandbox/build-context";
import {
Expand Down Expand Up @@ -42,6 +44,15 @@ import {
import { createDeepAgentsCodeBaseImageResolutionOptions } from "./deep-agents-code-base-image";
import type { AgentDefinition } from "./defs";

function corporateCaBuildArgs(
env: NodeJS.ProcessEnv = process.env,
): Record<string, string> | undefined {
const corporateCa = resolveCorporateCa(env);
return corporateCa
? { NEMOCLAW_CORPORATE_CA_B64: encodeCorporateCaArg(corporateCa.pem) }
: undefined;
}

const HERMES_MCP_RUNTIME_PROBE_OK = "nemoclaw-hermes-mcp-runtime-ok";
// Matches the official Hermes base repository for both Dockerfile manifest-list
// pins and Docker-normalized platform manifest digests.
Expand Down Expand Up @@ -263,6 +274,7 @@ function createAgentBaseImageResolutionOptions(
return {
imageName,
dockerfilePath,
buildArgs: agent.name === "langchain-deepagents-code" ? corporateCaBuildArgs() : undefined,
localTag: buildLocalBaseTag(`nemoclaw-${agent.name}-sandbox-base-local`, ROOT),
envVar: getAgentSandboxBaseImageEnvVar(agent.name),
label: `${agent.displayName} sandbox base image`,
Expand Down Expand Up @@ -501,6 +513,8 @@ export function ensureAgentBaseImage(
const buildProvenance = localBaseImageBuildProvenance(resolutionOptions);
console.log(` Rebuilding ${agent.displayName} base image...`);
const buildResult = dockerBuild(baseDockerfile, forceBuildTag, ROOT, {
buildArgs: resolutionOptions.buildArgs,

ignoreError: true,
labels: buildProvenance.labels,
stdio: ["ignore", "inherit", "inherit"],
Expand Down Expand Up @@ -610,6 +624,8 @@ export function ensureAgentBaseImage(
console.log(` Building ${agent.displayName} base image (first time only)...`);
const buildProvenance = localBaseImageBuildProvenance(resolutionOptions);
const buildResult = dockerBuild(baseDockerfile, baseImageTag, ROOT, {
buildArgs: resolutionOptions.buildArgs,

ignoreError: true,
labels: buildProvenance.labels,
stdio: ["ignore", "inherit", "inherit"],
Expand Down
Loading
Loading