fix: validate frozen Telegram explicit command denials (#150761)

This commit is contained in:
Dallin Romney 2026-09-17 03:12:43 -07:00 • committed by GitHub
parent 847aef50ad
commit 35abd04c0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 195 additions and 2 deletions

View file

@ -348,6 +348,7 @@ jobs:
qa/scenarios/channels/telegram-progress-tool-visibility.yaml
qa/scenarios/channels/telegram-queue-invalid-mode.yaml
qa/scenarios/channels/telegram-rich-inline-composition.yaml
qa/scenarios/channels/telegram-repeated-command-authorization.yaml
src/agents/embedded-agent-subscribe.ts
- name: Resolve frozen package Telegram scenarios

View file

@ -1,7 +1,7 @@
import { execFileSync } from "node:child_process";
import { appendFileSync, readFileSync } from "node:fs";
import { appendFileSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { fileURLToPath, pathToFileURL } from "node:url";
const PARTIAL_FAILURE_RECOVERY_SCENARIO = "telegram-partial-failure-recovery";
const SETTLED_EMPTY_RESPONSE_SCENARIO = "telegram-empty-response-after-write-recovery";
@ -13,6 +13,37 @@ const POLICY_HOT_RELOAD_SCENARIOS = [
"telegram-policy-hot-reload",
"telegram-group-policy-hot-reload",
];
const REPEATED_COMMAND_SCENARIO =
"qa/scenarios/channels/telegram-repeated-command-authorization.yaml";
const SILENT_DENIAL = " - waitForNoOutbound:\n quietMs: 3000\n";
const EXPLICIT_DENIAL =
" - waitForOutbound:\n" +
" conversation: { id: telegram-command-room, kind: channel }\n" +
" textIncludes: You are not authorized to use this command.\n" +
" timeoutMs: 60000\n";
function resolveFrozenTelegramCommandScenario(sourceRoot: string): string | undefined {
const target = readSource(sourceRoot, REPEATED_COMMAND_SCENARIO);
if (target === undefined) {
return undefined;
}
const hasExplicitDenial = target.split(EXPLICIT_DENIAL).length === 2;
const hasSilentDenial = target.split(SILENT_DENIAL).length === 2;
if (hasExplicitDenial === hasSilentDenial) {
throw new Error("unrecognized frozen Telegram repeated-command denial contract");
}
if (!hasExplicitDenial) {
return undefined;
}
// Target source selects a known contract; only trusted tooling supplies executable YAML.
// Retire this projection when no supported frozen release returns explicit denials.
const trustedRoot = fileURLToPath(new URL("../../../../", import.meta.url));
const trusted = readFileSync(path.join(trustedRoot, REPEATED_COMMAND_SCENARIO), "utf8");
if (trusted.split(SILENT_DENIAL).length !== 2 || trusted.includes(EXPLICIT_DENIAL)) {
throw new Error("trusted Telegram repeated-command denial action changed");
}
return trusted.replace(SILENT_DENIAL, EXPLICIT_DENIAL);
}
function readSource(sourceRoot: string, relativePath: string): string | undefined {
try {
@ -100,6 +131,21 @@ function main(): void {
if (actualSha !== selectedSha) {
throw new Error("frozen Telegram source checkout does not match package source SHA");
}
const commandScenario = resolveFrozenTelegramCommandScenario(sourceRoot);
if (commandScenario !== undefined) {
const overlayDir = mkdtempSync(
path.join(path.dirname(path.resolve(sourceRoot)), "telegram-contract-"),
);
const overlay = path.join(overlayDir, path.basename(REPEATED_COMMAND_SCENARIO));
writeFileSync(
overlay,
`# Frozen package source ${actualSha}: explicit authorization denial.\n${commandScenario}`,
);
appendFileSync(output, `OPENCLAW_NPM_TELEGRAM_COMMAND_SCENARIO=${overlay}\n`);
process.stdout.write(
`Telegram repeated-command authorization: explicit denial contract from ${actualSha}\n`,
);
}
const omittedScenarios = resolveFrozenTelegramScenarioOmissions(sourceRoot);
if (omittedScenarios.length > 0) {
appendFileSync(

View file

@ -429,6 +429,14 @@ EOF
# Mount the trusted current-source QA harness separately from the installed
# package candidate. The candidate remains the absolute CLI/runtime SUT.
command_scenario_mount_args=()
if [ -n "${OPENCLAW_NPM_TELEGRAM_COMMAND_SCENARIO:-}" ]; then
if [ ! -f "$OPENCLAW_NPM_TELEGRAM_COMMAND_SCENARIO" ]; then
echo "Frozen Telegram command scenario is missing" >&2
exit 1
fi
command_scenario_mount_args=(-v "$OPENCLAW_NPM_TELEGRAM_COMMAND_SCENARIO:/app/qa/scenarios/channels/telegram-repeated-command-authorization.yaml:ro")
fi
run_logged_print_heartbeat "npm-telegram-live-suite" 60 docker_e2e_run_with_harness \
"${docker_env[@]}" \
-v "$ROOT_DIR/.artifacts:/app/.artifacts" \
@ -442,6 +450,7 @@ run_logged_print_heartbeat "npm-telegram-live-suite" 60 docker_e2e_run_with_harn
-v "$ROOT_DIR/.agents:/app/.agents:ro" \
-v "$ROOT_DIR/taxonomy.yaml:/app/taxonomy.yaml:ro" \
-v "$ROOT_DIR/qa/scenarios:/app/qa/scenarios:ro" \
${command_scenario_mount_args[@]+"${command_scenario_mount_args[@]}"} \
${prepublish_registry_mount_args[@]+"${prepublish_registry_mount_args[@]}"} \
-v "$npm_prefix_host:/npm-global" \
-i "$IMAGE_NAME" bash -s <<'EOF'

View file

@ -0,0 +1,137 @@
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, expect, it } from "vitest";
import YAML from "yaml";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const repoRoot = fileURLToPath(new URL("../../", import.meta.url));
const scenarioPath = "qa/scenarios/channels/telegram-repeated-command-authorization.yaml";
const trusted = readFileSync(path.join(repoRoot, scenarioPath), "utf8");
const silent = " - waitForNoOutbound:\n quietMs: 3000\n";
const explicit =
" - waitForOutbound:\n" +
" conversation: { id: telegram-command-room, kind: channel }\n" +
" textIncludes: You are not authorized to use this command.\n" +
" timeoutMs: 60000\n";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
function fixture(source: string) {
const root = tempDirs.make("telegram-frozen-command-");
const target = path.join(root, "target");
const file = path.join(target, scenarioPath);
mkdirSync(path.dirname(file), { recursive: true });
writeFileSync(file, source);
execFileSync("git", ["init", "-q", target]);
execFileSync("git", ["-C", target, "add", "."]);
execFileSync("git", [
"-C",
target,
"-c",
"user.name=Fixture",
"-c",
"user.email=fixture@example.test",
"commit",
"-qm",
"target",
]);
const sha = execFileSync("git", ["-C", target, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
const output = path.join(root, "github.env");
return {
root,
sha,
resolve: (selectedSha = sha) => {
execFileSync(
process.execPath,
[
path.join(repoRoot, "scripts/e2e/lib/npm-telegram-live/resolve-target-scenarios.mts"),
target,
],
{
env: { ...process.env, OPENCLAW_SELECTED_SHA: selectedSha, GITHUB_ENV: output },
stdio: "pipe",
},
);
return Object.fromEntries(
readFileSync(output, "utf8")
.trim()
.split("\n")
.map((line) => {
const equal = line.indexOf("=");
return [line.slice(0, equal), line.slice(equal + 1)];
}),
);
},
};
}
function captureSuiteMounts(overlay?: string) {
const script = readFileSync(
path.join(repoRoot, "scripts/e2e/npm-telegram-live-docker.sh"),
"utf8",
);
const mountCall = script.slice(
script.indexOf("command_scenario_mount_args=()"),
script.indexOf(" bash -s <<'EOF'", script.indexOf("command_scenario_mount_args=()")),
);
return execFileSync(
"/bin/bash",
[
"-eu",
"-c",
`
docker_env=(-e FIXTURE=1)
prepublish_registry_mount_args=()
ROOT_DIR=/trusted
OUTPUT_DIR_HOST=/output
OUTPUT_DIR_CONTAINER=/app/output
harness_package_json=/trusted/package.json
npm_prefix_host=/npm
IMAGE_NAME=fixture
run_logged_print_heartbeat() { printf '%s\\n' "$@"; }
${mountCall}
`,
],
{
encoding: "utf8",
env: { ...process.env, OPENCLAW_NPM_TELEGRAM_COMMAND_SCENARIO: overlay ?? "" },
},
)
.trim()
.split("\n");
}
it("mounts a source-qualified trusted denial action without changing restored command proof", () => {
// Frozen YAML selects the contract but must never supply executable expressions.
const f = fixture(
trusted.replace(silent, explicit).replace("title:", "# target-only-untrusted-content\ntitle:"),
);
const env = f.resolve();
const overlay = env.OPENCLAW_NPM_TELEGRAM_COMMAND_SCENARIO;
assert.ok(overlay);
const actual = readFileSync(overlay, "utf8");
expect(actual).toContain(`# Frozen package source ${f.sha}: explicit authorization denial.`);
expect(actual).not.toContain("target-only-untrusted-content");
expect(YAML.parse(actual)).toEqual(YAML.parse(trusted.replace(silent, explicit)));
expect(env.OPENCLAW_NPM_TELEGRAM_OMIT_DEFAULT_SCENARIOS).not.toContain(
"telegram-repeated-command-authorization",
);
const mounts = captureSuiteMounts(overlay);
const baseIndex = mounts.indexOf("/trusted/qa/scenarios:/app/qa/scenarios:ro");
expect(baseIndex).toBeGreaterThan(0);
expect(mounts.indexOf(`${overlay}:/app/${scenarioPath}:ro`)).toBeGreaterThan(baseIndex);
});
it("keeps current silent denial on the normal trusted scenario mount", () => {
const env = fixture(trusted).resolve();
expect(env.OPENCLAW_NPM_TELEGRAM_COMMAND_SCENARIO).toBeUndefined();
expect(captureSuiteMounts().filter((arg) => arg.includes(scenarioPath))).toEqual([]);
});
it("refuses unknown source contracts and mismatched selected source identities", () => {
expect(() => fixture(trusted.replace(silent, "")).resolve()).toThrow();
expect(() => fixture(trusted.replace(silent, explicit)).resolve("0".repeat(40))).toThrow();
expect(() => captureSuiteMounts("/missing/frozen-scenario.yaml")).toThrow();
});