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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/patch-github-guard-difc-footer-notice.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

136 changes: 136 additions & 0 deletions actions/setup/js/gateway_difc_filtered.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// @ts-check
/// <reference types="@actions/github-script" />

/**
* Gateway DIFC Filtered Module
*
* This module handles reading MCP gateway logs and extracting DIFC_FILTERED events
* for display in AI-generated footers.
*/

const fs = require("fs");

const GATEWAY_JSONL_PATH = "/tmp/gh-aw/mcp-logs/gateway.jsonl";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GATEWAY_JSONL_PATH constant is hardcoded to /tmp/gh-aw/mcp-logs/gateway.jsonl. Consider making this configurable via environment variable to support different deployment environments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These path constants could be configurable via environment variables to improve testability and flexibility across different deployment environments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded path /tmp/gh-aw/mcp-logs/gateway.jsonl looks good for the CI environment. Consider making this configurable via an environment variable for flexibility in different deployment contexts.

const RPC_MESSAGES_PATH = "/tmp/gh-aw/mcp-logs/rpc-messages.jsonl";

/**
* Parses JSONL content and extracts DIFC_FILTERED events
* @param {string} jsonlContent - The JSONL file content
* @returns {Array<Object>} Array of DIFC_FILTERED event objects
*/
function parseDifcFilteredEvents(jsonlContent) {
const filteredEvents = [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good approach using a pre-allocated array for filteredEvents. The early-continue with !trimmed.includes("DIFC_FILTERED") is an efficient short-circuit before the more expensive JSON.parse.

const lines = jsonlContent.split("\n");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using jsonlContent.split("\n").filter(Boolean) to avoid processing empty lines before the loop, which would simplify the inner if (!trimmed ...) check.

for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || !trimmed.includes("DIFC_FILTERED")) continue;
try {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good optimization using a string pre-check before attempting JSON.parse. This avoids the overhead of parsing lines that clearly won't contain DIFC_FILTERED events. 👍

const entry = JSON.parse(trimmed);
if (entry.type === "DIFC_FILTERED") {
filteredEvents.push(entry);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The early-exit check !trimmed.includes("DIFC_FILTERED") is a nice optimization to skip JSON parsing on irrelevant lines. Consider also checking for the opening brace trimmed.startsWith("{") to skip non-JSON lines even faster before the includes check.

}
} catch {
// skip malformed lines
}
}
return filteredEvents;
Comment on lines +21 to +36
}

/**
* Reads DIFC_FILTERED events from MCP gateway logs.
*
* This function checks two possible locations for gateway logs:
* 1. Path specified by gatewayJsonlPath (or /tmp/gh-aw/mcp-logs/gateway.jsonl by default)
* 2. Path specified by rpcMessagesPath (or /tmp/gh-aw/mcp-logs/rpc-messages.jsonl as fallback)
*
* @param {string} [gatewayJsonlPath] - Path to gateway.jsonl. Defaults to /tmp/gh-aw/mcp-logs/gateway.jsonl
* @param {string} [rpcMessagesPath] - Path to rpc-messages.jsonl fallback. Defaults to /tmp/gh-aw/mcp-logs/rpc-messages.jsonl
* @returns {Array<Object>} Array of DIFC_FILTERED event objects
*/
function getDifcFilteredEvents(gatewayJsonlPath, rpcMessagesPath) {
const jsonlPath = gatewayJsonlPath || GATEWAY_JSONL_PATH;
const rpcPath = rpcMessagesPath || RPC_MESSAGES_PATH;

if (fs.existsSync(jsonlPath)) {
try {
const content = fs.readFileSync(jsonlPath, "utf8");
return parseDifcFilteredEvents(content);
} catch {
return [];
}
}

if (fs.existsSync(rpcPath)) {
try {
const content = fs.readFileSync(rpcPath, "utf8");
return parseDifcFilteredEvents(content);
} catch {
return [];
}
}

return [];
}

/**
* Generates HTML details/summary section for integrity-filtered items wrapped in a GitHub note alert.
* @param {Array<Object>} filteredEvents - Array of DIFC_FILTERED event objects
* @returns {string} GitHub note alert with details section, or empty string if no filtered events
*/
function generateDifcFilteredSection(filteredEvents) {
if (!filteredEvents || filteredEvents.length === 0) {
return "";
}

// Deduplicate events by their significant fields
const seen = new Set();
const uniqueEvents = filteredEvents.filter(event => {
const key = [event.html_url || "", event.tool_name || "", event.description || "", event.reason || ""].join("|");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deduplication key includes all four fields joined by |. If any of these fields contain a | character, it could cause false negatives (different events treated as duplicates). Consider using a safer separator like \0 (null byte) or JSON serialization for the key.

if (seen.has(key)) return false;
seen.add(key);
return true;
});

const count = uniqueEvents.length;
const itemWord = count === 1 ? "item" : "items";

let section = "\n\n> [!NOTE]\n";
section += `> <details>\n`;
section += `> <summary>🔒 Integrity filtering filtered ${count} ${itemWord}</summary>\n`;
section += `>\n`;
section += `> Integrity filtering activated and filtered the following ${itemWord} during workflow execution.\n`;
section += `> This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.\n`;
section += `>\n`;

const maxItems = 16;
const visibleEvents = uniqueEvents.slice(0, maxItems);
const remainingCount = uniqueEvents.length - visibleEvents.length;

for (const event of visibleEvents) {
let reference;
if (event.html_url) {
const label = event.number ? `#${event.number}` : event.html_url;
reference = `[${label}](${event.html_url})`;
} else {
reference = event.description || (event.tool_name ? `\`${event.tool_name}\`` : "-");
}
const tool = event.tool_name ? `\`${event.tool_name}\`` : "-";
const reason = (event.reason || "-").replace(/\n/g, " ");
section += `> - ${reference} (${tool}: ${reason})\n`;
}

if (remainingCount > 0) {
section += `> - ... and ${remainingCount} more ${remainingCount === 1 ? "item" : "items"}\n`;
}

section += `>\n`;
section += `> </details>\n`;

return section;
}

module.exports = {
parseDifcFilteredEvents,
getDifcFilteredEvents,
generateDifcFilteredSection,
};
Loading
Loading