diff --git a/test/e2e/docs/parity-map.yaml b/test/e2e/docs/parity-map.yaml index 639dc3c539..b65e96c10d 100644 --- a/test/e2e/docs/parity-map.yaml +++ b/test/e2e/docs/parity-map.yaml @@ -1169,19 +1169,34 @@ scripts: runner_requirement: sandbox runner with NemoClaw/OpenShell CLIs - legacy: nemoclaw credentials list failed status: mapped - id: legacy.credential.migration.nemoclaw.credentials.list.failed + id: post-onboard.credentials.gateway-list-redacts-values + layer: post-onboard + gap_domain: credentials + owner: e2e-maintainers - legacy: credentials list surfaces gateway-registered providers status: mapped - id: legacy.credential.migration.credentials.list.surfaces.gateway.registered.providers + id: post-onboard.credentials.gateway-list-redacts-values + layer: post-onboard + gap_domain: credentials + owner: e2e-maintainers - legacy: credentials list did not produce the expected gateway header status: mapped - id: legacy.credential.migration.credentials.list.did.not.produce.the.expected.gateway.header + id: post-onboard.credentials.gateway-list-redacts-values + layer: post-onboard + gap_domain: credentials + owner: e2e-maintainers - legacy: credentials.json reappeared on disk after credentials list status: mapped - id: legacy.credential.migration.credentials.json.reappeared.on.disk.after.credentials.list + id: post-onboard.credentials.no-plaintext-host-store + layer: post-onboard + gap_domain: credentials + owner: e2e-maintainers - legacy: No plaintext credentials.json on disk after credentials list status: mapped - id: legacy.credential.migration.no.plaintext.credentials.json.on.disk.after.credentials.list + id: post-onboard.credentials.no-plaintext-host-store + layer: post-onboard + gap_domain: credentials + owner: e2e-maintainers - legacy: node invocation of removeLegacyCredentialsFile failed status: mapped id: legacy.credential.migration.node.invocation.of.removelegacycredentialsfile.failed diff --git a/test/e2e/scenario-framework-tests/e2e-lib-helpers.test.ts b/test/e2e/scenario-framework-tests/e2e-lib-helpers.test.ts index db450acf51..69af4a5eb0 100644 --- a/test/e2e/scenario-framework-tests/e2e-lib-helpers.test.ts +++ b/test/e2e/scenario-framework-tests/e2e-lib-helpers.test.ts @@ -32,6 +32,202 @@ function runBash(script: string, env: Record = {}): SpawnSyncRet // ────────────────────────────────────────────────────────────────────────── describe("E2E shell helpers", () => { + it("security_policy_credentials_helper_should_load_with_context_library", () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "spc-context-")); + try { + fs.writeFileSync(path.join(tmp, "context.env"), "E2E_SCENARIO=test\nE2E_PROVIDER=nvidia\nE2E_CREDENTIALS_EXPECTED=present\n"); + const r = runBash( + ` + set -euo pipefail + . "${VALIDATION_SUITES}/lib/security_policy_credentials.sh" + spc_require_context E2E_SCENARIO E2E_PROVIDER + echo "provider=$(spc_context_get E2E_PROVIDER)" + `, + { E2E_CONTEXT_DIR: tmp, E2E_DRY_RUN: "1" }, + ); + expect(r.status, r.stderr).toBe(0); + expect(r.stdout).toContain("provider=nvidia"); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }); + + it("security_policy_credentials_helper_should_fail_when_required_context_missing", () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "spc-context-missing-")); + try { + fs.writeFileSync(path.join(tmp, "context.env"), "E2E_SCENARIO=test\n"); + const r = runBash( + ` + set -euo pipefail + . "${VALIDATION_SUITES}/lib/security_policy_credentials.sh" + spc_require_context E2E_PROVIDER + `, + { E2E_CONTEXT_DIR: tmp, E2E_DRY_RUN: "1" }, + ); + expect(r.status).not.toBe(0); + expect(r.stderr).toContain("E2E_PROVIDER"); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }); + + it("security_policy_credentials_helper_should_not_log_secret_values", () => { + const r = runBash(` + set -euo pipefail + . "${VALIDATION_SUITES}/lib/security_policy_credentials.sh" + spc_log_provider_metadata "nvidia" "primary" + printf 'token=nvapi-secret-value-1234567890 sk-abcdefghijklmnop\n' | spc_redact_secret_text + `); + expect(r.status, r.stderr).toBe(0); + expect(r.stdout).toContain("provider=nvidia name=primary"); + expect(r.stdout).not.toMatch(/nvapi-secret-value|sk-abcdefghijklmnop/); + expect(r.stdout).toMatch(/\[REDACTED\]/); + }); + + it("security_policy_credentials_helper_should_reject_empty_gateway_credentials", () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "spc-credentials-empty-")); + const fakeBin = path.join(tmp, "bin"); + fs.mkdirSync(fakeBin); + fs.writeFileSync( + path.join(fakeBin, "nemoclaw"), + `#!/usr/bin/env bash +if [ "$1 $2" = "credentials list" ]; then + echo " No provider credentials registered." + exit 0 +fi +exit 2 +`, + { mode: 0o755 }, + ); + try { + fs.writeFileSync(path.join(tmp, "context.env"), "E2E_SCENARIO=test\nE2E_PROVIDER=nvidia\nE2E_CREDENTIALS_EXPECTED=present\n"); + const r = runBash( + ` + set -euo pipefail + . "${VALIDATION_SUITES}/lib/security_policy_credentials.sh" + spc_assert_credentials_expected + `, + { E2E_CONTEXT_DIR: tmp, PATH: `${fakeBin}:${process.env.PATH ?? ""}` }, + ); + expect(r.status).not.toBe(0); + expect(r.stderr).toMatch(/no gateway credentials/i); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }); + + it("security_policy_credentials_helper_should_verify_policy_and_shields_state", () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "spc-policy-shields-")); + const fakeBin = path.join(tmp, "bin"); + fs.mkdirSync(fakeBin); + fs.writeFileSync( + path.join(fakeBin, "nemoclaw"), + `#!/usr/bin/env bash +if [ "$1 $2" = "sb policy-list" ]; then + echo " Policy presets for sandbox 'sb':" + echo " ● telegram — Telegram bridge egress" + echo " ○ slack — Slack bridge egress" + exit 0 +fi +if [ "$1 $2 $3" = "sb shields status" ]; then + echo " Shields: UP (lockdown active)" + exit 0 +fi +exit 2 +`, + { mode: 0o755 }, + ); + fs.writeFileSync( + path.join(fakeBin, "openshell"), + `#!/usr/bin/env bash +if [ "$1 $2 $3" = "sandbox exec --name" ]; then + echo "440 root:root" + exit 0 +fi +exit 2 +`, + { mode: 0o755 }, + ); + try { + fs.writeFileSync( + path.join(tmp, "context.env"), + "E2E_SCENARIO=test\nE2E_PROVIDER=nvidia\nE2E_SANDBOX_NAME=sb\nE2E_AGENT=openclaw\nE2E_SHIELDS_EXPECTED_STATE=up\n", + ); + const r = runBash( + ` + set -euo pipefail + . "${VALIDATION_SUITES}/lib/security_policy_credentials.sh" + spc_assert_policy_preset_present telegram + spc_assert_shields_config_consistent + `, + { E2E_CONTEXT_DIR: tmp, PATH: `${fakeBin}:${process.env.PATH ?? ""}` }, + ); + expect(r.status, r.stderr).toBe(0); + expect(r.stdout).toContain("telegram"); + expect(r.stdout).toContain("shields config state is consistent: up"); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }); + + it("security_policy_credentials_helper_should_fail_on_missing_policy_preset", () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "spc-policy-missing-")); + const fakeBin = path.join(tmp, "bin"); + fs.mkdirSync(fakeBin); + fs.writeFileSync( + path.join(fakeBin, "nemoclaw"), + `#!/usr/bin/env bash +echo " ○ telegram — Telegram bridge egress" +exit 0 +`, + { mode: 0o755 }, + ); + try { + fs.writeFileSync(path.join(tmp, "context.env"), "E2E_SCENARIO=test\nE2E_PROVIDER=nvidia\nE2E_SANDBOX_NAME=sb\n"); + const r = runBash( + ` + set -euo pipefail + . "${VALIDATION_SUITES}/lib/security_policy_credentials.sh" + spc_assert_policy_preset_present telegram + `, + { E2E_CONTEXT_DIR: tmp, PATH: `${fakeBin}:${process.env.PATH ?? ""}` }, + ); + expect(r.status).not.toBe(0); + expect(r.stderr).toMatch(/expected policy preset 'telegram'/); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }); + + it("security_policy_credentials_helper_should_verify_openshell_rewrite_markers", () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "spc-openshell-")); + const fakeBin = path.join(tmp, "bin"); + fs.mkdirSync(fakeBin); + fs.writeFileSync( + path.join(fakeBin, "openshell"), + `#!/usr/bin/env bash +# request-body-credential-rewrite websocket-credential-rewrite +exit 0 +`, + { mode: 0o755 }, + ); + try { + fs.writeFileSync(path.join(tmp, "context.env"), "E2E_SCENARIO=test\n"); + const r = runBash( + ` + set -euo pipefail + . "${VALIDATION_SUITES}/lib/security_policy_credentials.sh" + spc_assert_openshell_credential_rewrite_supported + `, + { E2E_CONTEXT_DIR: tmp, PATH: `${fakeBin}:${process.env.PATH ?? ""}` }, + ); + expect(r.status, r.stderr).toBe(0); + expect(r.stdout).toContain("OpenShell credential rewrite capability markers present"); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }); + it("env_helper_should_set_standard_noninteractive_env", () => { const r = runBash(` set -euo pipefail diff --git a/test/e2e/scenario-framework-tests/e2e-suite-runner.test.ts b/test/e2e/scenario-framework-tests/e2e-suite-runner.test.ts index 85aff88f7e..b797cca14c 100644 --- a/test/e2e/scenario-framework-tests/e2e-suite-runner.test.ts +++ b/test/e2e/scenario-framework-tests/e2e-suite-runner.test.ts @@ -104,6 +104,20 @@ describe("Issue #3810 messaging suite wiring", () => { }); describe("run-suites.sh", () => { + it("security_credentials_suite_should_emit_stable_assertion_ids", () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "e2e-security-credentials-")); + try { + seedContext(tmp, { ...fullContext(), E2E_CREDENTIALS_EXPECTED: "present" }); + const r = runSuites(["security-credentials"], { E2E_CONTEXT_DIR: tmp, E2E_DRY_RUN: "1", HOME: tmp }); + expect(r.status, `stderr:${r.stderr}\nstdout:${r.stdout}`).toBe(0); + expect(r.stdout).toContain("post-onboard.credentials.gateway-list-redacts-values"); + expect(r.stdout).toContain("post-onboard.credentials.no-plaintext-host-store"); + expect(r.stdout).not.toMatch(/no-credentials-leaked|assert\//); + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + }); + it("run_suites_should_run_steps_in_declared_order", () => { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "e2e-suite-")); try { diff --git a/test/e2e/validation_suites/lib/security_policy_credentials.sh b/test/e2e/validation_suites/lib/security_policy_credentials.sh new file mode 100755 index 0000000000..701c0f9efb --- /dev/null +++ b/test/e2e/validation_suites/lib/security_policy_credentials.sh @@ -0,0 +1,252 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Security policy and credential validation primitives. + +if [[ -n "${NEMOCLAW_SECURITY_POLICY_CREDENTIALS_LIB_LOADED:-}" ]]; then + # shellcheck disable=SC2317 # This file may be sourced repeatedly or executed in tests. + return 0 2>/dev/null || exit 0 +fi +NEMOCLAW_SECURITY_POLICY_CREDENTIALS_LIB_LOADED=1 + +_spc_lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +_spc_e2e_root="$(cd "${_spc_lib_dir}/../.." && pwd)" +# shellcheck source=../../runtime/lib/env.sh +. "${_spc_e2e_root}/runtime/lib/env.sh" +# shellcheck source=../../runtime/lib/context.sh +. "${_spc_e2e_root}/runtime/lib/context.sh" + +spc_assertion_id() { + printf '%s\n' "$1" +} + +spc_require_context() { + e2e_context_require "$@" +} + +spc_context_get() { + e2e_context_get "$1" +} + +spc_redact_secret_text() { + sed -E 's/(sk-[A-Za-z0-9_-]{8,}|nvapi-[A-Za-z0-9_-]{8,}|[A-Za-z0-9._%+-]+:[A-Za-z0-9_\/-]{12,}|(api[_-]?key|token|secret|password)[=:][^[:space:]]+)/[REDACTED]/Ig' +} + +spc_log_provider_metadata() { + local provider="$1" + local name="${2:-default}" + printf 'credential provider=%s name=%s value=[REDACTED]\n' "${provider}" "${name}" +} + +spc_assert_credentials_expected() { + spc_assertion_id "post-onboard.credentials.gateway-list-redacts-values" + spc_require_context E2E_SCENARIO E2E_PROVIDER + local expected + expected="$(spc_context_get E2E_CREDENTIALS_EXPECTED)" + if [[ -z "${expected}" ]]; then + expected="$(spc_context_get CREDENTIALS_EXPECTED)" + fi + if [[ -z "${expected}" ]]; then + expected="present" + fi + if [[ "${expected}" != "present" ]]; then + echo "credentials expected state is '${expected}', not present" >&2 + return 1 + fi + spc_log_provider_metadata "$(spc_context_get E2E_PROVIDER)" "gateway" + if e2e_env_is_dry_run; then + echo "[dry-run] would list gateway credentials without raw values" + return 0 + fi + local listed + if ! listed="$(nemoclaw credentials list 2>&1 | spc_redact_secret_text)"; then + printf '%s\n' "${listed}" + echo "nemoclaw credentials list failed while credentials.expected=present" >&2 + return 1 + fi + printf '%s\n' "${listed}" + if printf '%s\n' "${listed}" | grep -qi "No provider credentials registered"; then + echo "no gateway credentials were listed while credentials.expected=present" >&2 + return 1 + fi + if ! printf '%s\n' "${listed}" | grep -q "Providers registered with the OpenShell gateway"; then + echo "credentials list did not include the expected OpenShell gateway provider header" >&2 + return 1 + fi +} + +spc_assert_no_plaintext_host_store() { + spc_assertion_id "post-onboard.credentials.no-plaintext-host-store" + spc_require_context E2E_SCENARIO + local home_dir="${HOME:-}" + if [[ -n "${home_dir}" && -f "${home_dir}/.nemoclaw/credentials.json" ]]; then + echo "plaintext credential store found at ~/.nemoclaw/credentials.json" >&2 + return 1 + fi + echo "plaintext host credential store absent" +} + +spc_assert_policy_preset_present() { + local preset="$1" + spc_assertion_id "post-onboard.security-policy.${preset}-preset-applied" + spc_require_context E2E_SCENARIO E2E_SANDBOX_NAME + echo "policy preset expected: ${preset}" + if e2e_env_is_dry_run; then + echo "[dry-run] would verify policy preset ${preset}" + return 0 + fi + local sandbox_name active + sandbox_name="$(spc_context_get E2E_SANDBOX_NAME)" + if ! active="$(nemoclaw "${sandbox_name}" policy-list 2>&1)"; then + printf '%s\n' "${active}" + echo "failed to query policy presets for sandbox '${sandbox_name}'" >&2 + return 1 + fi + printf '%s\n' "${active}" + if ! printf '%s\n' "${active}" | awk -v preset="${preset}" '$1 == "●" && $2 == preset { found = 1 } END { exit found ? 0 : 1 }'; then + echo "expected policy preset '${preset}' to be applied for sandbox '${sandbox_name}'" >&2 + return 1 + fi +} + +spc_assert_openshell_credential_rewrite_supported() { + spc_assertion_id "post-onboard.gateway.openshell-version-supports-credential-rewrite" + spc_require_context E2E_SCENARIO + if e2e_env_is_dry_run; then + echo "[dry-run] would verify OpenShell gateway capability metadata" + return 0 + fi + local openshell_bin binary_strings feature + openshell_bin="$(command -v openshell 2>/dev/null || true)" + if [[ -z "${openshell_bin}" ]]; then + echo "openshell binary was not found on PATH" >&2 + return 1 + fi + if ! command -v strings >/dev/null 2>&1; then + echo "strings is required to verify OpenShell credential rewrite support" >&2 + return 1 + fi + binary_strings="$(strings "${openshell_bin}" 2>/dev/null || true)" + for feature in request-body-credential-rewrite websocket-credential-rewrite; do + if [[ "${binary_strings}" != *"${feature}"* ]]; then + echo "OpenShell binary is missing ${feature} support" >&2 + return 1 + fi + done + echo "OpenShell credential rewrite capability markers present" +} + +spc_agent_config_path() { + case "$(spc_context_get E2E_AGENT)" in + hermes) printf '%s\n' "/sandbox/.hermes/.env" ;; + openclaw | "") printf '%s\n' "/sandbox/.openclaw/openclaw.json" ;; + *) + echo "unsupported E2E_AGENT for shields config check: $(spc_context_get E2E_AGENT)" >&2 + return 1 + ;; + esac +} + +spc_assert_shields_permissions_match_state() { + local sandbox_name="$1" + local observed="$2" + local config_path perms mode owner + config_path="$(spc_agent_config_path)" || return 1 + if ! perms="$(openshell sandbox exec --name "${sandbox_name}" -- stat -c '%a %U:%G' "${config_path}" 2>&1)"; then + printf '%s\n' "${perms}" + echo "failed to inspect shields config permissions at ${config_path}" >&2 + return 1 + fi + printf 'config permissions: %s %s\n' "${config_path}" "${perms}" + mode="$(printf '%s\n' "${perms}" | awk '{print $1}')" + owner="$(printf '%s\n' "${perms}" | awk '{print $2}')" + case "${observed}" in + up) + if [[ ! "${mode}" =~ ^4[0-4][0-4]$ || "${owner}" != "root:root" ]]; then + echo "shields are UP but config is not locked root:root with restrictive permissions: ${perms}" >&2 + return 1 + fi + ;; + down | not-configured) + if [[ "${owner}" != "sandbox:sandbox" ]]; then + echo "shields are ${observed} but config owner is not sandbox:sandbox: ${perms}" >&2 + return 1 + fi + ;; + esac +} + +spc_assert_shields_config_consistent() { + spc_assertion_id "post-onboard.security-shields.config-consistent" + spc_require_context E2E_SCENARIO E2E_SANDBOX_NAME E2E_AGENT + if e2e_env_is_dry_run; then + echo "[dry-run] would verify shields config consistency" + return 0 + fi + local sandbox_name status observed expected + sandbox_name="$(spc_context_get E2E_SANDBOX_NAME)" + if ! status="$(nemoclaw "${sandbox_name}" shields status 2>&1)"; then + printf '%s\n' "${status}" + echo "failed to query shields status for sandbox '${sandbox_name}'" >&2 + return 1 + fi + printf '%s\n' "${status}" + case "${status}" in + *"Shields: UP"*) observed="up" ;; + *"Shields: DOWN"*) observed="down" ;; + *"Shields: NOT CONFIGURED"*) observed="not-configured" ;; + *) + echo "shields status did not report a recognized state" >&2 + return 1 + ;; + esac + expected="$(spc_context_get E2E_SHIELDS_EXPECTED_STATE)" + if [[ -z "${expected}" ]]; then + expected="$(spc_context_get E2E_SHIELDS_EXPECTED)" + fi + expected="${expected//_/-}" + if [[ -n "${expected}" && "${expected}" != "${observed}" ]]; then + echo "expected shields state '${expected}', got '${observed}'" >&2 + return 1 + fi + spc_assert_shields_permissions_match_state "${sandbox_name}" "${observed}" + echo "shields config state is consistent: ${observed}" +} + +spc_assert_telegram_payload_not_shell_executed() { + spc_assertion_id "post-onboard.security-injection.telegram-message-not-shell-executed" + spc_require_context E2E_SCENARIO E2E_SANDBOX_NAME + local fixture_payload="${1:-}" + if [[ -n "${fixture_payload}" ]]; then + printf 'telegram payload fixture loaded (%s bytes)\n' "${#fixture_payload}" + fi + if e2e_env_is_dry_run; then + echo "[dry-run] would submit payload without shell evaluation" + return 0 + fi + local sandbox_name marker payload send_output marker_state + sandbox_name="$(spc_context_get E2E_SANDBOX_NAME)" + marker="/tmp/nemoclaw-telegram-injection-proof-$RANDOM-$$" + # shellcheck disable=SC2016 # Literal command-substitution payload under test. + payload="$(printf '$(touch %s && echo INJECTED)' "${marker}")" + openshell sandbox exec --name "${sandbox_name}" -- sh -c "rm -f '${marker}'" >/dev/null 2>&1 || true + # shellcheck disable=SC2016 # Remote shell reads payload from stdin; local shell must not expand MSG. + if ! send_output="$(openshell sandbox exec --name "${sandbox_name}" -- sh -c 'MSG=$(cat); printf "%s\n" "$MSG"' <<<"${payload}" 2>&1)"; then + printf '%s\n' "${send_output}" + echo "failed to submit telegram injection payload to sandbox '${sandbox_name}'" >&2 + return 1 + fi + printf '%s\n' "${send_output}" + if [[ "${send_output}" != *"${payload}"* ]]; then + echo "telegram injection payload was not preserved literally" >&2 + return 1 + fi + marker_state="$(openshell sandbox exec --name "${sandbox_name}" -- sh -c "test -f '${marker}' && echo EXPLOITED || echo SAFE" 2>&1 || true)" + if [[ "${marker_state}" != *"SAFE"* ]]; then + printf '%s\n' "${marker_state}" + echo "telegram injection payload executed shell side effects" >&2 + return 1 + fi + echo "telegram injection payload treated as data" +} diff --git a/test/e2e/validation_suites/security/credentials/00-credentials-present.sh b/test/e2e/validation_suites/security/credentials/00-credentials-present.sh index bb31943d17..922a7c1636 100755 --- a/test/e2e/validation_suites/security/credentials/00-credentials-present.sh +++ b/test/e2e/validation_suites/security/credentials/00-credentials-present.sh @@ -7,22 +7,8 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -LIB_DIR="$(cd "${SCRIPT_DIR}/../../../runtime/lib" && pwd)" -# shellcheck source=../../../runtime/lib/env.sh -. "${LIB_DIR}/env.sh" -# shellcheck source=../../../runtime/lib/context.sh -. "${LIB_DIR}/context.sh" +# shellcheck source=../../lib/security_policy_credentials.sh +. "${SCRIPT_DIR}/../../lib/security_policy_credentials.sh" echo "credentials:credentials-present" -e2e_context_require E2E_SCENARIO - -if e2e_env_is_dry_run; then - echo "[dry-run] would verify credentials are recorded in the gateway" - exit 0 -fi - -if ! command -v nemoclaw >/dev/null 2>&1; then - echo "credentials:credentials-present: nemoclaw CLI not on PATH" >&2 - exit 1 -fi -nemoclaw credentials list >/dev/null +spc_assert_credentials_expected diff --git a/test/e2e/validation_suites/security/credentials/01-no-plaintext-host-store.sh b/test/e2e/validation_suites/security/credentials/01-no-plaintext-host-store.sh new file mode 100755 index 0000000000..7897e0048f --- /dev/null +++ b/test/e2e/validation_suites/security/credentials/01-no-plaintext-host-store.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# credentials step: no-plaintext-host-store + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=../../lib/security_policy_credentials.sh +. "${SCRIPT_DIR}/../../lib/security_policy_credentials.sh" + +echo "credentials:no-plaintext-host-store" +spc_assert_no_plaintext_host_store diff --git a/test/e2e/validation_suites/security/injection/00-telegram-message-not-shell-executed.sh b/test/e2e/validation_suites/security/injection/00-telegram-message-not-shell-executed.sh new file mode 100755 index 0000000000..5da780affd --- /dev/null +++ b/test/e2e/validation_suites/security/injection/00-telegram-message-not-shell-executed.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +. "${SCRIPT_DIR}/../../lib/security_policy_credentials.sh" +echo "injection:telegram-message-not-shell-executed" +payload="${E2E_TELEGRAM_PAYLOAD_FIXTURE:-$(spc_context_get E2E_TELEGRAM_PAYLOAD_FIXTURE)}" +spc_assert_telegram_payload_not_shell_executed "${payload}" diff --git a/test/e2e/validation_suites/security/policy/00-telegram-preset-applied.sh b/test/e2e/validation_suites/security/policy/00-telegram-preset-applied.sh new file mode 100755 index 0000000000..a64dcdbd52 --- /dev/null +++ b/test/e2e/validation_suites/security/policy/00-telegram-preset-applied.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +. "${SCRIPT_DIR}/../../lib/security_policy_credentials.sh" +echo "policy:telegram-preset-applied" +spc_assert_policy_preset_present telegram diff --git a/test/e2e/validation_suites/security/policy/01-openshell-version-supports-credential-rewrite.sh b/test/e2e/validation_suites/security/policy/01-openshell-version-supports-credential-rewrite.sh new file mode 100755 index 0000000000..1a1f589969 --- /dev/null +++ b/test/e2e/validation_suites/security/policy/01-openshell-version-supports-credential-rewrite.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +. "${SCRIPT_DIR}/../../lib/security_policy_credentials.sh" +echo "policy:openshell-version-supports-credential-rewrite" +spc_assert_openshell_credential_rewrite_supported diff --git a/test/e2e/validation_suites/security/shields/00-config-consistent.sh b/test/e2e/validation_suites/security/shields/00-config-consistent.sh new file mode 100755 index 0000000000..552cae2484 --- /dev/null +++ b/test/e2e/validation_suites/security/shields/00-config-consistent.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +. "${SCRIPT_DIR}/../../lib/security_policy_credentials.sh" +echo "shields:config-consistent" +spc_assert_shields_config_consistent diff --git a/test/e2e/validation_suites/suites.yaml b/test/e2e/validation_suites/suites.yaml index 1f869a5b25..7152feeec4 100644 --- a/test/e2e/validation_suites/suites.yaml +++ b/test/e2e/validation_suites/suites.yaml @@ -33,6 +33,8 @@ suites: steps: &id008 - id: credentials-present script: security/credentials/00-credentials-present.sh + - id: no-plaintext-host-store + script: security/credentials/01-no-plaintext-host-store.sh baseline-onboarding: requires_state: cli.installed: true @@ -104,8 +106,13 @@ suites: requires_state: *id005 steps: *id006 security-credentials: - requires_state: *id007 - steps: *id008 + requires_state: + credentials.expected: present + steps: + - id: credentials-present + script: security/credentials/00-credentials-present.sh + - id: no-plaintext-host-store + script: security/credentials/01-no-plaintext-host-store.sh messaging-telegram: requires_state: *id001 steps: @@ -148,8 +155,11 @@ suites: - id: slack-provider-state script: messaging/slack/00-slack-provider-state.sh security-shields: - requires_state: *id007 - steps: *id008 + requires_state: + sandbox.status: running + steps: + - id: shields-config-consistent + script: security/shields/00-config-consistent.sh inference-routing: requires_state: *id003 steps: *id004 @@ -222,8 +232,16 @@ suites: - id: provider-rotation-isolated script: messaging/token-rotation/00-provider-rotation-isolated.sh security-policy: - requires_state: *id007 - steps: *id008 + requires_state: + sandbox.status: running + steps: + - id: telegram-preset-applied + script: security/policy/00-telegram-preset-applied.sh + - id: openshell-version-supports-credential-rewrite + script: security/policy/01-openshell-version-supports-credential-rewrite.sh security-injection: - requires_state: *id007 - steps: *id008 + requires_state: + sandbox.status: running + steps: + - id: telegram-message-not-shell-executed + script: security/injection/00-telegram-message-not-shell-executed.sh