feat: add secure WP7-02 external executor
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 48s

This commit is contained in:
suyx
2026-08-04 15:46:38 +08:00
parent 597f4647ef
commit c2f89453a2
6 changed files with 520 additions and 10 deletions
+254
View File
@@ -0,0 +1,254 @@
import { createHash } from "node:crypto";
import { WP7_02_MODEL_IDS, buildModelContractPlan } from "./wp7-02-external-contract.mjs";
export const WP7_02_CONTROLLED_REAL_LIMIT = 120;
const ratios = ["3:4", "1:1", "4:3", "9:16"];
const openAiSizes = Object.freeze({
"3:4": "1024x1536",
"1:1": "1024x1024",
"4:3": "1536x1024",
"9:16": "1024x1536",
});
const allowedMimeTypes = new Set(["image/jpeg", "image/png", "image/webp"]);
const forbiddenEvidenceKeys = /(?:^|_)(?:absolute_path|authorization|body|credential|credential_value|image|password|path|prompt|raw|raw_provider_payload|raw_prompt|secret|token)(?:_|$)/i;
function sha256(value) {
return createHash("sha256").update(value).digest("hex").toUpperCase();
}
function assertModelConfig(modelConfig) {
if (!modelConfig || typeof modelConfig !== "object" || !WP7_02_MODEL_IDS.includes(modelConfig.model_id)) {
throw new Error("WP7_02_MODEL_CONFIG_INVALID");
}
if (!Number.isSafeInteger(modelConfig.config_version) || modelConfig.config_version <= 0) {
throw new Error("WP7_02_MODEL_CONFIG_VERSION_INVALID");
}
const profile = modelConfig.route_profile;
if (!profile || typeof profile !== "object" || typeof profile.endpoint !== "string"
|| !profile.endpoint.startsWith("https://oneapi.intelligrow.cn/")
|| !["gemini-native-v1beta", "openai-images-v1"].includes(profile.protocol_version)) {
throw new Error("WP7_02_ROUTE_PROFILE_INVALID");
}
return modelConfig;
}
export function buildControlledExecutionPlan(modelConfig) {
const config = assertModelConfig(modelConfig);
const contractPlan = buildModelContractPlan(config.model_id);
const realScenarios = [
...ratios.map((ratio) => ({ input: "pure_text", ratio, source: "real_gateway" })),
{ input: "reference_image", ratio: "1:1", source: "real_gateway" },
];
return {
config_version: config.config_version,
error_scenarios: contractPlan.error_categories.map((name) => ({
expected: contractPlan.error_expectations[name], name, source: "deterministic_local",
})),
execution_modes: [
{ mode: "sync", source: "real_gateway" },
{ mode: "async", source: "deterministic_local" },
{ mode: "poll", source: "deterministic_local" },
],
model_id: config.model_id,
planned_real_calls: realScenarios.length,
quota_impact: "authorized_test_key_up_to_120_requests",
real_scenarios: realScenarios,
response_checks: ["single_image", "mime", "dimensions", "sanitized_usage", "evidence_hash"],
state_scenarios: contractPlan.state_checks.map((name) => ({ name, source: "deterministic_local" })),
};
}
export function buildProviderRequest({ modelConfig, prompt, ratio, reference }) {
const config = assertModelConfig(modelConfig);
if (typeof prompt !== "string" || !prompt.trim() || !ratios.includes(ratio)) throw new Error("WP7_02_REQUEST_FIXTURE_INVALID");
if (reference && (!Buffer.isBuffer(reference.bytes) || reference.bytes.length === 0 || !allowedMimeTypes.has(reference.mime_type))) {
throw new Error("WP7_02_REFERENCE_FIXTURE_INVALID");
}
const headers = { "content-type": "application/json" };
if (config.route_profile.protocol_version === "gemini-native-v1beta") {
const parts = [{ text: prompt }];
if (reference) parts.push({ inlineData: { data: reference.bytes.toString("base64"), mimeType: reference.mime_type } });
return {
body: {
contents: [{ parts, role: "user" }],
generationConfig: { imageConfig: { aspectRatio: ratio }, responseModalities: ["TEXT", "IMAGE"] },
},
headers,
method: "POST",
url: config.route_profile.endpoint,
};
}
const body = {
model: config.model_id,
prompt,
response_format: "b64_json",
size: openAiSizes[ratio],
};
if (reference) body.image = `data:${reference.mime_type};base64,${reference.bytes.toString("base64")}`;
return { body, headers, method: "POST", url: config.route_profile.endpoint };
}
function pngDimensions(bytes) {
const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
if (bytes.length < 24 || !bytes.subarray(0, 8).equals(signature)) return undefined;
return { height: bytes.readUInt32BE(20), width: bytes.readUInt32BE(16) };
}
function jpegDimensions(bytes) {
if (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8) return undefined;
let offset = 2;
while (offset + 9 < bytes.length) {
if (bytes[offset] !== 0xff) { offset += 1; continue; }
const marker = bytes[offset + 1];
if ([0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf].includes(marker)) {
return { height: bytes.readUInt16BE(offset + 5), width: bytes.readUInt16BE(offset + 7) };
}
if (marker === 0xd8 || marker === 0xd9 || (marker >= 0xd0 && marker <= 0xd7)) { offset += 2; continue; }
const length = bytes.readUInt16BE(offset + 2);
if (length < 2) return undefined;
offset += length + 2;
}
return undefined;
}
function webpDimensions(bytes) {
if (bytes.length < 30 || bytes.toString("ascii", 0, 4) !== "RIFF" || bytes.toString("ascii", 8, 12) !== "WEBP") return undefined;
const kind = bytes.toString("ascii", 12, 16);
if (kind === "VP8X") {
return {
height: 1 + bytes.readUIntLE(27, 3),
width: 1 + bytes.readUIntLE(24, 3),
};
}
if (kind === "VP8 " && bytes.length >= 30) return { height: bytes.readUInt16LE(28) & 0x3fff, width: bytes.readUInt16LE(26) & 0x3fff };
if (kind === "VP8L" && bytes.length >= 25) {
const bits = bytes.readUInt32LE(21);
return { height: 1 + ((bits >> 14) & 0x3fff), width: 1 + (bits & 0x3fff) };
}
return undefined;
}
function inspectImage(bytes, declaredMime) {
const png = pngDimensions(bytes);
if (png && declaredMime === "image/png") return { ...png, mime: declaredMime };
const jpeg = jpegDimensions(bytes);
if (jpeg && declaredMime === "image/jpeg") return { ...jpeg, mime: declaredMime };
const webp = webpDimensions(bytes);
if (webp && declaredMime === "image/webp") return { ...webp, mime: declaredMime };
throw new Error("WP7_02_RESPONSE_MEDIA_INVALID");
}
function integerOrZero(value) {
return Number.isSafeInteger(value) && value >= 0 ? value : 0;
}
function geminiUsage(response) {
const usage = response?.usageMetadata;
return {
input_units: integerOrZero(usage?.promptTokenCount),
output_units: integerOrZero(usage?.candidatesTokenCount),
total_units: integerOrZero(usage?.totalTokenCount),
};
}
function openAiUsage(response) {
const usage = response?.usage;
return {
input_units: integerOrZero(usage?.input_tokens ?? usage?.inputTokens),
output_units: integerOrZero(usage?.output_tokens ?? usage?.outputTokens),
total_units: integerOrZero(usage?.total_tokens ?? usage?.totalTokens),
};
}
export function normalizeProviderResponse(modelConfig, response) {
const config = assertModelConfig(modelConfig);
let bytes;
let mime;
let usageSummary;
if (config.route_profile.protocol_version === "gemini-native-v1beta") {
const parts = response?.candidates?.flatMap((candidate) => candidate?.content?.parts ?? []) ?? [];
const images = parts.map((part) => part?.inlineData ?? part?.inline_data).filter((entry) => entry?.data);
if (images.length !== 1) throw new Error("WP7_02_RESPONSE_SINGLE_IMAGE_REQUIRED");
mime = images[0].mimeType ?? images[0].mime_type;
bytes = Buffer.from(images[0].data, "base64");
usageSummary = geminiUsage(response);
} else {
if (!Array.isArray(response?.data) || response.data.length !== 1 || typeof response.data[0]?.b64_json !== "string") {
throw new Error("WP7_02_RESPONSE_SINGLE_IMAGE_REQUIRED");
}
bytes = Buffer.from(response.data[0].b64_json, "base64");
mime = "image/png";
usageSummary = openAiUsage(response);
}
const media = inspectImage(bytes, mime);
return {
bytes,
dimensions: { height: media.height, width: media.width },
evidence_hash: `sha256:${sha256(bytes)}`,
mime: media.mime,
usage_summary: usageSummary,
};
}
export function buildSanitizedResponseEvidence(normalized) {
const evidence = {
dimensions: structuredClone(normalized.dimensions),
evidence_hash: normalized.evidence_hash,
mime: normalized.mime,
usage_summary: structuredClone(normalized.usage_summary),
};
return validateSanitizedEvidence(evidence);
}
function inspectEvidenceValue(value, seen = new Set()) {
if (value && typeof value === "object") {
if (seen.has(value)) throw new Error("WP7_02_EVIDENCE_CYCLE_FORBIDDEN");
seen.add(value);
for (const [key, entry] of Object.entries(value)) {
if (key === "verified") throw new Error("WP7_02_SHARED_VERIFIED_FORBIDDEN");
if (forbiddenEvidenceKeys.test(key)) throw new Error("WP7_02_SENSITIVE_EVIDENCE_FORBIDDEN");
inspectEvidenceValue(entry, seen);
}
seen.delete(value);
} else if (typeof value === "string" && /[A-Za-z]:\\Users\\/i.test(value)) {
throw new Error("WP7_02_SENSITIVE_EVIDENCE_FORBIDDEN");
}
}
export function validateSanitizedEvidence(evidence) {
inspectEvidenceValue(evidence);
return evidence;
}
export async function executeProviderRequest({ fetchImpl = fetch, modelConfig, prompt, ratio, reference, token, timeoutMs = 180_000 }) {
if (typeof token !== "string" || token.length < 8) throw new Error("WP7_02_CREDENTIAL_INVALID");
const request = buildProviderRequest({ modelConfig, prompt, ratio, reference });
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
const startedAt = performance.now();
try {
const response = await fetchImpl(request.url, {
body: JSON.stringify(request.body),
headers: { ...request.headers, authorization: `Bearer ${token}` },
method: request.method,
signal: controller.signal,
});
const durationMs = Math.round(performance.now() - startedAt);
if (!response.ok) throw new Error(`WP7_02_UPSTREAM_HTTP_${response.status}`);
const normalized = normalizeProviderResponse(modelConfig, await response.json());
return {
duration_ms: durationMs,
http_status: response.status,
normalized,
response_evidence: buildSanitizedResponseEvidence(normalized),
};
} catch (error) {
if (error?.name === "AbortError") throw new Error("WP7_02_UPSTREAM_TIMEOUT");
if (error instanceof Error && /^WP7_02_[A-Z0-9_]+$/.test(error.message)) throw error;
throw new Error("WP7_02_UPSTREAM_FAILED");
} finally {
clearTimeout(timeout);
}
}
+45 -9
View File
@@ -16,6 +16,12 @@ internal static class Program
return await RunCredentialChildAsync(); return await RunCredentialChildAsync();
} }
if (args.FirstOrDefault() == "--credential-echo")
{
Console.Write(await Console.In.ReadToEndAsync());
return 0;
}
if (args.FirstOrDefault() == "--instance-probe") if (args.FirstOrDefault() == "--instance-probe")
{ {
using var instance = await SingleInstanceCoordinator.TryAcquireAsync(args[1], args[2]); using var instance = await SingleInstanceCoordinator.TryAcquireAsync(args[1], args[2]);
@@ -120,6 +126,28 @@ internal static class Program
var workerProbe = await LaunchCredentialProbeAsync(ChildRole.Worker, store); var workerProbe = await LaunchCredentialProbeAsync(ChildRole.Worker, store);
EqualSequence(new[] { CredentialCatalog.WorkerAiGateway }, workerProbe.Names, "Worker credential scope"); EqualSequence(new[] { CredentialCatalog.WorkerAiGateway }, workerProbe.Names, "Worker credential scope");
var leakProbe = await CredentialProcessLauncher.RunToCompletionAsync(
new ProcessStartInfo(Environment.ProcessPath!, "--credential-echo"), ChildRole.Worker, store);
True(leakProbe.SensitiveOutputDetected, "credential echo must be detected");
Equal(string.Empty, leakProbe.StandardOutput, "credential echo output discarded");
Equal(string.Empty, leakProbe.StandardError, "credential echo error discarded");
var externalArguments = new[]
{
"--service", "ai-gateway-service-id",
"--model", "gpt-image-2",
"--run-id", "wp7-02-supervisor-probe",
"--candidate-record", "candidate.json",
"--config-manifest", "config.json",
"--evidence-dir", "evidence",
"--max-real-calls", "120",
"--confirm-controlled-real",
"--execute-controlled-real",
};
EqualSequence(externalArguments, ControlledExternalValidationLauncher.ValidateArguments(externalArguments), "controlled external argument allowlist");
Throws<ArgumentException>(
() => ControlledExternalValidationLauncher.ValidateArguments(externalArguments.Where(value => value != "--confirm-controlled-real").ToArray()),
"controlled external confirmation required");
store.Delete(CredentialCatalog.WorkerAiGateway); store.Delete(CredentialCatalog.WorkerAiGateway);
await ThrowsAsync<MissingCredentialException>( await ThrowsAsync<MissingCredentialException>(
@@ -166,15 +194,10 @@ internal static class Program
private static async Task<CredentialProbe> LaunchCredentialProbeAsync(ChildRole role, ICredentialStore store) private static async Task<CredentialProbe> LaunchCredentialProbeAsync(ChildRole role, ICredentialStore store)
{ {
var startInfo = new ProcessStartInfo(Environment.ProcessPath!, "--credential-child") var result = await CredentialProcessLauncher.RunToCompletionAsync(new ProcessStartInfo(Environment.ProcessPath!, "--credential-child"), role, store);
{ Equal(0, result.ExitCode, "credential child exit code");
RedirectStandardOutput = true, False(result.SensitiveOutputDetected, "credential child output contains injected value");
}; return JsonSerializer.Deserialize<CredentialProbe>(result.StandardOutput, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
using var process = await CredentialProcessLauncher.StartAsync(startInfo, role, store);
var output = await process.StandardOutput.ReadToEndAsync();
await process.WaitForExitAsync();
Equal(0, process.ExitCode, "credential child exit code");
return JsonSerializer.Deserialize<CredentialProbe>(output, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })
?? throw new InvalidOperationException("Credential child returned invalid JSON."); ?? throw new InvalidOperationException("Credential child returned invalid JSON.");
} }
@@ -355,6 +378,19 @@ internal static class Program
throw new InvalidOperationException(message); throw new InvalidOperationException(message);
} }
private static void Throws<TException>(Action action, string message) where TException : Exception
{
try
{
action();
}
catch (TException)
{
return;
}
throw new InvalidOperationException(message);
}
private sealed record CredentialProbe(string[] Names, bool EnvironmentContainsMarker, bool ArgumentsContainMarker); private sealed record CredentialProbe(string[] Names, bool EnvironmentContainsMarker, bool ArgumentsContainMarker);
private sealed class TestCredentialStore : ICredentialStore private sealed class TestCredentialStore : ICredentialStore
@@ -0,0 +1,138 @@
using System.Diagnostics;
using System.Text.Json;
using System.Text.RegularExpressions;
namespace Dada.Supervisor;
internal static partial class ControlledExternalValidationLauncher
{
private static readonly HashSet<string> AllowedModels =
[
"gemini-3.1-flash-image-preview",
"gemini-3-pro-image-preview",
"gpt-image-2",
];
private static readonly HashSet<string> ValueOptions =
[
"--candidate-record",
"--config-manifest",
"--evidence-dir",
"--max-real-calls",
"--model",
"--run-id",
"--service",
];
private static readonly HashSet<string> SwitchOptions =
[
"--confirm-controlled-real",
"--execute-controlled-real",
];
internal static async Task<int> RunAsync(string[] args, ICredentialStore credentials, CancellationToken cancellationToken = default)
{
var validated = ValidateArguments(args);
var script = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "scripts", "validate-external.mjs"));
if (!File.Exists(script)) throw new InvalidOperationException("external_validator_not_found");
var startInfo = new ProcessStartInfo("node") { WorkingDirectory = Environment.CurrentDirectory };
startInfo.ArgumentList.Add(script);
foreach (var value in validated) startInfo.ArgumentList.Add(value);
startInfo.ArgumentList.Add("--credential-stdin");
var result = await CredentialProcessLauncher.RunToCompletionAsync(startInfo, ChildRole.Worker, credentials, cancellationToken);
if (result.SensitiveOutputDetected || !TrySelectSanitizedJson(result, out var output, out var useError))
{
Console.Error.WriteLine("{\"code\":\"external_validator_output_invalid\",\"real_calls\":0,\"status\":\"failed\"}");
return 1;
}
if (useError) Console.Error.WriteLine(output); else Console.WriteLine(output);
return result.ExitCode;
}
internal static string[] ValidateArguments(string[] args)
{
var values = new Dictionary<string, string>(StringComparer.Ordinal);
var switches = new HashSet<string>(StringComparer.Ordinal);
for (var index = 0; index < args.Length; index++)
{
var option = args[index];
if (SwitchOptions.Contains(option))
{
if (!switches.Add(option)) throw new ArgumentException("external_validator_argument_duplicate");
continue;
}
if (!ValueOptions.Contains(option) || index + 1 >= args.Length || !values.TryAdd(option, args[++index]))
{
throw new ArgumentException("external_validator_argument_invalid");
}
}
if (values.GetValueOrDefault("--service") != "ai-gateway-service-id"
|| !AllowedModels.Contains(values.GetValueOrDefault("--model") ?? string.Empty)
|| !SafeRunId().IsMatch(values.GetValueOrDefault("--run-id") ?? string.Empty)
|| values.GetValueOrDefault("--max-real-calls") != "120"
|| !values.ContainsKey("--candidate-record")
|| !values.ContainsKey("--config-manifest")
|| !values.ContainsKey("--evidence-dir")
|| !switches.SetEquals(SwitchOptions))
{
throw new ArgumentException("external_validator_argument_invalid");
}
if (values.Values.Any(value => value.Length == 0 || value.IndexOfAny(['\r', '\n', '\0']) >= 0))
{
throw new ArgumentException("external_validator_argument_invalid");
}
return args.ToArray();
}
private static bool TrySelectSanitizedJson(CredentialProcessResult result, out string output, out bool useError)
{
var stdout = result.StandardOutput.Trim();
var stderr = result.StandardError.Trim();
useError = stdout.Length == 0;
output = useError ? stderr : stdout;
if (output.Length == 0 || (stdout.Length > 0 && stderr.Length > 0)) return false;
try
{
using var document = JsonDocument.Parse(output);
return IsSanitized(document.RootElement);
}
catch (JsonException)
{
return false;
}
}
private static bool IsSanitized(JsonElement element)
{
if (element.ValueKind == JsonValueKind.Object)
{
foreach (var property in element.EnumerateObject())
{
if (ForbiddenKey().IsMatch(property.Name) || property.NameEquals("verified") || !IsSanitized(property.Value)) return false;
}
}
else if (element.ValueKind == JsonValueKind.Array)
{
foreach (var item in element.EnumerateArray()) if (!IsSanitized(item)) return false;
}
else if (element.ValueKind == JsonValueKind.String)
{
var value = element.GetString() ?? string.Empty;
if (WindowsUserPath().IsMatch(value) || BearerValue().IsMatch(value)) return false;
}
return true;
}
[GeneratedRegex("^[A-Za-z0-9][A-Za-z0-9._-]{0,119}$", RegexOptions.CultureInvariant)]
private static partial Regex SafeRunId();
[GeneratedRegex("(?:^|_)(?:absolute_path|authorization|body|credential|image|password|path|prompt|raw|secret|token)(?:_|$)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
private static partial Regex ForbiddenKey();
[GeneratedRegex("[A-Za-z]:\\\\Users\\\\", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
private static partial Regex WindowsUserPath();
[GeneratedRegex("(?:Bearer\\s+|\\bsk-[A-Za-z0-9_-]{8,})", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
private static partial Regex BearerValue();
}
+50
View File
@@ -37,8 +37,58 @@ internal interface ICredentialStore
internal sealed class MissingCredentialException(string target) internal sealed class MissingCredentialException(string target)
: InvalidOperationException($"Required credential is not configured: {target}"); : InvalidOperationException($"Required credential is not configured: {target}");
internal sealed record CredentialProcessResult(int ExitCode, string StandardOutput, string StandardError, bool SensitiveOutputDetected);
internal static class CredentialProcessLauncher internal static class CredentialProcessLauncher
{ {
internal static async Task<CredentialProcessResult> RunToCompletionAsync(
ProcessStartInfo startInfo,
ChildRole role,
ICredentialStore store,
CancellationToken cancellationToken = default)
{
var credentials = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (var target in CredentialCatalog.RequiredFor(role))
{
credentials[target] = store.Read(target) ?? throw new MissingCredentialException(target);
}
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardInput = true;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
using var process = Process.Start(startInfo) ?? throw new InvalidOperationException("Unable to start credential child process.");
var outputTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
var errorTask = process.StandardError.ReadToEndAsync(cancellationToken);
var payload = JsonSerializer.SerializeToUtf8Bytes(credentials);
try
{
await process.StandardInput.BaseStream.WriteAsync(payload, cancellationToken);
await process.StandardInput.BaseStream.FlushAsync(cancellationToken);
process.StandardInput.Close();
await process.WaitForExitAsync(cancellationToken);
var output = await outputTask;
var error = await errorTask;
var sensitive = credentials.Values.Where(value => value.Length > 0).Any(value =>
output.Contains(value, StringComparison.Ordinal) || error.Contains(value, StringComparison.Ordinal));
return sensitive
? new CredentialProcessResult(1, string.Empty, string.Empty, true)
: new CredentialProcessResult(process.ExitCode, output, error, false);
}
catch
{
if (!process.HasExited) process.Kill(entireProcessTree: true);
throw;
}
finally
{
Array.Clear(payload);
foreach (var target in credentials.Keys.ToArray()) credentials[target] = string.Empty;
credentials.Clear();
}
}
internal static async Task<Process> StartAsync( internal static async Task<Process> StartAsync(
ProcessStartInfo startInfo, ProcessStartInfo startInfo,
ChildRole role, ChildRole role,
@@ -29,6 +29,7 @@ internal static class OfflineCommandRouter
"secrets" => RunSecrets(args.Skip(1).ToArray(), credentials), "secrets" => RunSecrets(args.Skip(1).ToArray(), credentials),
"admin-allowlist" => RunAdminAllowlist(args.Skip(1).ToArray(), credentials), "admin-allowlist" => RunAdminAllowlist(args.Skip(1).ToArray(), credentials),
"doctor" when args.Length == 1 => RunDoctor(credentials), "doctor" when args.Length == 1 => RunDoctor(credentials),
"validate-external" => await ControlledExternalValidationLauncher.RunAsync(args.Skip(1).ToArray(), credentials),
_ => Usage(), _ => Usage(),
}; };
} }
@@ -198,7 +199,7 @@ internal static class OfflineCommandRouter
private static int Usage() private static int Usage()
{ {
WriteResult("usage: configure init|data-root|asset-root; secrets set|status|clear; admin-allowlist add|remove|status; doctor", false); WriteResult("usage: configure init|data-root|asset-root; secrets set|status|clear; admin-allowlist add|remove|status; doctor; validate-external", false);
return 2; return 2;
} }
} }
@@ -6,6 +6,7 @@ import {
buildControlledExecutionPlan, buildControlledExecutionPlan,
buildProviderRequest, buildProviderRequest,
buildSanitizedResponseEvidence, buildSanitizedResponseEvidence,
executeProviderRequest,
normalizeProviderResponse, normalizeProviderResponse,
validateSanitizedEvidence, validateSanitizedEvidence,
} from "../../scripts/lib/wp7-02-controlled-executor.mjs"; } from "../../scripts/lib/wp7-02-controlled-executor.mjs";
@@ -107,3 +108,33 @@ test("TDD-WP7-EXT-001 rejects sensitive or shared evidence fields", () => {
} }
assert.throws(() => validateSanitizedEvidence({ status: "passed", verified: true }), /WP7_02_SHARED_VERIFIED_FORBIDDEN/); assert.throws(() => validateSanitizedEvidence({ status: "passed", verified: true }), /WP7_02_SHARED_VERIFIED_FORBIDDEN/);
}); });
test("TDD-WP7-EXT-001 confines the credential to the request header and discards provider error bodies", async () => {
const credentialMarker = "controlled-secret-value-for-test-only";
const success = await executeProviderRequest({
fetchImpl: async (_url, init) => {
assert.equal(init.headers.authorization, `Bearer ${credentialMarker}`);
return new Response(JSON.stringify({
candidates: [{ content: { parts: [{ inlineData: { data: onePixelPng.toString("base64"), mimeType: "image/png" } }] } }],
}), { headers: { "content-type": "application/json" }, status: 200 });
},
modelConfig: models[0],
prompt: "controlled fixture prompt",
ratio: "1:1",
token: credentialMarker,
});
assert.equal(success.http_status, 200);
assert.doesNotMatch(JSON.stringify({ ...success, normalized: undefined }), new RegExp(credentialMarker));
await assert.rejects(() => executeProviderRequest({
fetchImpl: async () => new Response(JSON.stringify({ provider_body: credentialMarker }), { status: 502 }),
modelConfig: models[0],
prompt: "controlled fixture prompt",
ratio: "1:1",
token: credentialMarker,
}), (error) => {
assert.equal(error.message, "WP7_02_UPSTREAM_HTTP_502");
assert.doesNotMatch(error.message, new RegExp(credentialMarker));
return true;
});
});