| 🛡️ |
Transient Security Explanation |
Closed-world author assist for one confirmed Defender scan contention. |
| pull_request_target |
roles |
bots |
|
|
|
| wingetvalidator-prod[bot] |
|
|
github.event_name == 'pull_request_target' && github.event.action == 'labeled' && github.event.label.name == 'Validation-Defender-Error' && github.actor == 'wingetvalidator-prod[bot]' && github.event.pull_request.user.login != 'wingetbot' |
false |
| group |
cancel-in-progress |
queue |
| gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} |
false |
max |
|
| name |
uses |
env |
with |
| Classify trusted validation operation |
actions/github-script@v9 |
| TARGET_PR |
TRIGGER_HEAD_SHA |
| ${{ github.event.pull_request.number || '' }} |
${{ github.event.pull_request.head.sha || '' }} |
|
| github-token |
script |
| ${{ github.token }} |
const fs = require("fs");
const zlib = require("zlib");
const outputPath = "/tmp/gh-aw/transient-security-evidence.json";
fs.mkdirSync("/tmp/gh-aw", { recursive: true });
const owner = "microsoft";
const repo = "winget-pkgs";
const appId = 1451866;
const appSlug = "wingetvalidator-prod";
const targetLabel = "Validation-Defender-Error";
const conflicts = new Set([
"Binary-Validation-Error", "Blocking-Issue",
"Error-Analysis-Timeout", "Error-Hash-Mismatch",
"Internal-Error", "Internal-Error-AppsAndFeaturesVersion",
"Internal-Error-Dependencies", "Internal-Error-Domain",
"Internal-Error-Dynamic-Scan", "Internal-Error-Keyword-Policy",
"Internal-Error-Manifest", "Internal-Error-Manifest-Installer",
"Internal-Error-NoArchitectures",
"Internal-Error-NoSupportedArchitectures", "Internal-Error-PR",
"Internal-Error-Static-Scan", "Internal-Error-URL",
"Internal-Error-Webhook", "Needs-SmartScreen-Investigation",
"Network-Blocker", "Package-Flagged", "PUA-Detection",
"PullRequest-Error", "Scripted-Application",
"URL-Validation-Error", "Validation-Certificate-Root",
"Validation-Defender-Error", "Validation-Executable-Error",
"Validation-Hash-Flagged", "Validation-Hash-Verification-Failed",
"Validation-HTTP-Error", "Validation-No-Executables",
"Validation-Shell-Execute", "Validation-SmartScreen",
"Validation-SmartScreen-Error", "Validation-Submission-Expired",
"Validation-Submission-Failed", "Validation-Submission-Mismatch",
"Validation-Submission-Missing",
"Validation-Submission-Unsupported",
"Validation-Virus-Scan-Error",
]);
const outcomes = new Map([
["01. Pull Request Validation", "success"],
["02. Manifest Validation", "success"],
["03. URLs Validation", "success"],
["04. URL Domain Validation", "success"],
["05. Manifest Policy Validation", "success"],
["06. Catalog Content Verification", "success"],
["07. Installers Scan", "success"],
["08. Installation Validation", "failure|action_required"],
["09. Installer Metadata Validation", "skipped"],
["10. Validation Completed", "success"],
]);
const prNumber = Number(process.env.TARGET_PR);
const triggerHead = String(process.env.TRIGGER_HEAD_SHA ?? "").trim();
const evidence = {
schemaVersion: 1, verdict: "noop", reasonCode: "unclassified",
pullRequestNumber: Number.isSafeInteger(prNumber) ? prNumber : null,
headSha: null, operationId: null, completionCheckId: null,
checkRunIds: null, facts: null,
};
const finish = (reason) => { evidence.reasonCode = reason; };
const trusted = (c, head) =>
c?.app?.id === appId && c?.app?.slug === appSlug && c?.head_sha === head;
const boundedOutput = (c) => {
const o = c?.output;
const skippedText = c?.name === "09. Installer Metadata Validation" &&
(o?.text === null || o?.text === "");
const values = [o?.title, o?.summary, skippedText ? "" : o?.text];
return values.every((v) => typeof v === "string") &&
o.title.length > 0 && o.summary.length > 0 &&
o.title.length <= 1000 && o.summary.length <= 10000 &&
values[2].length <= 30000 &&
(skippedText || values[2].length > 0) &&
!/\b(?:output (?:was )?truncated|truncated due to)\b|[\s*(?:output\s+)?truncated\s*]/i
.test(values.join("\n"));
};
const finding = (text) => [
/\b(?:detected|found|blocked|quarantined)\b.{0,100}\b(?:threat|malware|virus|trojan|pua)\b/is,
/\b(?:threat|malware|virus|trojan|pua)\b.{0,100}\b(?:detected|found|blocked|quarantined)\b/is,
/\bsmartscreen\b.{0,100}\b(?:blocked|malicious|unsafe|warning|failed)\b/is,
/\b(?:hash|signature|integrity)\b.{0,100}\b(?:mismatch|invalid|tampered|compromised)\b/is,
].some((pattern) => pattern.test(text));
const safePath = (name) =>
typeof name === "string" && name.length > 0 && name.length <= 240 &&
!/[\:\u0000-\u001f\u007f]/.test(name) && !name.startsWith("/") &&
name.split("/").every((part) => part && part !== "." && part !== "..");
const crcTable = Array.from({ length: 256 }, (_, n) => {
let c = n;
for (let i = 0; i < 8; i++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
return c >>> 0;
});
const crc32 = (buffer) => {
let crc = 0xffffffff;
for (const byte of buffer) crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8);
return (crc ^ 0xffffffff) >>> 0;
};
const readNamedLogs = (zip, artifact, declared, wanted) => {
let eocd = -1;
for (let i = zip.length - 22; i >= Math.max(0, zip.length - 65557); i--) {
if (zip.readUInt32LE(i) === 0x06054b50) { eocd = i; break; }
}
if (eocd < 0 || eocd + 22 + zip.readUInt16LE(eocd + 20) !== zip.length ||
zip.readUInt16LE(eocd + 4) !== 0 || zip.readUInt16LE(eocd + 6) !== 0 ||
zip.readUInt16LE(eocd + 8) !== artifact.TotalFilesCount ||
zip.readUInt16LE(eocd + 10) !== artifact.TotalFilesCount) throw new Error("zip_eocd");
const centralSize = zip.readUInt32LE(eocd + 12);
const centralOffset = zip.readUInt32LE(eocd + 16);
if (centralOffset + centralSize !== eocd) throw new Error("zip_central");
const decoder = new TextDecoder("utf-8", { fatal: true });
const entries = new Map();
let offset = centralOffset;
let totalSize = 0;
for (let i = 0; i < artifact.TotalFilesCount; i++) {
if (offset + 46 > eocd || zip.readUInt32LE(offset) !== 0x02014b50)
throw new Error("zip_entry");
const flags = zip.readUInt16LE(offset + 8);
const method = zip.readUInt16LE(offset + 10);
const crc = zip.readUInt32LE(offset + 16);
const compressedSize = zip.readUInt32LE(offset + 20);
const size = zip.readUInt32LE(offset + 24);
const nameLength = zip.readUInt16LE(offset + 28);
const extraLength = zip.readUInt16LE(offset + 30);
const commentLength = zip.readUInt16LE(offset + 32);
const localOffset = zip.readUInt32LE(offset + 42);
const end = offset + 46 + nameLength + extraLength + commentLength;
if (end > eocd || (flags & ~0x800) !== 0 || ![0, 8].includes(method) ||
extraLength > 256 || commentLength > 256 ||
[compressedSize, size, localOffset].includes(0xffffffff) ||
size > 100000 || compressedSize > artifact.ZipFileSizeBytes ||
(compressedSize === 0 ? size !== 0 : size > compressedSize * 100 + 1024))
throw new Error("zip_limits");
const name = decoder.decode(zip.subarray(offset + 46, offset + 46 + nameLength));
const expected = declared.get(name);
if (!safePath(name) || entries.has(name) || !expected || expected.SizeBytes !== size)
throw new Error("zip_declaration");
entries.set(name, { flags, method, crc, compressedSize, size, localOffset, name });
totalSize += size;
offset = end;
}
if (offset !== eocd || entries.size !== declared.size ||
totalSize !== artifact.TotalSizeBytes) throw new Error("zip_completeness");
const logs = new Map();
for (const name of wanted) {
const e = entries.get(name);
if (!e || e.localOffset + 30 > centralOffset ||
zip.readUInt32LE(e.localOffset) !== 0x04034b50 ||
zip.readUInt16LE(e.localOffset + 6) !== e.flags ||
zip.readUInt16LE(e.localOffset + 8) !== e.method) throw new Error("zip_local");
const nameLength = zip.readUInt16LE(e.localOffset + 26);
const extraLength = zip.readUInt16LE(e.localOffset + 28);
const dataOffset = e.localOffset + 30 + nameLength + extraLength;
const localName = decoder.decode(
zip.subarray(e.localOffset + 30, e.localOffset + 30 + nameLength));
if (localName !== name || extraLength > 256 ||
dataOffset + e.compressedSize > centralOffset) throw new Error("zip_local");
const compressed = zip.subarray(dataOffset, dataOffset + e.compressedSize);
const value = e.method === 0 ? Buffer.from(compressed) :
zlib.inflateRawSync(compressed, { maxOutputLength: e.size });
if (value.length !== e.size || crc32(value) !== e.crc) throw new Error("zip_crc");
logs.set(name, value);
}
return logs;
};
const progressPrefixes = (text, operationId) => {
const clean = text.split(/\r?\n/)
.map((line) => line.replace(/^\d{4}-\d\d-\d\d[ T]\d\d:\d\d:\d\dZ\s+/, ""))
.join("\n");
const blocks = [...clean.matchAll(/{[^{}]*}/g)].map((m) => {
try { return JSON.parse(m[0]); } catch { return null; }
});
if (!blocks.length || blocks.some((v) => !v)) return null;
const groups = new Map();
for (const row of blocks) {
if (row.OperationId !== operationId ||
!/^[A-Za-z0-9.-]+$/.test(row.Arch) ||
!/^[A-Za-z0-9.-]+$/.test(row.InstallerType) ||
typeof row.InstallerUrl !== "string" || typeof row.InstallerHash !== "string" ||
!["Waiting", "Completed"].includes(row.Status)) return null;
const scope = row.Scope == null ? "Undefined" : String(row.Scope);
const locale = row.Locale == null ? "Undefined" : String(row.Locale);
if (!/^[A-Za-z0-9.-]+$/.test(scope) || !/^[A-Za-z0-9.-]+$/.test(locale)) return null;
const key = `${row.Arch}-Scope_${scope}-Locale_${locale}`.toLowerCase();
if (!groups.has(key)) groups.set(key, []);
groups.get(key).push(row.Status);
}
for (const states of groups.values())
if (states.length !== 2 || !states.includes("Waiting") || !states.includes("Completed"))
return null;
return new Set(groups.keys());
};
const classifyLogs = (logs) => {
let contentionCount = 0;
for (const bytes of logs.values()) {
if (bytes.length < 2 || bytes.length % 2 || bytes.length > 20000) return false;
let zeroes = 0;
for (let i = 1; i < bytes.length; i += 2) if (bytes[i] === 0) zeroes++;
if (zeroes < bytes.length / 8) return false;
const text = bytes.toString("utf16le");
if (text.includes("\0") || finding(text)) return false;
const starts = [...text.matchAll(/^MpCmdRun: Command Line:.*$/gmi)];
let scanCount = 0;
for (let i = 0; i < starts.length; i++) {
const record = text.slice(starts[i].index, starts[i + 1]?.index ?? text.length);
const command = starts[i][0];
if (!/(?:^|\s)-Scan(?:\s|$)/i.test(command)) continue;
scanCount++;
const results = [...record.matchAll(/^MpCmdRun.exe:\s*hr\s*=\s*(0x[0-9a-f]+).\s*$/gmi)];
const errors = [...record.matchAll(/^ERROR:\s*(.+?)\s*$/gmi)].map((m) => m[1]);
if (results.length !== 1 || !/^MpCmdRun: End Time:/mi.test(record)) return false;
const code = results[0][1].toLowerCase();
if (code === "0x0") {
if (errors.length) return false;
} else if (code === "0x8050111c" && errors.length === 1 &&
/^Another scan already in progress,\s*cannot start scan$/i.test(errors[0])) {
contentionCount++;
} else {
return false;
}
}
if (scanCount !== 1) return false;
}
return contentionCount > 0;
};
try {
if (!Number.isSafeInteger(prNumber) || prNumber <= 0 ||
!/^[0-9a-f]{40}$/.test(triggerHead)) return finish("invalid_target");
const getPull = () => github.rest.pulls.get({ owner, repo, pull_number: prNumber });
const initial = (await getPull()).data;
const head = String(initial.head?.sha ?? "");
const labels = new Set((initial.labels ?? []).map((l) => l.name));
evidence.headSha = head || null;
if (initial.state !== "open" || head !== triggerHead) return finish("stale_head");
if (!labels.has(targetLabel)) return finish("inactive_label");
if ([...conflicts].some((l) => l !== targetLabel && labels.has(l)))
return finish("conflicting_label");
const response = await github.rest.checks.listForRef({
owner, repo, ref: head, app_id: appId, filter: "latest", per_page: 100,
});
const runs = response.data.check_runs;
if (!Array.isArray(runs) || response.data.total_count !== runs.length || runs.length > 100)
return finish("check_response_incomplete");
const completions = runs.filter((c) =>
trusted(c, head) && c.name === "10. Validation Completed" &&
c.status === "completed" && Number.isFinite(Date.parse(c.completed_at)))
.sort((a, b) => Date.parse(b.completed_at) - Date.parse(a.completed_at) ||
Number(b.id) - Number(a.id));
const completion = completions[0];
if (!completion || !boundedOutput(completion)) return finish("completion_missing");
const operationId = String(completion.external_id ?? "").trim();
if (!new RegExp(`^WinGetSvc-Validation-${prNumber}-[0-9]+$`).test(operationId))
return finish("completion_untrusted");
const blocks = [...completion.output.text.matchAll(/```json\s*([\s\S]*?)```/gi)];
let payload = null;
try { if (blocks.length === 1) payload = JSON.parse(blocks[0][1]); } catch {}
if (payload?.PullRequestNumber !== prNumber || payload?.OperationId !== operationId ||
!Array.isArray(payload.Labels) || payload.Labels.length !== 1 ||
payload.Labels[0]?.Name !== targetLabel || payload.Labels[0]?.Result !== "TestPlan")
return finish("completion_untrusted");
const operation = runs.filter((c) =>
trusted(c, head) && String(c.external_id ?? "").trim() === operationId);
if (operation.length !== outcomes.size) return finish("operation_incomplete");
const byName = new Map();
for (const check of operation) {
if (!outcomes.has(check.name) || byName.has(check.name) || !boundedOutput(check))
return finish("evidence_unavailable");
byName.set(check.name, check);
}
for (const [name, allowed] of outcomes) {
const check = byName.get(name);
if (check?.status !== "completed" ||
!new RegExp(`^(?:${allowed})$`).test(String(check.conclusion).toLowerCase()))
return finish("unsupported_check_outcome");
}
if (finding(operation.map((c) =>
`${c.output.title}\n${c.output.summary}\n${c.output.text}`).join("\n")))
return finish("security_finding");
const artifact = payload.Artifacts;
if (!artifact || artifact.OperationId !== operationId ||
artifact.ZipFileName !== `${operationId}-artifacts.zip` ||
!Number.isSafeInteger(artifact.ZipFileSizeBytes) ||
artifact.ZipFileSizeBytes <= 0 || artifact.ZipFileSizeBytes > 1000000 ||
!Number.isSafeInteger(artifact.TotalFilesCount) ||
artifact.TotalFilesCount <= 0 || artifact.TotalFilesCount > 64 ||
!Number.isSafeInteger(artifact.TotalSizeBytes) ||
artifact.TotalSizeBytes <= 0 || artifact.TotalSizeBytes > 1000000 ||
!Array.isArray(artifact.ValidationResults) ||
!Array.isArray(artifact.InstallationLogs)) return finish("artifact_untrusted");
const declarations = [...artifact.ValidationResults, ...artifact.InstallationLogs];
if (declarations.length !== artifact.TotalFilesCount) return finish("artifact_untrusted");
const declared = new Map();
for (const item of declarations) {
if (!safePath(item?.RelativePath) || item.FileName !== item.RelativePath.split("/").at(-1) ||
!Number.isSafeInteger(item.SizeBytes) || item.SizeBytes < 0 ||
item.SizeBytes > 100000 || declared.has(item.RelativePath))
return finish("artifact_untrusted");
declared.set(item.RelativePath, item);
}
if ([...declared.values()].reduce((sum, item) => sum + item.SizeBytes, 0) !==
artifact.TotalSizeBytes) return finish("artifact_untrusted");
const analysisItems = artifact.InstallationLogs.filter((item) =>
/^[A-Za-z0-9._-]+-MpCmdRunanalysis.log$/.test(item.FileName) &&
item.RelativePath === `InstallationVerificationLogs/${item.FileName}`);
if (!analysisItems.length || analysisItems.length > 8 ||
analysisItems.length !== artifact.InstallationLogs.filter((item) =>
/MpCmdRunanalysis/i.test(String(item?.FileName))).length ||
analysisItems.some((item) => item.SizeBytes <= 0 || item.SizeBytes > 20000))
return finish("artifact_untrusted");
const expectedPrefixes = progressPrefixes(
byName.get("08. Installation Validation").output.text, operationId);
const logPrefixes = new Set(analysisItems.map((item) =>
item.FileName.slice(0, -"-MpCmdRunanalysis.log".length).toLowerCase()));
if (!expectedPrefixes || expectedPrefixes.size !== logPrefixes.size ||
[...expectedPrefixes].some((prefix) => !logPrefixes.has(prefix)))
return finish("record_binding_failed");
let url;
try { url = new URL(artifact.ArtifactDownloadUrl); } catch {
return finish("artifact_untrusted");
}
if (url.protocol !== "https:" || url.hostname !== "cdn.winget.microsoft.com" ||
url.port || url.username || url.password || url.search || url.hash ||
url.pathname !== `/artifacts/${artifact.ZipFileName}`)
return finish("artifact_untrusted");
const download = await fetch(url, {
redirect: "error", signal: AbortSignal.timeout(15000),
headers: { accept: "application/octet-stream" },
});
if (download.status !== 200 ||
download.headers.get("content-type") !== "application/octet-stream" ||
Number(download.headers.get("content-length")) !== artifact.ZipFileSizeBytes)
return finish("artifact_download_failed");
const reader = download.body?.getReader();
if (!reader) return finish("artifact_download_failed");
const chunks = []; let received = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
received += value.byteLength;
if (received > artifact.ZipFileSizeBytes) {
await reader.cancel(); return finish("artifact_download_failed"); }
chunks.push(Buffer.from(value));
}
if (received !== artifact.ZipFileSizeBytes) return finish("artifact_download_failed");
const zip = Buffer.concat(chunks, received);
let logs;
try {
logs = readNamedLogs(zip, artifact, declared,
analysisItems.map((item) => item.RelativePath));
} catch {
return finish("artifact_invalid");
}
if (!classifyLogs(logs)) return finish("unsupported_scan_result");
const final = (await getPull()).data;
const finalLabels = new Set((final.labels ?? []).map((l) => l.name));
if (final.state !== "open" || final.head?.sha !== head ||
!finalLabels.has(targetLabel) ||
[...conflicts].some((l) => l !== targetLabel && finalLabels.has(l)))
return finish("stale_after_classification");
evidence.verdict = "transient_defender_scan_contention";
evidence.reasonCode = "supported";
evidence.operationId = operationId;
evidence.completionCheckId = completion.id;
evidence.checkRunIds = operation.map((check) => check.id).sort(
(left, right) => left - right,
);
evidence.facts = {
completeTrustedOperation: true,
allInstallationRecordsBound: true,
allSecurityScanFailuresExactContention: true,
transientScanContentionConfirmed: true,
actualSecurityFindingExcluded: true,
};
} catch {
finish("retrieval_failed");
} finally {
fs.writeFileSync(outputPath, JSON.stringify(evidence));
}
|
|
|
| name |
uses |
env |
with |
| Skip agent when transient-security evidence is ineligible |
actions/github-script@v9 |
| GH_AW_SAFE_OUTPUTS |
| ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} |
|
| script |
| const fs = require("fs");
const path = require("path");
const evidence = JSON.parse(
fs.readFileSync(
"/tmp/gh-aw/transient-security-evidence.json",
"utf8",
),
);
if (evidence.verdict !== "transient_defender_scan_contention") {
const safeOutputsPath =
String(process.env.GH_AW_SAFE_OUTPUTS ?? "").trim() ||
path.join(
process.env.RUNNER_TEMP || "/tmp",
"gh-aw",
"safeoutputs",
"outputs.jsonl",
);
fs.mkdirSync(path.dirname(safeOutputsPath), { recursive: true });
fs.appendFileSync(
safeOutputsPath,
`${JSON.stringify({
type: "noop",
message: "No eligible transient-security evidence is available.",
})}\n`,
);
}
|
|
|
| name |
uses |
with |
| Seal deterministic transient-security evidence |
actions/upload-artifact@v7 |
| name |
path |
if-no-files-found |
retention-days |
compression-level |
| transient-security-evidence-${{ github.run_id }}-${{ github.run_attempt }} |
/tmp/gh-aw/transient-security-evidence.json |
error |
1 |
0 |
|
|
|
copilot |
| checks |
contents |
issues |
pull-requests |
copilot-requests |
| read |
read |
read |
read |
write |
|
| allowed |
| defaults |
| cdn.winget.microsoft.com |
|
|
| github |
bash |
| toolsets |
allowed-repos |
min-integrity |
| context |
| repos |
| issues |
| pull_requests |
|
|
none |
|
|
|
| threat-detection |
report-failure-as-issue |
report-failed-jobs |
report-incomplete |
noop |
missing-tool |
missing-data |
jobs |
| true |
false |
false |
|
|
false |
false |
| post-transient-security-comment |
| description |
runs-on |
output |
if |
inputs |
permissions |
steps |
| Post the fixed transient-security explanation to the triggering PR. |
ubuntu-slim |
Transient-security explanation posted. |
needs.detection.result == 'success' && needs.detection.outputs.detection_success == 'true' |
| body |
| description |
required |
type |
| Exact body without the footer |
true |
string |
|
|
| checks |
pull-requests |
| read |
write |
|
| name |
uses |
with |
| Download sealed transient-security evidence |
actions/download-artifact@v8 |
| name |
path |
| transient-security-evidence-${{ github.run_id }}-${{ github.run_attempt }} |
${{ runner.temp }}/gh-aw/trusted-evidence |
|
|
| name |
uses |
env |
with |
| Validate and post fixed-target comment |
actions/github-script@v9 |
| EVIDENCE_ARTIFACT |
EVIDENCE_DIR |
| transient-security-evidence-${{ github.run_id }}-${{ github.run_attempt }} |
${{ runner.temp }}/gh-aw/trusted-evidence |
|
| github-token |
script |
| ${{ github.token }} |
const fs = require("fs");
const owner = "microsoft", repo = "winget-pkgs";
const label = "Validation-Defender-Error", footer = "###### Template: msftbot/authorAssist/transientSecurity";
const appId = 1451866, appSlug = "wingetvalidator-prod";
const outcomes = new Map([
["01. Pull Request Validation", "success"],
["02. Manifest Validation", "success"],
["03. URLs Validation", "success"],
["04. URL Domain Validation", "success"],
["05. Manifest Policy Validation", "success"],
["06. Catalog Content Verification", "success"],
["07. Installers Scan", "success"],
["08. Installation Validation", "failure|action_required"],
["09. Installer Metadata Validation", "skipped"],
["10. Validation Completed", "success"],
]);
const conflicts = new Set([
"Binary-Validation-Error", "Blocking-Issue",
"Error-Analysis-Timeout", "Error-Hash-Mismatch",
"Internal-Error", "Internal-Error-AppsAndFeaturesVersion",
"Internal-Error-Dependencies", "Internal-Error-Domain",
"Internal-Error-Dynamic-Scan", "Internal-Error-Keyword-Policy",
"Internal-Error-Manifest", "Internal-Error-Manifest-Installer",
"Internal-Error-NoArchitectures",
"Internal-Error-NoSupportedArchitectures", "Internal-Error-PR",
"Internal-Error-Static-Scan", "Internal-Error-URL",
"Internal-Error-Webhook", "Needs-SmartScreen-Investigation",
"Network-Blocker", "Package-Flagged", "PUA-Detection",
"PullRequest-Error", "Scripted-Application",
"URL-Validation-Error", "Validation-Certificate-Root",
"Validation-Defender-Error", "Validation-Executable-Error",
"Validation-Hash-Flagged", "Validation-Hash-Verification-Failed",
"Validation-HTTP-Error", "Validation-No-Executables",
"Validation-Shell-Execute", "Validation-SmartScreen",
"Validation-SmartScreen-Error", "Validation-Submission-Expired",
"Validation-Submission-Failed",
"Validation-Submission-Mismatch",
"Validation-Submission-Missing",
"Validation-Submission-Unsupported",
"Validation-Virus-Scan-Error",
]);
const trusted = (check, head) =>
check?.app?.id === appId &&
check?.app?.slug === appSlug &&
check?.head_sha === head;
const boundedOutput = (check) => {
const output = check?.output;
const skippedText =
check?.name === "09. Installer Metadata Validation" &&
(output?.text === null || output?.text === "");
const values = [
output?.title,
output?.summary,
skippedText ? "" : output?.text,
];
return values.every((value) => typeof value === "string") &&
output.title.length > 0 && output.summary.length > 0 &&
output.title.length <= 1000 &&
output.summary.length <= 10000 &&
values[2].length <= 30000 &&
(skippedText || values[2].length > 0) &&
!/\b(?:output (?:was )?truncated|truncated due to)\b|[\s*(?:output\s+)?truncated\s*]/i
.test(values.join("\n"));
};
const finding = (text) => [
/\b(?:detected|found|blocked|quarantined)\b.{0,100}\b(?:threat|malware|virus|trojan|pua)\b/is,
/\b(?:threat|malware|virus|trojan|pua)\b.{0,100}\b(?:detected|found|blocked|quarantined)\b/is,
/\bsmartscreen\b.{0,100}\b(?:blocked|malicious|unsafe|warning|failed)\b/is,
/\b(?:hash|signature|integrity)\b.{0,100}\b(?:mismatch|invalid|tampered|compromised)\b/is,
].some((pattern) => pattern.test(text));
const parseCompletion = (check) => {
if (!boundedOutput(check)) return null;
const blocks = [
...check.output.text.matchAll(
/```json\s*([\s\S]*?)```/gi,
),
];
try {
return blocks.length === 1
? JSON.parse(blocks[0][1]) : null;
} catch {
return null;
}
};
const eventPr = context.payload.pull_request;
const prNumber = Number(eventPr?.number), eventHead = String(eventPr?.head?.sha ?? "");
if (context.eventName !== "pull_request_target" || context.payload.action !== "labeled" ||
context.payload.label?.name !== label || context.repo.owner !== owner ||
context.actor !== "wingetvalidator-prod[bot]" || context.repo.repo !== repo ||
!Number.isSafeInteger(prNumber) || prNumber <= 0 ||
!/^[0-9a-f]{40}$/.test(eventHead)) return;
const evidenceArtifact = process.env.EVIDENCE_ARTIFACT;
const expectedArtifact =
`transient-security-evidence-${process.env.GITHUB_RUN_ID}-${process.env.GITHUB_RUN_ATTEMPT}`;
const evidenceDir = process.env.EVIDENCE_DIR;
if (evidenceArtifact !== expectedArtifact || !evidenceDir ||
!fs.existsSync(evidenceDir)) return;
const entries = fs.readdirSync(evidenceDir, { withFileTypes: true });
if (entries.length !== 1 || !entries[0].isFile() ||
entries[0].name !== "transient-security-evidence.json") return;
const evidenceFile = `${evidenceDir}/transient-security-evidence.json`;
const evidenceStat = fs.lstatSync(evidenceFile);
if (!evidenceStat.isFile() || evidenceStat.size < 2 || evidenceStat.size > 4096) return;
const evidence = JSON.parse(fs.readFileSync(evidenceFile, "utf8"));
const evidenceKeys = [
"schemaVersion", "verdict", "reasonCode", "pullRequestNumber",
"headSha", "operationId", "completionCheckId", "checkRunIds",
"facts",
];
const requiredFacts = [
"completeTrustedOperation", "allInstallationRecordsBound",
"allSecurityScanFailuresExactContention", "transientScanContentionConfirmed",
"actualSecurityFindingExcluded",
];
if (evidence?.schemaVersion !== 1 ||
Object.keys(evidence).length !== evidenceKeys.length ||
!evidenceKeys.every((key) => Object.hasOwn(evidence, key)) ||
evidence.verdict !== "transient_defender_scan_contention" ||
evidence.reasonCode !== "supported" || evidence.pullRequestNumber !== prNumber ||
evidence.headSha !== eventHead ||
!new RegExp(
`^WinGetSvc-Validation-${prNumber}-[0-9]+$`,
).test(evidence.operationId) ||
!Number.isSafeInteger(evidence.completionCheckId) ||
!Array.isArray(evidence.checkRunIds) ||
evidence.checkRunIds.length !== 10 ||
evidence.checkRunIds.some(
(id) => !Number.isSafeInteger(id),
) ||
new Set(evidence.checkRunIds).size !==
evidence.checkRunIds.length ||
!evidence.checkRunIds.includes(
evidence.completionCheckId,
) ||
!evidence.facts ||
Object.keys(evidence.facts).length !== requiredFacts.length ||
!requiredFacts.every((fact) => evidence.facts[fact] === true)) return;
const outputFile = process.env.GH_AW_AGENT_OUTPUT;
if (!outputFile || fs.statSync(outputFile).size > 100000) return;
const output = JSON.parse(fs.readFileSync(outputFile, "utf8"));
const items = Array.isArray(output.items) ?
output.items.filter((item) => item?.type === "post_transient_security_comment") : [];
if (items.length !== 1 || Object.keys(items[0]).some(
(key) => !["type", "body"].includes(key))) return;
const expectedBody = [
"> [!NOTE]",
"> **Temporary validation-infrastructure condition**",
">",
"> The validator reported a transient Microsoft Defender scan-contention condition. This is not a",
"> malware finding, but it is also not a successful security scan. No manifest change is indicated",
"> by this validation result. A maintainer may revalidate the pull request.",
">",
`> Head SHA: `${eventHead}``,
].join("\n");
const body = items[0].body;
if (typeof body !== "string" || body.length < 200 || body.length > 1200 ||
body.includes("@") || body.trim() !== expectedBody) return;
const { data: pull } = await github.rest.pulls.get({
owner, repo, pull_number: prNumber,
});
const labels = new Set((pull.labels ?? []).map((item) => item.name));
if (pull.number !== prNumber || pull.state !== "open" || pull.head?.sha !== eventHead ||
pull.user?.login === "wingetbot" || !labels.has(label) ||
[...conflicts].some((item) => item !== label && labels.has(item))) return;
const comments = []; let complete = false;
for (let page = 1; page <= 10; page++) {
const result = await github.rest.issues.listComments(
{ owner, repo, issue_number: prNumber, per_page: 100, page });
comments.push(...result.data);
if (result.data.length < 100) { complete = true; break; }
}
if (!complete || comments.some((comment) => String(comment.body ?? "").includes(footer) &&
String(comment.body ?? "").includes(`Head SHA: `${eventHead}``))) return;
const { data: finalPull } = await github.rest.pulls.get({
owner, repo, pull_number: prNumber,
});
const finalLabels = new Set(
(finalPull.labels ?? []).map((item) => item.name),
);
if (
finalPull.state !== "open" ||
finalPull.head?.sha !== eventHead ||
!finalLabels.has(label) ||
[...conflicts].some(
(item) => item !== label && finalLabels.has(item),
)
) return;
// Keep trusted Checks as the final evidence read before posting.
const response = await github.rest.checks.listForRef({
owner, repo, ref: eventHead, app_id: appId,
filter: "all", per_page: 100, page: 1,
});
const runs = response.data?.check_runs ?? [];
if (
response.data?.total_count !== runs.length ||
runs.length > 100
) return;
const trustedRuns = runs.filter(
(check) => trusted(check, eventHead),
);
const operationPattern = new RegExp(
`^WinGetSvc-Validation-${prNumber}-([0-9]+)$`,
);
const selectedSequence = operationPattern.exec(
evidence.operationId,
);
const operationSequences = trustedRuns.map((check) => {
const match = operationPattern.exec(
String(check.external_id ?? "").trim(),
);
return match ? BigInt(match[1]) : null;
});
if (
!selectedSequence ||
operationSequences.some(
(sequence) =>
sequence === null ||
sequence > BigInt(selectedSequence[1]),
)
) return;
const completions = trustedRuns.filter(
(check) =>
check.name === "10. Validation Completed" &&
check.status === "completed" &&
Number.isSafeInteger(check.id) &&
Number.isFinite(Date.parse(check.completed_at ?? "")),
).sort(
(left, right) =>
Date.parse(right.completed_at) -
Date.parse(left.completed_at) ||
right.id - left.id,
);
const completion = completions[0];
const payload = parseCompletion(completion);
const completionTime = Date.parse(
completion?.completed_at ?? "",
);
const newerTrustedRun = trustedRuns.some(
(check) =>
check.id !== completion?.id &&
(
(
["queued", "in_progress"].includes(check.status) &&
String(check.external_id ?? "").trim() !==
evidence.operationId
) ||
!Number.isSafeInteger(check.id) ||
check.id > completion?.id ||
Date.parse(check.started_at ?? "") > completionTime
),
);
const operation = trustedRuns.filter(
(check) =>
String(check.external_id ?? "").trim() ===
evidence.operationId,
);
const freshIds = operation.map((check) => check.id).sort(
(left, right) => left - right,
);
const byName = new Map();
for (const check of operation) {
if (
!outcomes.has(check.name) ||
byName.has(check.name) ||
!boundedOutput(check)
) return;
byName.set(check.name, check);
}
if (
!completion ||
completion.id !== evidence.completionCheckId ||
completion.conclusion !== "success" ||
String(completion.external_id ?? "").trim() !==
evidence.operationId ||
payload?.PullRequestNumber !== prNumber ||
payload?.OperationId !== evidence.operationId ||
!Array.isArray(payload?.Labels) ||
payload.Labels.length !== 1 ||
payload.Labels[0]?.Name !== label ||
payload.Labels[0]?.Result !== "TestPlan" ||
newerTrustedRun ||
operation.length !== outcomes.size ||
freshIds.join(",") !== evidence.checkRunIds.join(",")
) return;
for (const [name, allowed] of outcomes) {
const check = byName.get(name);
if (
check?.status !== "completed" ||
!new RegExp(`^(?:${allowed})$`).test(
String(check.conclusion).toLowerCase(),
)
) return;
}
if (finding(operation.map((check) =>
`${check.output.title}\n${check.output.summary}\n` +
check.output.text).join("\n"))) return;
if (process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true") return;
await github.rest.issues.createComment({
owner, repo, issue_number: prNumber, body: `${expectedBody}\n\n${footer}`,
});
|
|
|
|
|
|
|